Files
sea/tcl/drivers/secop_0.tcl
2022-08-22 14:59:00 +02:00

676 lines
18 KiB
Tcl

# secop driver
namespace eval secop {} {
}
proc stdConfig::secop {{shownUnits ALL}} {
variable node
variable name
set timeout 5
controller secop timeout=$timeout
prop commerror secop::errorscript
prop connection_lost 0
prop check secop::check
prop write secop::write
prop startcmd *IDN?
prop end_fast 0
prop secopPath /$name
prop timeout $timeout
prop active 0
prop shownUnits $shownUnits
set node $node/tasks
prop start secop::start
# the pollperiod does not really matter
pollperiod 5 5
obj SECoP -text wr
prop read secop::readmsg
prop test secop::test
prop check secop::checkmsg
prop write secop::writemsg
prop cmd ""
variable ctrl
variable path
hsetprop /sics/$ctrl ignore_no_response 1
}
proc secop::errorscript {} {
if {[string match {ASCERR: no response*} [sct result]]} {
sct send ping
return secop::update
}
sct connection_lost 1
error [sct result]
}
proc secop::checkmsg {} {
# variable MQ[sct]
# upvar 0 MQ[sct] mq
# if {![info exists mq]} {
# # create message queue
# set mq [list]
# }
# if {[llength $mq] > 0} {
# set next [lindex $mq 0]
# set mq [lrange $mq 1 end]
# lappend mq [sct target]
# sct target $next
# }
# interrupt pending wait for update
[sct controller] timeout 0.001
# without the following line "0.001" appears on client, why?
return ""
}
proc secop::writemsg {} {
# we grabbed access, so restore normal timeout
[sct controller] timeout [sct timeout]
sct send [sct target]
return secop::update
}
proc secop::readmsg {} {
if {[sct connection_lost]} {
sct connection_lost 0
return [secop::start]
}
return [secop::get]
}
proc secop::test {} {
clientput test
return idle
}
proc secop::check {} {
set validator [silent {} sct validator]
eval $validator
lassign [split [hinfo [sct]] ","] type
if {$type eq "text"} {
set msg "change [sct secopar] \"[sct target]\""
} else {
set msg "change [sct secopar] [sct target]"
}
[sct controller] que [sct secopPath] write [list secop::queuedwrite $msg]
}
proc secop::queuedwrite {msg} {
sct changed [DoubleTime]
# send message on /secop node
sct send $msg
return secop::update
}
proc secop::write {} {
# dummy write
# clientput "secop::write [sct] [hvali [sct]]"
return idle
}
proc secop::get {} {
sct send ""
return secop::update
}
proc secop::check_range {{low None} {high None}} {
[sct controller] timeout 0.001
if {$low ne "None" && [sct target] < $low} {
error "value [sct target] must be >= $low"
}
if {$high ne "None" && [sct target] > $high} {
error "value [sct target] must be <= $high"
}
}
proc secop::check_enum {} {
[sct controller] timeout 0.001
}
proc secop::check_length {{low None} {high None}} {
if {$low ne "None" && [string length [sct target]] < $low} {
error "value [sct target] must not be shorter than $low"
}
if {$high ne "None" && [string length [sct target]] > $high} {
error "value [sct target] must not be longer than $high"
}
}
proc secop::make_par {secopar desc {kind ""}} {
set path [topath $secopar [silent "" dict get $desc group]]
array set props $desc
set validator_args [lassign [silent "" set props(datatype)] secoptype0]
if {$secoptype0 eq "tuple" && [string match *:status $secopar]} {
lassign $validator_args elements
make_par0 text ${path}_text $secopar $desc
hsetprop ${path}_text width 24
set validator_args [lassign [lindex $elements 0] secoptype]
set status_node 1
} else {
set secoptype $secoptype0
set status_node 0
}
switch -- $secoptype {
double {set type float}
int - enum {set type int}
string {
set type text
# can not use SICS drivable for string
set kind ""
}
bool {set type text}
none {set type none}
default {
clientput "unknown type for $secopar (use text): $secoptype ($props(datatype))"
set type text
}
}
make_par0 $type $path $secopar $desc $kind
hsetprop $path secoptype $secoptype0
if {$status_node} {
hsetprop $path nonewline 1
}
switch -- $secoptype {
enum {
set enumprop [list]
set wid 8
foreach {name value} [lindex $validator_args 0] {
lappend enumprop "$name=$value"
set wid [expr max($wid,[string length $name])]
}
hsetprop $path enum [join $enumprop ,]
if {$wid > 8} {
hsetprop $path width $wid
}
}
bool {
hsetprop $path enum 1
hsetprop $path validator secop::check_enum
}
double - int {
hsetprop $path validator [concat secop::check_range $validator_args]
}
string {
hsetprop $path width 16
hsetprop $path validator [concat secop::check_range $validator_args]
}
}
return $path
}
proc secop::make_par0 {type path secopar desc {kind std}} {
array set props $desc
# clientput "$path $desc"
set readonly [silent 0 set props(readonly)]
if {$readonly} {
set priv internal
} else {
set priv user
}
if {[silent "" hinfo $path] ne ""} {
error "$path exists already"
}
lassign [split $path /] nul obj par
if {$par eq ""} {
if {$kind eq "driv"} {
dynsctdriveobj $obj float user SECoP [sct controller]
hfactory $path link $obj
hsetprop $obj checklimits secop::checklimits
hsetprop $obj halt secop::halt
# allow start without run:
hsetprop $obj check secop::checklimits
hsetprop $obj write secop::complete_run
set readonly 0
hsetprop $obj sicscommand "run $obj"
} else {
# clientput "OBJ $obj $type"
dynsicsobj $obj SECoP $priv $type
hfactory $path link $obj
}
hsetprop $path group $obj
hsetprop $path objectPath $path
hsetprop /sics/[sct controller] p_$secopar:value $path
} else {
if {$par eq "status"} {
set path /$obj/s_status
}
# clientput "PAR $path $type"
hfactory $path plain $priv $type
if {[info exists props(visibility)]} {
if {$props(visibility) >= 3} {
hsetprop $path visible false
}
}
}
hsetprop $path secopar $secopar
hsetprop /sics/[sct controller] p_$secopar $path
if {!$readonly} {
[sct controller] write $path
} else {
[sct controller] connect $path
}
logsetup $path 1
if {[info exists props(value)]} {
clientput "VALUE in descr $path"
if {[catch {hupdate /$path $props(value)} msg]} {
clientput $msg
}
unset props(value)
}
set fmtunit ""
if {[info exists props(unit)]} {
set fmtunit [format { [%s]} $props(unit)]
if {$par eq "" || $par eq "target"} {
if {[sct shownUnits] eq "ALL" || [lsearch [sct shownUnits] $props(unit)] >= 0} {
GraphAdd $path $props(unit) [join [lrange [split $path /] 1 end] .]
}
}
}
if {[info exists props(description)]} {
hsetprop $path help "$props(description)$fmtunit"
unset props(description)
}
foreach {prop item} [array get props] {
hsetprop $path s_$prop $item
}
}
proc secop::make_cmd {secopar desc {first 0}} {
array set props $desc
set path [topath $secopar [silent "" dict get $desc group]]
lassign [split $secopar :] obj cmd
lassign [silent "" set props(arguments)] secoptype validator
if {$secoptype eq ""} {
$obj makescriptfunc $cmd "secop::check_cmd [sct secopPath] $secopar" user
hsetprop $path newline $first
hsetprop $path secopar $secopar
hsetprop $path sicscommand "$obj $cmd"
if {[info exists props(visibility)]} {
if {$props(visibility) >= 3} {
hsetprop $path visible false
}
}
} else {
if {[llength $secoptype] == 1} {
set secoptype [lindex $secoptype 0]
}
clientput "MAKE_CMD $secoptype"
dict set desc datatype $secoptype
make_par $secopar $desc
lassign $secoptype maintype
if {$maintype eq "double" || $maintype eq "int" || $maintype eq "bool"} {
hsetprop $path check "secop::check_cmd_num [sct secopPath] $secopar"
} else {
hsetprop $path check "secop::check_cmd_text [sct secopPath] $secopar"
}
}
}
proc secop::check_cmd {secopPath secopar} {
hset $secopPath "do $secopar"
}
proc secop::check_cmd_num {secopPath secopar} {
hset $secopPath [format {do %s [%.15g]} $secopar [sct target]]
sct update [sct target]
}
proc secop::check_cmd_text {secopPath secopar} {
hset $secopPath [format {do %s ["%s"]} $secopar [sct target]]
sct update [sct target]
}
proc secop::make_module {obj desc} {
if {[obj_list exists $obj]} {
clientput "$obj exists already"
return
}
obj_list makeitem $obj /$obj
array unset modprop
set parlist [list]
set cmdlist [list]
set pardict [dict create]
foreach {key item} $desc {
switch $key {
parameters {
set parlist $item
}
commands {
set cmdlist $item
}
default {
set modprop($key) $item
}
}
}
if {[dict exists $parlist value]} {
set value [dict get $parlist value]
dict unset parlist value
} else {
set value [dict create datatype none]
}
set classes [silent "" set modprop(interface_class)]
if {[string match "* Drivable *" " $classes "]} {
set path [make_par $obj $value driv]
} else {
set path [make_par $obj $value]
}
if {[info exists modprop(visibility)] && $modprop(visibility) >= 3} {
hdelprop $path group
}
foreach {prop val} [array get modprop] {
hsetprop $obj sm_$prop $val
}
device_layout makeitem /$obj [silent 0 set modprop(layoutpos)]
set groups [dict create]
foreach {parname pardesc} [concat $parlist $cmdlist] {
if {[dict exists $pardesc group]} {
dict set groups [dict get $pardesc group] 1
}
}
foreach g [dict keys $groups] {
clientput "GROUP $g"
hfactory $obj/$g plain user none
hsetprop $obj/$g group "group $g"
}
foreach {parname pardesc} $parlist {
make_par $obj:$parname $pardesc
}
# first commands with arguments
foreach {parname pardesc} $cmdlist {
if {[dict get $pardesc arguments] ne ""} {
make_cmd $obj:$parname $pardesc 1
}
}
# then commands without arguments, on one line
set first 1
foreach {parname pardesc} $cmdlist {
if {[dict get $pardesc arguments] eq ""} {
make_cmd $obj:$parname $pardesc $first
set first 0
}
}
}
proc secop::make_node {desc} {
array unset nodeprop
set modlist [list]
foreach {key item} $desc {
switch $key {
modules {
set modlist $item
}
default {
set nodeprop($key) $item
}
}
}
foreach {modname moddesc} $modlist {
make_module $modname $moddesc
}
foreach {prop val} [array get nodeprop] {
sct sn_$prop $val
}
sort_layout
}
proc secop::topath {secopar {pargroup ""}} {
lassign [split [string tolower $secopar] :] module parameter
if {$parameter eq "value" || $parameter eq ""} {
return "/$module"
}
if {$parameter eq "status"} {
set parameter s_status
}
if {[string match {_*} $parameter]} {
set parameter [string range $parameter 1 end]
}
if {$pargroup ne ""} {
return "/$module/$pargroup/$parameter"
}
return "/$module/$parameter"
}
proc secop::update {} {
set sent_message [silent 0 sct sent_message]
set message_to_client ""
lassign [sct result] messagetype par val
set path [silent "" hgetpropval /sics/[sct controller] p_$par]
lassign [split $par :] obj
switch $messagetype {
update - changed {
#if {[sct] ne "/secop"} {
# clientput "[sct] is not /secop, why?"
#}
#if {![sct active]} {
# clientput [sct result]
#}
if {$messagetype eq "changed"} {
if {[string match *:target $par]} {
hsetprop /$obj writestatus done
}
hsetprop $path changed 0
if {[lrange $sent_message 0 1] eq [list change $par]} {
set message_to_client [sct result]
}
# clientput "CH $path [sct result]"
} else {
if {[DoubleTime] < [silent 0 hgetpropval $path changed] + 10} {
# ignore updates of variables during change
# clientput "ignore [sct result]"
return idle
}
if {[lrange $sent_message 0 1] eq [list read $par]} {
set message_to_client [sct result]
}
}
lassign $val value qual
if {[silent 0 hgetpropval $path secoptype] eq "tuple" &&
[string match *:status $par]} {
if {[llength $value] > 2} {
set text_value [lrange $value 1 end]
} else {
set text_value [lindex $value 1]
}
set objpath [sct parent $path]
set visible_old [silent true hgetpropval $objpath visible]
if {$text_value ne "disabled"} {
set visible_new true
set shown 1
} else {
set visible_new false
set shown 0
}
if {$visible_new ne $visible_old} {
hsetprop $objpath visible $visible_new
GraphItem shown $objpath $shown
}
lassign $value value
if {[catch {updateval ${path}_text $text_value}]} {
clientput "cannot update ${path}_text to $text_value"
clientput "MSG([sct result])"
}
if {[string match *:status $par]} {
set oldstatus [silent idle hgetpropval /$obj status]
if {[silent 0 hgetpropval /$obj writestatus] eq "start"} {
set status run
} elseif {[string match 3* $value]} {
set status run
} elseif {[string match 4* $value]} {
set status posfault
} else {
set status idle
if {[silent "" hgetpropval $obj type] eq "drivable" && $oldstatus eq "run"} {
if {[catch {
set oldvalue [silent 0 hgetpropval $obj value_before_run]
set oldtarget [silent 0 hgetpropval $obj target_before_run]
set value [hval /$obj]
set target [silent 0 hgetpropval /$obj target]
set delay [expr [DoubleTime] - [silent 0 hgetpropval $obj write_time]]
if {abs($value - $oldvalue) < abs($target - $oldtarget) * 0.1 && $delay < 10} {
# clientput "WARNING: $obj status is idle, but value has not moved: abs($value - $oldvalue) < abs($target - $oldtarget) * 0.1 delay $delay"
set status run
}
} msg]} {
clientput "WARNING: $msg"
}
}
}
hsetprop /$obj status $status
}
}
if {[catch {updateval $path $value} msg]} {
if {$value eq "None"} {
hsetprop $path geterror None
} else {
clientput "cannot update $path to $value"
clientput $msg
}
} elseif {[string match *:target $par]} {
# clientput [sct result]/[silent "" hgetpropval /$obj status]
if {[silent "" hgetpropval /$obj status] eq "idle"} {
hsetprop /$obj target $value
}
}
catch {
hsetprop $path timestamp [dict get $qual t]
}
}
pong {
if {[lindex $sent_message 0] eq "ping"} {
set message_to_client [sct result]
}
}
done {
if {[lrange $sent_message 0 2] eq [list do $par]} {
set message_to_client [sct result]
} else {
clientput "done $par $val"
}
}
active {
if {[lindex $sent_message 0] eq "activate"} {
set message_to_client [sct result]
} else {
clientput ACTIVE
}
sct active 1
sct end_fast 0
}
error {
lassign $val origin info
lassign $origin requesttype requestpar requestval
set path [silent "" hgetpropval /sics/[sct controller] p_$requestpar]
if {$requesttype eq "change" && $path ne ""} {
hsetprop $path changed 0
}
set errortext [lindex [dict get $info errorinfo] 0]
if {$origin eq $sent_message} {
set message_to_client [sct result]
} else {
clientput "ERROR: $path $errortext"
}
}
default {
if {$sent_message ne ""} {
set message_to_client [sct result]
} else {
# show untreated message
clientput [sct result]
}
}
}
if {$message_to_client ne ""} {
clientput "> $sent_message\n< $message_to_client"
sct sent_message ""
sct sent_time 1e20
} elseif {[DoubleTime] > [silent 1e20 sct sent_time] + 10} {
clientput "timeout waiting for response to $sent_message"
sct sent_message ""
sct sent_time 1e20
}
if {[DoubleTime] < [sct end_fast]} {
return secop::get
}
[sct controller] queue [sct] read secop::get
return idle
}
proc secop::checklimits {} {
# for whatever strange reason checklimits is called twice
# in addition again as write script of the obj node
# do this only once
if {[silent 0 sct status] ne "run" ||
[sct target] != [silent "x" hgetpropval [sct]/target target]} {
sct value_before_run [hval [sct]]
sct target_before_run [hval [sct]/target]
hset [sct]/target [sct target]
sct status run
}
}
proc secop::complete_run {} {
sct print "run [sct objectName] to [sct target]"
return idle
}
proc secop::halt {} {
[sct objectName] stop
sct writestatus done
sct target_before_run [silent 0 sct target]
# sct status posfault
# clientput HALT:[sct]
return idle
}
proc secop::start {} {
[sct controller] timeout [sct timeout]
sct send *IDN?
return secop::getidn
}
proc secop::getidn {} {
clientput [sct result]
[sct controller] queue [sct secopPath] start secop::describe
sct active 0
return idle
}
proc secop::describe {} {
sct send describe
return secop::describing
}
proc secop::describing {} {
lassign [sct result] messagetype par val
switch $messagetype {
describing {
do_as_manager {
make_node $val
}
}
default {
clientput "ignore $messagetype $par ..."
}
}
sct send activate
sct end_fast [expr [DoubleTime] + 5]
return secop::update
}
proc secop_send {args} {
hset /secop $args
hsetprop /secop sent_message $args
hsetprop /secop sent_time [DoubleTime]
}
publishLazy secop_send