# secop driver namespace eval secop {} { } proc stdConfig::secop {{shownUnits ALL}} { variable node variable name controller secop3 timeout=60 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 active 0 prop shownUnits $shownUnits set node $node/tasks prop start secop::start pollperiod 0.01 0.01 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 [sct controller] poll [sct] 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 # } return "" } proc secop::writemsg {} { sct send [sct target] return secop::update_ } proc secop::readmsg_ {} { # ending with _: invisible on debug if {[sct connection_lost]} { sct connection_lost 0 return [secop::start] } sct send "" return secop::update_ } proc secop::test {} { clientput test return idle } proc secop::check {} { if {[silent "" sct secopar] eq ""} return 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 {} { error "secop::get is obsolete" sct send "" return secop::update_ } proc secop::check_range {{low None} {high None}} { 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_bool {} { switch -- [string tolower [sct target]] { off - false - no - 0 - on - true - yes - 1 { return } } error "illegal value for boolean: [sct target]" } 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 set text_path [regsub {status_code$} $path status_text] make_par0 text $text_path $secopar $desc hsetprop $text_path 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 } lassign [split $path /] nul obj par set fmtunit "" if {[lsearch [list enum int double] $secoptype0] >= 0} { set fmtunit "" if {[info exists props(unit)]} { set fmtunit [format { [%s]} $props(unit)] } if {$par eq "" || $par eq "target"} { set unit [silent 1 set props(unit)] if {[sct shownUnits] eq "ALL" || [lsearch [sct shownUnits] $unit] >= 0} { GraphAdd $path $unit } } } if {[info exists props(description)]} { hsetprop $path help "$props(description)$fmtunit" unset props(description) } switch -- $secoptype { enum { set enumprop [list] set wid 8 set sorted [list] foreach {name value} [lindex $validator_args 0] { lappend sorted [list $value $name] } foreach value_name [lsort -integer -index 0 $sorted] { lassign $value_name value name lappend enumprop "$name=$value" set wid [expr max($wid,[string length $name])] } hsetprop $path enum [join $enumprop ,] if {[silent "" hgetpropval $path type] eq "drivable"} { hsetprop $path visible false } if {$wid > 8} { hsetprop $path width $wid } } bool { hsetprop $path enum 1 hsetprop $path validator secop::check_bool } 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 checkstatus secop::checkstatus 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 s_group $obj hsetprop $path objectPath $path hsetprop /sics/[sct controller] p_$secopar:value $path } else { if {$par eq "status"} { set path /$obj/status_code } # clientput "PAR $path $type [array get props]" 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) } 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]] set cmd [join [lassign [split $path /] _ obj] /] lassign [lindex [silent "" set props(datatype)] 1] secoptype validator if {$secoptype eq "None"} { $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] } 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} { clientput "MAKE_MODULE $obj" if {[obj_list exists $obj]} { clientput "$obj exists already" return } obj_list makeitem $obj /$obj array unset modprop set parlist [list] set pardict [dict create] foreach {key item} $desc { switch $key { accessibles { foreach acsitm $item { lassign $acsitm parname pardesc dict set pardict $parname $pardesc } } default { set modprop($key) $item } } } if {[dict exists $pardict value]} { set value [dict get $pardict value] dict unset pardict 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} $pardict { 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" } set shortcmds [list] foreach {parname pardesc} $pardict { set datatype [dict get $pardesc datatype] if {[lindex $datatype 0] eq "command"} { if {[lindex $datatype 1] ne "None"} { # only commands with arguments make_cmd $obj:$parname $pardesc 1 } else { lappend shortcmds $parname $pardesc } } else { make_par $obj:$parname $pardesc } } # then commands without arguments, on one line set first 1 foreach {parname pardesc} $shortcmds { 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 moditem $modlist { lassign $moditem modname moddesc 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 status_code } if {[string match {_*} $parameter]} { set parameter [string range $parameter 1 end] } if {$pargroup ne ""} { return "/$module/$pargroup/$parameter" } return "/$module/$parameter" } proc secop::update_ {} { if {[silent "" sct result] eq ""} { return idle } set sent_message [silent "" 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 { # clientput "*** [DoubleTime]: [sct result]" #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] lassign $value value if {$value != 0} { hsetprop $objpath group [hgetpropval $objpath s_group] set shown 1 } else { catch {hdelprop $objpath group} set shown 0 } if {$value < 100 || $value >= 400} { # error updateerror $objpath $text_value catch { logsetup $objpath/target clear } } else { # logsetup $objpath 1 updateval $objpath [hvali $objpath] } GraphItem shown $objpath $shown if {[silent "" hgetpropval $objpath/target logger_name] ne ""} { GraphItem shown $objpath/target $shown } set text_path [regsub {status_code$} $path status_text] if {[catch {updateval $text_path $text_value}]} { clientput "cannot update $text_path to $text_value" clientput "MSG([sct result])" } } 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]} { if {[string match 1* [silent 0 hval /$obj/status_code]]} { 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 errortext lassign $origin requesttype requestpar requestval set path [silent "" hgetpropval /sics/[sct controller] p_$requestpar] if {$requesttype eq "change" && $path ne ""} { hsetprop $path changed 0 } if {$origin eq $sent_message} { set message_to_client [sct result] } else { clientput "ERROR: $path $errortext" } } describing { do_as_manager { make_node $val } [sct controller] poll [sct] 0.01 sct send activate return secop::update_ } default { if {[string match "*,*" $messagetype]} { clientput IDN=[sct result] sct send describe sct active 0 return secop::update_ } 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 set ws [silent 0 sct writestatus] if {$ws ne "checked" && $ws ne "start" || [sct target] != [silent "x" hgetpropval [sct]/target target]} { hset [sct]/target [sct target] sct writestatus checked } } proc secop::checkstatus {} { set ws [silent 0 sct writestatus] set status [hvali [sct]/status_code] if {[string match 3* $status]} { set result run } elseif {[string match 4* $status]} { set result posfault } else { if {$ws ne "done"} { set result run } else { set result idle } } sct status $result return $result } proc secop::complete_run {} { sct print "run [sct objectName] to [sct target]" return idle } proc secop::halt {} { [sct objectName] stop sct writestatus done # clientput HALT:[sct] return idle } proc secop::start {} { sct send *IDN? return secop::update_ } proc secop::describe {} { sct send describe return secop::describing } proc secop::describing {} { #obsolete? 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