# 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