Files
sea/tcl/stdsct.tcl
2024-08-12 13:15:04 +02:00

888 lines
19 KiB
Tcl

# config commands
namespace eval stdConfig {
}
namespace eval stdSct {
}
proc stdConfig::make args {
scanargs $args arg -driver -name -ctrl 0 -base / -port 0 -permanent 0 -args
variable driver $arg(driver)
variable name $arg(name)
variable base $arg(base)
variable hostport $arg(port)
variable makeargs $arg(args)
variable permanent $arg(permanent)
# permanent=0: object to be registered in obj_list
# therefore removed with samenv none
# permanent=1: object created in init file
# no need to register make command in status file
# permanent=2: object created on demand, persistent, removed only explicitly
if {[string equal 0 $arg(ctrl)]} {
variable ctrl _$name
} else {
variable ctrl $arg(ctrl)
}
if {[llength [info procs ${driver}]] == 0} {
# this is not needed when make is called from makenv, but on startup, when
# executing a creationCommand
source_sct_driver $driver
}
set cmd [linsert $makeargs 0 $driver]
# clientput "MCMD: $cmd"
if {[catch {set des [eval $cmd]} msg]} {
clientput "ERROR: calling stdConfig::$cmd:"
clientput $msg
return
}
if {$permanent == 0} {
desc_env makeitem $name $des
obj_list makeitem $name $des
obj_dependencies makeitem $name $ctrl
}
put_secop_info $base$name
}
proc stdConfig::controllerDesc {desc} {
variable ctrl
variable permanent
if {$permanent == 0} {
obj_list makeitem $ctrl $desc
desc_env makeitem $ctrl $desc
}
}
proc stdConfig::controller {{prot std} args} {
variable driver
variable ctrl
variable node
variable resolution 5
variable fastperiod 5
variable slowperiod 10
variable hostport
variable permanent
if {[lindex $args 0] eq "without_connection" || $prot eq "syncedprot"} {
set hostport without_connection
}
set node /sics/$ctrl
if {$hostport eq "0"} {
set hostport [silent 0 result cfg_env $ctrl]
if {$hostport eq "without_connection" || $hostport eq "0"} {
set hostport unconnected:0
}
}
if {[string index $hostport 0] eq "@"} {
if {[sicsdescriptor alias_hostport] eq "notfound"} {
makeobject alias_hostport array
}
alias_hostport makeitem $ctrl $hostport
set hostport [silent 0 result cfg_env $hostport]
if {[string equal 0 $hostport]} {
set hostport unconnected:0
}
} else {
catch {alias_hostport deleteitem $ctrl}
}
if {"$hostport" eq "undefined"} {
set hostport $ctrl
}
set makecmd [concat makesctcontroller $ctrl $prot $hostport $args]
# clientput "MSCT: $makecmd"
if {[string equal SctController [sicsdescriptor $ctrl]]} {
set _makecmd [hgetpropval $node _makecmd]
hsetprop $node _makecmd $makecmd
set makecmd [hgetpropval $node _makecmd]
if {! [string equal $makecmd $_makecmd]} {
hsetprop $node _makecmd $_makecmd
error "shared controllers do not match\n(a) $_makecmd\n(b) $makecmd"
}
return 0
}
eval $makecmd
prop _makecmd $makecmd
prop idn ""
prop tasksPath $node/tasks
prop state idle
prop read stdSct::read
prop update stdSct::update
prop write stdSct::write
prop complete stdSct::completeUpdate
prop commerror stdSct::errorScript
set reconnect_script "$ctrl queue /sics/$ctrl/tasks start stdSct::prestart"
prop reconnect_script $reconnect_script
logsetup /sics/$ctrl 1
set node /sics/$ctrl/tasks
hfactory $node plain user none
prop sync stdSct::sync
prop start stdSct::start
prop complete stdSct::completeStart
eval $reconnect_script
if {$permanent == 0} {
obj_list makeitem $ctrl "$driver controller"
desc_env makeitem $ctrl "$driver controller"
}
set node /sics/$ctrl
return 1
}
proc stdSct::prestart {} {
catch {
set hp [[sct controller] hostport]
set ip [[sct controller] ip]
hupdate /sics/[sct controller] "$hp ($ip)"
}
return start
}
proc stdConfig::pollperiod {fast {slow 0} {res 0}} {
if {$fast > 0} {
variable fastperiod $fast
}
if {$slow > 0} {
variable slowperiod $slow
}
if {$res > 0} {
variable resolution $res
} elseif {$fast > 0} {
variable resolution $fast
}
}
proc stdConfig::wr {{period 0} {prio slow} {action read}} {
variable slowperiod
variable node
variable ctrl
if {$period == 0} {
set period $slowperiod
}
$ctrl write $node
poll $period $prio $action
prop geterror "not read yet"
prop __save update
}
proc stdConfig::rd {{period 0} {prio read} {action read}} {
variable fastperiod
if {$period == 0} {
set period $fastperiod
}
poll $period $prio $action
}
proc stdConfig::upd {} {
variable ctrl
variable node
$ctrl connect $node
}
proc stdConfig::out {} {
variable ctrl
variable node
$ctrl write $node
prop __save update
}
proc stdConfig::par {value} {
variable node
hupdate $node $value
prop __save update
}
proc stdConfig::treatProps {kind props} {
variable level
foreach line [split $props "\n"] {
append prop $line
if {[info complete $prop]} {
prop [uplevel #$level subst [lindex $prop 0]] \
[uplevel #$level subst [lrange $prop 1 end]
} else {
append prop "\n"
}
}
foreach prop $props {
switch -- $prop
}
switch -- $kind {
wr {}
rd {}
upd {}
out {}
par {}
}
}
proc stdConfig::treatArgs {arguments} {
variable priv undef
variable type float
variable nolog
variable secop_module
variable cfgcmd [list]
if {[llength $arguments] == 3} {
# check for new syntax (not yet used?)
switch -- [lindex $arguments 0] {
none - int - float - text - drive -
intar - floatar - intvarar - floatvarar -
object - func {
set type [lindex $arguments 0]
treatProps [lindex $arguments 1] [lindex $arguments 2]
}
}
}
set opt cfg
set defpriv internal
set nolog 0
set secop_module ""
foreach a $arguments {
switch -glob -- $a {
-internal - -spy - -user - -mugger {
set priv [string range $a 1 end]
}
-none - -int - -float - -text -
-intar - -floatar - -intvarar - -floatvarar -
-object - -func - -drive {
set type [string range $a 1 end]
}
-nolog {
set nolog 1
}
-secop=* {
# clientput "SECOP $a"
set secop_module [string range $a 7 end]
# clientput "SECOP $secop_module"
}
default {
switch -- $a {
wr - out - par {
set defpriv user
}
}
lappend cfgcmd $a
}
}
}
if {[string equal undef $priv]} {
set priv $defpriv
}
}
proc stdConfig::obj {class args} {
variable name
variable base
variable hostport
variable makeargs
variable resolution
variable ctrl
variable driver
variable cfgcmd
variable priv
variable type
variable permanent
treatArgs $args
variable objpath $base$name
variable path $objpath
variable node $path
if {! [info exists ctrl]} {
error "ctrl must be called before obj"
}
# Layout hdb
# set path /$name
if {[string equal drive $type]} {
hfactory $path plain $priv float
dynsctdriveobj $name $path $class $ctrl
hsetprop $path sicscommand "run $name"
} else {
dynsicsobj $name $class $priv $type
hfactory $path link $name
}
eval $cfgcmd
set node $path
prop objectPath $objpath
if {$permanent != 1} {
prop creationCmd "stdConfig::make $driver $name $ctrl $base -port $hostport $makeargs"
}
set node $path/send
hfactory $node plain user text
$ctrl write $node
prop write stdSct::startSend
prop complete stdSct::completeSend
prop visible false
set node $path/status
hfactory $node plain internal text
prop visible false
hupdate $node ""
logsetup $path/status 1
if {[string equal drive $type]} {
set node $path/is_running
hfactory $node plain user int
prop visible false
prop check stdSct::setRunning
prop write stdSct::complete
$ctrl write $node
hupdate $node 0
logsetup $path/is_running 1
hsetprop $path updatestatus stdSct::updatestatus_
}
set node $path
switch $type {
none - func - object {
}
default {
hsetprop $node secop_module $name
logsetup $node $resolution
}
}
if {$permanent == 0} {
obj_list makeitem $name $class
desc_env makeitem $name $class
}
return 1
}
proc stdConfig::node {name args} {
variable objpath
variable path
variable ctrl
variable resolution
variable cfgcmd
variable priv
variable type
variable nolog
variable secop_module
treatArgs $args
variable node $path/$name
if {[lindex $cfgcmd 0] eq "alias"} {
if {[llength $cfgcmd] != 2 || $args ne $cfgcmd} {
error "Usage: node <node> alias <originalpath>"
}
hfactory $node alias [lindex $cfgcmd 1]
} else {
hfactory $node plain $priv $type
if {!$nolog} {
switch $type {
float - int {
hsetprop $node secop_module $secop_module
logsetup $node $resolution
}
text {
hsetprop $node secop_module $secop_module
logsetup $node $resolution
hupdate $node ""
}
}
}
eval $cfgcmd
}
prop objectPath $objpath
}
proc stdConfig::poll {args} {
variable ctrl
variable node
eval [concat $ctrl poll $node $args]
}
proc stdConfig::default {value} {
variable node
if {[silent "" hgetpropval $node enum] ne ""} {
enum_update $node $value
} else {
hupdate $node $value
}
hdelprop $node geterror
}
proc stdConfig::kids {{title 0} code} {
variable path
variable node
variable name
if {![string equal hidden $title]} {
if {[string equal 0 $title]} {
set title $name
}
prop group $title
prop newline 1
}
# if {[silent "" hgetpropval $node __save] eq ""} {
# prop __save kids
# }
set oldpath $path
set path $node
if {[catch {uplevel 1 $code} msg]} {
clientput $msg
}
set path $oldpath
}
proc stdConfig::prop {prop args} {
variable node
eval [concat hsetprop $node $prop $args]
if {$prop eq "enum"} {
eval [concat hsetprop $node enum_ $args]
}
}
#action scripts
proc stdSct::sync {} {
set cmd [silent 0 sct synccmd]
if {[string equal 0 $cmd]} {
return start
}
sct send $cmd
sct synccnt 10
return stdSct::syncComplete
}
proc stdSct::syncComplete {} {
set sr [silent "" sct syncreply]
if {$sr ne "" && $sr ne [sct result]} {
sct send "@@NOSEND@@"
}
return start
}
proc stdSct::start {} {
set cmd [silent 0 sct startcmd]
if {[string equal 0 $cmd]} {
return unpoll
}
sct send $cmd
return complete
}
proc stdSct::completeStart {} {
set idn [eval [silent "sct result" sct convert_idn]]
if {[string equal [sct idn] $idn]} {
clientput "[sct sicsdev] resynchronized after comm. error"
return unpoll
} elseif {[string length [sct idn]] == 0} {
sct idn $idn
clientput "connected [sct sicsdev]. idn: [sct idn]"
} else {
clientput "[sct sicsdev]: bad answer to startcmd: [sct result]"
sct idn ""
return sync
}
return unpoll
}
proc stdSct::startSend {} {
sct send [sct target]
return complete
}
proc stdSct::completeSend {} {
sct print "response = [sct result]"
sct update "[sct target]"
return idle
}
proc stdSct::errorScript {} {
set s [split [sct result] :]
if {[string match *ERR* [lindex $s 0]]} {
set s [lreplace $s 0 0]
}
set s [join $s :]
[sct controllerName] poll [sct tasksPath] 1 start sync
error "$s"
}
proc stdSct::read {} {
sct send [format [sct readcmd] [silent 0 sct addr]]
return update
}
proc stdSct::scanf {fmt args} {
set scan [linsert $args 0 scan [sct result] "${fmt}%n"]
lappend scan _cnt_
set res [uplevel 1 "eval {$scan}"]
if {$res != [llength $args] + 1} {
error "in command:[sct send]\nbad response: [sct result]\nafter arg $res\nfmt=$fmt"
}
}
proc stdSct::scanresult {} {
set readfmt [silent 0 sct readfmt]
if {$readfmt eq "0"} {
return [sct result]
}
stdSct::scanf [sct readfmt] result
return $result
}
proc stdSct::update {} {
sct update [stdSct::scanresult]
return idle
}
proc stdSct::write {} {
sct send [format [sct writecmd] [sct target] [silent 0 sct addr]]
return complete
}
proc stdSct::complete args {
return idle
}
proc stdSct::completeUpdate args {
catch {
sct update [sct requested]
}
return idle
}
proc stdSct::setRunning args {
if {[sct target]} {
error "can not set running - use run command instead"
}
[sct controller] queue [sct parent] halt halt
}
# wait for specified time and block controller before starting script
proc stdSct::after {delay script} {
sct _after_count 10
[sct controller] poll [sct] [expr $delay * 0.1] start "stdSct::afterpoll $script"
return idle
}
proc stdSct::afterpoll {script} {
set cnt [sct _after_count]
incr cnt -1
if {$cnt < 0} {
[sct controller] queue [sct] start $script
return unpoll
}
sct _after_count $cnt
return idle
}
proc stdSct::updatestatus_ {} {
set is_running [hval [sct]/is_running]
set run_status "[sct status] [sct writestatus]"
if {$run_status ne [silent 0 sct prev_run_status]} {
hupdate [sct]/is_running [expr {[sct writestatus] eq "start" || [sct status] eq "run"}]
sct prev_run_status $run_status
}
}
proc wait_node {path {tmo 30}} {
set start [clock seconds]
while {[string length [silent "" hgetpropval $path requested]] != 0} {
if {[clock seconds] > $start + $tmo} {
return 1
}
wait 0.2
}
return 0
}
# return the value of a node, or if a write operation is pending, its requested target
proc sctval {node {default_value 0}} {
set val [silent $default_value hvali $node]
return [silent $val hgetpropval $node requested]
}
proc silentval {defval node} {
set value [hvali $node]
catch {
hgetpropval $node geterror
set value $defval
}
return $value
}
# update error and make logger value invalid
proc updateerror {path error {overwrite 0}} {
set e [silent 0 hgetpropval $path geterror]
set val 0
catch {set val [hvali $path]}
if {$e eq "0" || $overwrite} {
hsetprop $path geterror $error
}
logsetup $path clear
hupdate $path $val
}
# update and clear error
proc updateval {path val} {
hdelprop $path geterror
hupdate $path $val
}
# update and clear error, clear log when val = undefvalue
proc updateval_u {path val undefvalue} {
if {$val != $undefvalue} {
hupdate $path $val
} else {
if {[hvali $path] != $undefvalue} {
hupdate $path $undefvalue
}
logsetup $path clear
}
hdelprop $path geterror
}
# update and set error when val = undefvalue
proc updateval_e {path val undefvalue error} {
if {$val != $undefvalue} {
hupdate $path $val
hdelprop $path geterror
} else {
if {[silent 0 hvali $path] != $undefvalue} {
hupdate $path $undefvalue
}
logsetup $path clear
hsetprop $path geterror $error
}
}
proc synceddo {code {timeout 10}} {
set id [synced begin]
catch {
uplevel 1 $code
}
synced end $id
synced wait $id $timeout
}
proc sctsync {code} {
set id [synced begin]
uplevel 1 $code
synced end $id
sct send $id
}
proc internalset {path value} {
hsetprop $path internalset 1
if {[catch {hset $path $value} msg]} {
hsetprop $path internalset 0
error $msg
}
hsetprop $path internalset 0
}
proc enum_decode {path {input ""} {number_var ""} {name_var ""}} {
if {$number_var ne ""} {
upvar $number_var num
}
if {$name_var ne ""} {
upvar $name_var nam
}
if {$input eq ""} {
set input [hvali $path]
}
set idx 0
set enum [split [hgetpropval $path enum] ,]
if {$enum eq "1"} {
set enum [list 0 1]
}
foreach e $enum {
lassign [split $e =] name number
if {$number eq ""} {
set number $idx
} else {
set idx $number
}
incr idx
set match -1
foreach el $input {
set tst [expr {$el eq $name || $el eq $number}]
if {$match < 0} {
set match $tst
} elseif {$match != $tst} {
error "ERROR: $path: $input ambiguous"
}
}
if {$match > 0} {
set num $number
set nam $name
return
}
}
if {$number_var ne ""} {
set num $input
}
if {$name_var ne ""} {
set nam $input
clientput "WARNING: enum_decode: ($path) name in '$input' not found"
}
}
proc named_enum {{textvar ""}} {
if {$textvar ne ""} {
enum_decode [sct] [sct target] num nam
upvar $textvar tv
set tv $nam
} else {
enum_decode [sct] [sct target] num
}
sct target $num
sct requested $num
return $num
}
proc enum_val {path} {
# for compatibility
return [hvali $path]
}
proc enum_txt {path} {
set v [hvali $path]
enum_decode $path $v num nam
return $nam
}
proc enum_txt_req {path} {
set v [hvali $path]
catch {
set v [sct requested]
}
enum_decode $path $v num nam
return $nam
}
proc enum_update {args} {
# one argument: value (on this node)
# two arguments: path value
if {[llength $args] > 2} {
error {ERROR: Usage: enum_update [path] value}
}
if {[llength $args] == 2} {
set path [lindex $args 0]
set input [lindex $args 1]
} else {
set path ""
set input $args
}
if {$path eq ""} {
set ownpath 1
set path [sct]
} else {
set ownpath 0
}
if {$input eq ""} {
set input [hvali $path]
}
enum_decode $path $input num
if {$ownpath} {
sct update $num
} else {
hupdate $path $num
}
return
}
proc get_values {path args} {
foreach name $args {
set varname [regsub "/" $name "_"]
upvar $varname $varname
set $varname [expr double([hvali $path/$name])]
}
}
proc update_values {path args} {
foreach name $args {
set varname [regsub "/" $name "_"]
upvar $varname $varname
updateval $path/$name [set $varname]
}
}
# when value on <path> is updated, its gets updated also on the callers node
# older links to callers node are removed
proc link2me {src} {
catch {
if {[silent 0 sct link2me] ne $src} {
[sct controller] updatescript $src "link2me_update [sct]"
sct link2me $src
sct update [hvali $src]
} elseif {[hvali [sct]] ne [hvali $src]} {
clientlog "link2me [sct]=[hvali [sct]] but $src=[hvali $src]"
}
}
}
proc link2me_update {dest value} {
if {[silent "" hgetpropval $dest link2me] ne [sct]} {
[sct controller] killupdatescript [sct] "link2me_update $dest"
clientlog "link removed: [sct] -> $dest"
} else {
updateval $dest $value
}
}
proc on_update_call {src script {valueproperty value}} {
if {[catch {
[sct controller] updatescript $src "on_update_queue [sct] $script $valueproperty"
if {[silent 1 sct on_update_call_cnt] == 0} {
sct on_update_call_cnt 99
}
} msg]} {
clientput $msg
}
}
proc on_update_queue {dest script valueproperty value} {
# when on_update_call_cnt is present on <dest> node:
# it is counted down on every call and the script is killed when at 0
# in order to keep the script alive, on_update_call_cnt should be set
# to a low value in the script itself
set cnt [silent none hgetpropval $dest on_update_call_cnt]
if {$cnt ne "none"} {
if {$cnt <= 0} {
[sct controller] killupdatescript $dest "on_update_queue $dest $script $valueproperty"
return
}
hsetprop $dest on_update_call_cnt [expr $cnt - 1]
}
hsetprop $dest $valueproperty $value
[sct controller] queue $dest write $script
}
proc node_cmd args {
switch [llength $args] {
0 {
error "Usage: node_cmd <node> [<name> [<value>]]"
}
1 {
lassign $args node
return [hval $node]
}
2 {
lassign $args node name
return [hval $node/$name]
}
default {
set val [lassign $args node name]
hset $node/$name $val
return $val
}
}
}