#!/usr/bin/wish if [catch {package require Hal} msg] { puts "\nProblem: $msg" puts "Is linuxcnc installed?" puts "If using Run-In-Place build, source scripts/rip-environment first" exit 1 } proc usage {} { puts " Usage: $::SP(progname) \[Options\] name1 \[name2 ...\] & Options: --help (this text) --title title_string (window title, default: $::SP(progname)) Note: LinuxCNC (or a standalone Hal application) must be running A named item can specify a pin, param, or signal The item must be writable, e.g.: pin: IN or I/O (and not connected to a signal with a writer) param: RW signal: connected to a writable pin Hal item types bit,s32,u32,float are supported When a bit item is specified, a pushbutton is created to manage the item in one of three manners specified by radio buttons: toggle: Toggle value when button pressed pulse: Pulse item to 1 once when button pressed hold: Set to 1 while button pressed The bit pushbutton mode can be specified on the command line by formatting the item name: namei/mode=\[toggle | pulse | hold\] If the bit item mode begins with an uppercase letter, the radio buttons for selecting other modes are not shown " exit 1 } ;# usage proc add_item_to_gui {id itemname} { set l [split $itemname /] set itemname [lindex $l 0] set itemargs [lindex $l 1] set ::SP($id,onemode) 0 if { [string first "mode=" "$itemargs"] == 0} { set themode [lindex [split $itemargs =] 1] set firstchar [string range "$themode" 0 0] if {[string first "$firstchar" "PTH"] >= 0} { set ::SP($id,onemode) 1 } set ::SP($id,mode) [string tolower $themode] } else { set ::SP($id,mode) "default" } set ::SP($id,itemname) $itemname if ![item_info $itemname $id] { puts "$::SP(message)" return 0 } else { puts "$::SP(message)" } if { ![info exists ::SP(vframe)] \ || ($::SP(vframe,ct) >= $::SP(vframe,vct)) } { set ::SP(vframe,ct) 0 incr ::SP(vframe,column) set ::SP(vframe) [frame .vf-$::SP(vframe,column)] pack $::SP(vframe) -side left -fill both -expand 1 } incr ::SP(vframe,ct) set vf $::SP(vframe) set f [frame ${vf}.f$id -borderwidth 3 -relief ridge] pack [label $f.hdr -bg lightgray -fg blue \ -borderwidth 0 -relief raised \ -text "$::SP($id,itemname)"] \ -fill x -expand 1 switch $::SP($id,itemtype) { bit {add_bit_item_to_gui $f $id} s32 - u32 {add_number_item_to_gui $f $id 1} float {add_number_item_to_gui $f $id 0} default {return -code error \ "add_item_to_gui: unexpected itemtype <$::SP($id,itemtype)>" } } return 1 } ;# add_item_to_gui proc add_bit_item_to_gui {f id} { switch -nocase $::SP($id,mode) { pulse - hold - toggle {} default { if {"$::SP($id,mode)" != "default"} { puts "$::SP($id,itemname): unknown ,\ using /mode=$::SP(bit,mode,default)" } set ::SP($id,mode) $::SP(bit,mode,default) } } set value [get_item $id] set color lightgray if $value {set color magenta} pack [label $f.b \ -text "$::SP($::SP($id,mode),text)" \ -borderwidth 4 -relief raised ] \ -fill x -expand 1 set ::SP($id,button) $f.b bind $::SP($id,button) [list b_release $id] bind $::SP($id,button) [list b_press $id] set ::SP($id,ivalue) "$value" pack [label $f.l -bg $color -fg black \ -text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"] \ -fill x -expand 1 set ::SP($id,label) $f.l if {!$::SP($id,onemode)} { pack [radiobutton $f.p -text OnePulse \ -anchor w \ -value "pulse" \ -command [list bit_mode $id] \ -variable ::SP($id,mode)] \ -fill x -expand 0 pack [radiobutton $f.t -text ToggleValue \ -anchor w \ -value "toggle" \ -command [list bit_mode $id] \ -variable ::SP($id,mode)] \ -fill x -expand 0 pack [radiobutton $f.h -text "1 WhilePressed" \ -anchor w \ -value "hold" \ -command [list bit_mode $id] \ -variable ::SP($id,mode)] \ -fill x -expand 0 } pack $f -side top -fill x -expand 0 } ;# add_bit_item_to_gui proc add_number_item_to_gui {f id enable_plusminus} { set value [get_item $id] set color lightgray pack [frame $f.one] -fill x -expand 1 pack [button $f.one.b -bg $color -fg black \ -text "Set " \ -relief raised -bd 3 \ -command [list b_press $id] ]\ -side left -fill x -expand 1 if $enable_plusminus { pack [button $f.one.m -bg $color -fg black \ -text "-" \ -relief raised -bd 3 \ -command [list minus_number_item $id] ]\ -side left -fill x -expand 1 pack [button $f.one.p -bg $color -fg black \ -text "+" \ -relief raised -bd 3 \ -command [list plus_number_item $id] ]\ -side left -fill x -expand 1 } pack [button $f.one.r -bg $color -fg black \ -text "Reset" \ -relief raised -bd 3 \ -command [list reset_number_item $id] ]\ -side left -fill x -expand 1 set e [entry $f.e \ -justify right \ -textvariable ::SP($id,entry)] pack $e -fill x -expand 0 bind $e [list b_press $id] set ::SP($id,ivalue) "$value" pack [label $f.l -bg $color -fg black \ -anchor w \ -text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value"] \ -fill x -expand 1 set ::SP($id,label) $f.l if {$::SP($id,itemtype) == "u32"} { pack [label $f.hexl -bg $color -fg black \ -anchor w \ -text "$::SP(iprefix)[format %#X $::SP($id,ivalue)] \ $::SP(prefix)[format %#X $value]"] \ -fill x -expand 1 set ::SP($id,hexlabel) $f.hexl } pack $f -side top -fill x -expand 0 } ;# add_number_item_to_gui proc exact_name {name line} { set idx [string first $name $line] if {$idx < 0} {return 0} if {0 == [string compare $name [string range $line $idx end]]} { return 1 } return 0 } ;# exact_name proc connected_to {name line} { # check if an input pin is already connected to a signal # since it does not necessarily have a writer set idx [string first $name $line] if {$idx < 0} {return ""} # check if pin is an input if {-1 != [string first "$name <==" [string range $line $idx end]]} { set idx [string first "<==" $line] set signame [string range $line [expr 4 + $idx] end] return "$signame" } return "" } ;# connected_to proc item_info {itemname id} { set fmt "sim_pin: %-30s %5s %3s %s" set theitem "-----" set dir "---" set found 0 # try pin: set answer [hal show pin "$itemname"] set lines [split $answer \n] set lines [lreplace $lines 0 1] ;# discard header lines # look for exact match (hal show will present all matching leading part) foreach line $lines { if {"$line" == ""} continue if [exact_name $itemname $line] { set found 1 set theitem "PIN" break } set signame [connected_to $itemname $line] if {"" != "$signame"} { puts "pin <$itemname> is already connected, trying signal:<$signame>" set itemname $signame set ::SP($id,itemname) $itemname } } if !$found { # try param: set answer [hal show param "$itemname"] set lines [split $answer \n] set lines [lreplace $lines 0 1] ;# discard header lines # look for exact match (hal show will present all matching leading part) foreach line $lines { if {"$line" == ""} continue if [exact_name $itemname $line] { set found 1 set theitem "PARAM" break } } } if !$found { # try signal: set answer [hal show signal "$itemname"] set lines [split $answer \n] set lines [lreplace $lines 0 1] ;# discard header lines # look for exact match (hal show will present all matching leading part) foreach line $lines { if {"$line" == ""} continue if [exact_name $itemname $line] { set found 1 scan $line "%s %s" sigtype other switch $sigtype { bit - u32 - s32 - float {set theitem SIG} default { set ::SP(message) \ "Unknown type for signal item <$id $::SP($id,itemname) $sigtype>" return 0 } } break } } } if !$found { set ::SP(message) "Unknown item: $::SP($id,itemname)" return 0 } switch $theitem { PIN - PARAM { scan $line "%d %s %s %s %s %s %s" owner type dir value name arrows signalname if { ("$dir" == "IN") || ("$dir" == "I/O") || "$dir" == "RW"} { if [info exists arrows] { set ::SP(message) [format $fmt \ $itemname $theitem $dir "not writable (connected to signal)"] return 0 } else { #puts "OK <$dir> $line" } } else { set ::SP(message) [format $fmt \ $itemname $theitem $dir "not writable"] return 0 } } SIG { set sig_header_ct 0 foreach line $lines { if { ([string first "<==" $line] < 0) \ && ([string first "==>" $line] < 0) \ } { incr sig_header_ct } if {[string first "<==" $line] >= 0} { set has_writer 1 } } if {$sig_header_ct > 4} { # wild cards not supported: set ::SP(message) "Unknown item: $::SP($id,itemname)" return 0 } if [info exists has_writer] { set ::SP(message) [format $fmt \ $itemname $theitem $dir "signal has writer"] return 0 } else { set theitem "SIG" set is_signal 1 } } } if [info exists is_signal] { set ::SP($id,itemtype) $sigtype set ::SP($id,set_cmd) sets set ::SP($id,get_cmd) gets } else { set ::SP($id,itemtype) [hal ptype $itemname] set ::SP($id,set_cmd) setp set ::SP($id,get_cmd) getp } set ::SP(message) [format $fmt $itemname $theitem $dir ""] return 1 ;# ok } ;# item_info proc bit_mode {id} { switch -nocase $::SP($id,mode) { pulse {$::SP($id,button) config -text Pulse} hold {$::SP($id,button) config -text "1 while pressed"} toggle - default {$::SP($id,button) config -text Toggle} } } ;# bit_mode proc item_set {id {new_value 0}} { if [catch { switch $::SP($id,itemtype) { bit {hal $::SP($id,set_cmd) $::SP($id,itemname) 1} s32 - u32 - float {hal $::SP($id,set_cmd) $::SP($id,itemname) $new_value} } } msg ] { popup $msg return } item_show $id } ;# item_set proc item_unset {id} { if [catch {hal $::SP($id,set_cmd) $::SP($id,itemname) 0} msg] { popup $msg } set value [get_item $id] set color lightgray if $value {set color magenta} $::SP($id,label) configure -bg $color -fg black \ -text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value" } ;# item_unset proc item_show {id} { set value [get_item $id] set color lightgray if {$value != $::SP($id,ivalue)} {set color magenta} switch $::SP($id,itemtype) { bit { $::SP($id,label) configure -bg $color \ -text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value" } s32 - \ u32 - \ float {$::SP($id,label) configure -bg $color -fg black \ -text "$::SP(iprefix)$::SP($id,ivalue) $::SP(prefix)$value" } } if {$::SP($id,itemtype) == "u32"} { $::SP($id,hexlabel) configure -bg $color -fg black \ -text "$::SP(iprefix)[format %#X $::SP($id,ivalue)] \ $::SP(prefix)[format %#X $value]" } } ;# item_show proc b_press {id} { set value [get_item $id] switch $::SP($id,itemtype) { bit {switch -nocase $::SP($id,mode) { "hold" {item_set $id} "toggle" { if $value { item_unset $id } else { item_set $id } } "pulse" {item_set $id; after $::SP(pulse,ms) [list b_release $id]} } } s32 - \ u32 - \ float { set e $::SP($id,entry) if ![isnumber $e] { if [catch {set e [expr $::SP($id,entry)]} msg] { popup "Invalid Expression (<$e>)" set ::SP($id,entry) "" return } else { switch $e { Inf - NaN { popup "Bad expr result: <$e>" set ::SP($id,entry) "" return } } } } # Note: halcmd rejects numbers formatted 'nEmm' for s32, u32 if { (($::SP($id,itemtype) == "s32") || ($::SP($id,itemtype) == "u32")) \ && ![isinteger $e]} { popup "Integer required for u32,s32 entry (not <$e>)" return } if { ($::SP($id,itemtype) == "u32") \ && [isnegative $e]} { popup "Nonnegative Integer required for u32 entry (not <$e>)" return } item_set $id $e } default {return -code error \ "b_press: unknown pin type <$::SP($id,itemtype)> for $::SP($id,itemname)" } } } ;# b_press proc b_release {id} { switch -nocase $::SP($id,mode) { "hold" {item_unset $id} "toggle" {} "pulse" {item_unset $id} } } ;# b_release proc reset_number_item {id} { item_set $id $::SP($id,ivalue) } ;# reset_number_item proc plus_number_item {id} { item_set $id [expr 1 + [get_item $id]] } ;# plus_number_item proc minus_number_item {id} { item_set $id [expr -1 + [get_item $id]] } ;# minus_number_item proc get_item {id} { set value [hal $::SP($id,get_cmd) $::SP($id,itemname)] switch $::SP($id,itemtype) { bit { switch $value { FALSE {return 0} TRUE {return 1} } } s32 - u32 - float {return $value} default {return -code error \ "get_item: unknown item type <$::SP($id,itemtype)> for $::SP($id,itemname)" } } } ;# get_item proc update_current_values {} { for {set id 0} {$id < $::SP(id)} {incr id} { item_show $id } after $::SP(update,ms) update_current_values } ;# update_current_values proc isinteger {v} { if ![isnumber $v] {return 0} if {[string first . $v] >=0} {return 0} if {[string first e [string tolower $v]] >= 0} {return 0} return 1 } ;# isinteger proc isnumber {v} { if [catch {format %f $v}] { return 0 } else { return 1 } } ;# isnumber proc isnegative {v} { # Note:check with isnumber before this if {[format %f $v] < 0} {return 1} return 0 } ;# isnegative proc popup msg { tk_messageBox \ -type ok \ -title "$::SP(progname): Problem" \ -message $msg } ;# popup if [catch { if {[info exists ::argv0] && [info script] == $::argv0} { set ::SP(progname) [file tail $::argv0] set ::SP(update,ms) 300 if {$::argv == ""} {usage} set ::SP(bit,mode,default) toggle # button text for bit item modes: set ::SP(pulse,text) "Pulse" set ::SP(hold,text) "1 while Pressed" set ::SP(toggle,text) "Toggle" set ::SP(id) 0 set ::SP(vframe,column) 0 set ::SP(vframe,vct) 4 ;# howmany items in a column set ::SP(iprefix) "Initial=" ;# initial value prefix set ::SP(prefix) "Current=" ;# current value prefix set ::SP(pulse,ms) 200 ;# pulse duration set ::SP(title) $::SP(progname) set currentarg [lindex $::argv 0] while {[string first "-" $currentarg] == 0} { switch -- $currentarg { --help {usage} --title {set ::SP(title) [lindex $::argv 1] set ::argv [lreplace $::argv 0 0] } } set ::argv [lreplace $::argv 0 0] set currentarg [lindex $::argv 0] } foreach itemname $::argv { if [add_item_to_gui $::SP(id) $itemname] { incr ::SP(id) } } wm title . $::SP(title) if {$::SP(id) < 1} usage update_current_values } } msg] { puts "\nError: $msg" usage }