339 lines
12 KiB
Tcl
339 lines
12 KiB
Tcl
##
|
|
# @file sct_protek_common.tcl
|
|
# @brief Gets display state from Protek 608 multimeters.
|
|
#
|
|
# Author: Ferdi Franceschini (ffr@ansto.gov.au)
|
|
|
|
## MULTIMETER STATE FIELDS AND VALUES
|
|
###### MODES
|
|
# AUTOOFF:1/0 PULSE:1/0 MAX:1/0 PLUSPEAK:1/0 REL:1/0 RECALL:1/0 GO/NG:1/0 MINUSPER:1/0
|
|
# RS232C:1/0 PLUS:1/0 MINUS:1/0 MIN:1/0 MINUSPEAK:1/0 AVG:1/0 STORE:1/0 REF:1/0 PLUSPER:1/0
|
|
# LOBAT:1/0
|
|
|
|
###### MAIN DISPLAY
|
|
# AC1:1/0 SIGN1:1/0 DC1:1/0 PW1:1/0
|
|
# MD5:np MD4:np MD3:np MD2:np MD1:n
|
|
# M1:1/0 k1:1/0 OHM1:1/0
|
|
# u1:1/0 Hz1:1/0
|
|
# m1:1/0 V1:1/0 A1:1/0
|
|
# n1:1/0 S1:1/0 F1:1/0
|
|
# DEGC1:1/0 s1:1/0
|
|
# DEGF1:1/0
|
|
|
|
###### BAR GRAPH
|
|
# B16K:1/0 B8K:1/0 B4K:1/0 B2K:1/0 B1K:1/0
|
|
# B512:1/0 B256:1/0 B128:1/0 B64:1/0 B32:1/0
|
|
# B16:1/0 B8:1/0 B4:1/0 B2:1/0 B1:1/0 B0:1/0
|
|
|
|
###### SUB DISPLAY
|
|
# RANGE2:1/0 HOLD2:1/0 DUTY2:1/0
|
|
# CONT2:1/0 ZD2:1/0
|
|
# AC2:1/0 SIGN2:1/0 DC2:1/0
|
|
# SD5:np SD4:np SD3:np SD2:np SD1:n
|
|
# PERCENT2:1/0 dBm2:1/0
|
|
# m2:1/0 V2:1/0 A2:1/0 DEGK2:1/0
|
|
# G2:1/0 M2:1/0 k2:1/0 OHM2:1/0 Hz2:1/0
|
|
|
|
# ROTSWITCH:[0-8]
|
|
|
|
### The return values for nine bytes is undocumented,
|
|
### they are labelled as X1 to X9
|
|
# X[1-9]:?
|
|
|
|
##
|
|
# @brief Sends a state report request to the protek script context protocol handler.
|
|
proc rqStateRep {} {
|
|
sct send "STATE"
|
|
return rdStateRep
|
|
}
|
|
|
|
##
|
|
# @brief Processes state report from the protek script context protocol handler.
|
|
proc rdStateRep {} {
|
|
set stateRep [sct result]
|
|
if {[string match "ASCERR:*" $stateRep]} {
|
|
sct geterror $stateRep
|
|
# Setting oldval forces update and clears geterror on next call
|
|
sct oldval "UNKNOWN"
|
|
return idle
|
|
}
|
|
|
|
array set stateArr [split $stateRep "|:"]
|
|
if {$stateArr(AUTOOFF)} {
|
|
broadcast "PROTEK608:[sct IP]:[sct PORT] WARNING AUTO OFF IS ENABLED"
|
|
}
|
|
if {$stateArr(LOBAT)} {
|
|
broadcast "PROTEK608: LOW BATTERY WARNING"
|
|
}
|
|
if {$stateRep != [sct oldval]} {
|
|
sct update $stateRep
|
|
sct oldval $stateRep
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc rqVal {nextState} {
|
|
return $nextState
|
|
}
|
|
proc ProtekMainDisplay {protek nextState} {
|
|
if [catch {
|
|
set stateRep [hval $protek/state]
|
|
set MDpath [sct]
|
|
set parname [file tail $MDpath]
|
|
set scale [hval $MDpath/scale]
|
|
set offset [hval $MDpath/offset]
|
|
array set SA [split $stateRep "|:"]
|
|
if {$SA(SIGN1)} {
|
|
set sign1 "-"
|
|
} else {
|
|
set sign1 ""
|
|
}
|
|
set MDISP "$sign1$SA(MD5)$SA(MD4)$SA(MD3)$SA(MD2)$SA(MD1)"
|
|
if {[string is double $MDISP] == 0} {
|
|
error "Non-numeric reading ($MDISP) from Protek main display"
|
|
}
|
|
set MDISP [expr {$scale * $MDISP + $offset}]
|
|
set oldval [sct oldval]
|
|
if {$MDISP != $oldval} {
|
|
sct update $MDISP
|
|
sct oldval $MDISP
|
|
protek_debug_log $parname $MDISP
|
|
}
|
|
} msg ] {
|
|
return -code error "[info level 0]: $msg"
|
|
}
|
|
return $nextState
|
|
}
|
|
|
|
proc protek_debug_log {name args} {
|
|
set fd [open "/usr/local/sics/data/protek_$name.csv" a]
|
|
puts $fd "[clock format [clock seconds] -format "%d/%m/%Y, %T"], $args"
|
|
close $fd
|
|
}
|
|
##
|
|
# @brief Makes a state monitor object for the protek multimeter
|
|
#
|
|
# @param name, name of object which reports main display reading
|
|
# @param IP, IP address of protek moxa box
|
|
# @param PORT, port number for protek on moxa box
|
|
# @param scale, scales reading to physical units
|
|
# @param offset, offset for main display reading
|
|
# @param interval, polling interval in seconds (optional, default = 0.5seconds)
|
|
# @param cbFunc, this function will be called after the voltage reading has been updated
|
|
# NOTE: If the interval is negative then the multimeter will be polled on every cycle of
|
|
# the SICS task loop.
|
|
proc MakeProtek {name IP PORT {scale 1.0} {offset 0.0} {interval 0.5} {cbFunc "return idle"}} {
|
|
set catch_status [ catch {
|
|
set sctName "sct_$name"
|
|
set sobjName "$name"
|
|
set soState "so_state_$name"
|
|
clientput "MakeSICSObj $sobjName SCT_OBJECT"
|
|
MakeSICSObj $soState SCT_OBJECT
|
|
MakeSICSObj $sobjName SCT_OBJECT user float
|
|
sicslist setatt $sobjName long_name $sobjName
|
|
|
|
hfactory /sics/$soState/state plain user text
|
|
hsetprop /sics/$soState/state read rqStateRep
|
|
hsetprop /sics/$soState/state rdStateRep rdStateRep
|
|
hsetprop /sics/$soState/state oldval "UNKNOWN"
|
|
hsetprop /sics/$sobjName read rqVal "reportVal"
|
|
hsetprop /sics/$sobjName reportVal ProtekMainDisplay /sics/$soState callBack
|
|
hsetprop /sics/$sobjName callBack $cbFunc
|
|
hfactory /sics/$sobjName/scale plain user float
|
|
hset /sics/$sobjName/scale $scale
|
|
hfactory /sics/$sobjName/offset plain user float
|
|
hset /sics/$sobjName/offset $offset
|
|
hsetprop /sics/$sobjName oldval "UNKNOWN"
|
|
|
|
sicslist setatt $sobjName klass sample
|
|
::scobj::hinitprops $sobjName
|
|
sicslist setatt $sobjName long_name $name
|
|
if {[SplitReply [environment_simulation]] == false} {
|
|
makesctcontroller $sctName protek608 $IP:$PORT
|
|
hsetprop /sics/$soState/state IP $IP
|
|
hsetprop /sics/$soState/state PORT $PORT
|
|
$sctName poll /sics/$soState/state $interval
|
|
$sctName poll /sics/$sobjName $interval
|
|
}
|
|
} catch_message ] {
|
|
handle_exception ${catch_status} ${catch_message}
|
|
}
|
|
return /sics/$sobjName
|
|
}
|
|
|
|
|
|
proc add_protekmm {name IP PORT CTYPE CID {scale 1.0} {offset 0.0} {interval 0.5} {cbFunc "return idle"}} {
|
|
set catch_status [ catch {
|
|
MakeProtek $name $IP $PORT $scale $offset $interval $cbFunc
|
|
hsetprop /sics/$name permlink data_set ${CTYPE}${CID}S1
|
|
} catch_message ] {
|
|
handle_exception ${catch_status} ${catch_message}
|
|
}
|
|
}
|
|
|
|
namespace eval ::scobj::add_protekmm {
|
|
set debug_threshold 5
|
|
}
|
|
proc ::scobj::add_protekmm::sics_log {debug_level debug_string} {
|
|
set catch_status [ catch {
|
|
set debug_threshold ${::scobj::add_protekmm::debug_threshold}
|
|
if {${debug_level} >= ${debug_threshold}} {
|
|
sicslog "::scobj::add_protekmm::${debug_string}"
|
|
}
|
|
} catch_message ]
|
|
}
|
|
|
|
clientput "file evaluation of sct_protekmm.tcl"
|
|
::scobj::add_protekmm::sics_log 9 "file evaluation of sct_protekmm.tcl"
|
|
|
|
proc ::scobj::add_protekmm::read_config {} {
|
|
set catch_status [ catch {
|
|
set ns "::scobj::add_protekmm"
|
|
dict for {k v} $::config_dict {
|
|
if { [dict exists $v "implementation"] } {
|
|
if { !([dict exists $v "name"] && [dict exists $v "enabled"]) } {
|
|
continue
|
|
}
|
|
set name [dict get $v name]
|
|
set enabled [string tolower [dict get $v "enabled"]]
|
|
set implementation [dict get $v "implementation"]
|
|
if { !([dict exists $::config_dict $implementation]) } {
|
|
continue
|
|
}
|
|
set v [dict get $::config_dict $implementation]
|
|
if { !([dict exists $v "driver"]) } {
|
|
continue
|
|
}
|
|
if { [string equal -nocase [dict get $v "driver"] "protekmm"] } {
|
|
if { [string equal -nocase $enabled "true" ] || [string equal -nocase $enabled "always"] } {
|
|
if { ![string equal -nocase [SplitReply [environment_simulation]] "false"] } {
|
|
set asyncqueue "null"
|
|
${ns}::sics_log 9 "[environment_simulation] => using null asyncqueue"
|
|
} elseif { [dict exists $v "asyncqueue"] } {
|
|
set asyncqueue [dict get $v "asyncqueue"]
|
|
} else {
|
|
if { [dict exists $v "asyncprotocol"] } {
|
|
set asyncprotocol [dict get $v "asyncprotocol"]
|
|
} else {
|
|
set asyncprotocol ${name}_protocol
|
|
MakeAsyncProtocol ${asyncprotocol}
|
|
if { [dict exists $v "terminator"] } {
|
|
${asyncprotocol} sendterminator "[dict get $v "terminator"]"
|
|
${asyncprotocol} replyterminator "[dict get $v "terminator"]"
|
|
}
|
|
}
|
|
set asyncqueue ${name}_queue
|
|
set IP [dict get $v ip]
|
|
set PORT [dict get $v port]
|
|
MakeAsyncQueue ${asyncqueue} ${asyncprotocol} ${IP} ${PORT}
|
|
if { [dict exists $v "timeout"] } {
|
|
${asyncqueue} timeout "[dict get $v "timeout"]"
|
|
}
|
|
}
|
|
set arg_list [list]
|
|
foreach arg {name ip port datype id scal offset interval cbfunc} {
|
|
if {[dict exists $v $arg]} {
|
|
lappend arg_list "[dict get $v $arg]"
|
|
} else {
|
|
${ns}::sics_log 9 "Missing configuration value $arg"
|
|
error "Missing configuration value $arg"
|
|
}
|
|
}
|
|
add_protekmm ${name} "aqadapter" ${asyncqueue} {*}$arg_list
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch_message ]
|
|
handle_exception ${catch_status} ${catch_message}
|
|
}
|
|
|
|
if { [info exists ::config_dict] } {
|
|
::scobj::add_protekmm::read_config
|
|
} else {
|
|
::scobj::add_protekmm::sics_log 5 "No config dict"
|
|
}
|
|
# Check config_dict for configuration
|
|
namespace eval ::scobj::add_protekmm {
|
|
set debug_threshold 5
|
|
}
|
|
proc ::scobj::add_protekmm::sics_log {debug_level debug_string} {
|
|
set catch_status [ catch {
|
|
set debug_threshold ${::scobj::add_protekmm::debug_threshold}
|
|
if {${debug_level} >= ${debug_threshold}} {
|
|
sicslog "::scobj::add_protekmm::${debug_string}"
|
|
}
|
|
} catch_message ]
|
|
}
|
|
|
|
clientput "file evaluation of sct_protekmm.tcl"
|
|
::scobj::add_protekmm::sics_log 9 "file evaluation of sct_protekmm.tcl"
|
|
|
|
proc ::scobj::add_protekmm::read_config {} {
|
|
set catch_status [ catch {
|
|
set ns "::scobj::add_protekmm"
|
|
dict for {k v} $::config_dict {
|
|
if { [dict exists $v "implementation"] } {
|
|
if { !([dict exists $v "name"] && [dict exists $v "enabled"]) } {
|
|
continue
|
|
}
|
|
set name [dict get $v name]
|
|
set enabled [string tolower [dict get $v "enabled"]]
|
|
set implementation [dict get $v "implementation"]
|
|
if { !([dict exists $::config_dict $implementation]) } {
|
|
continue
|
|
}
|
|
set v [dict get $::config_dict $implementation]
|
|
if { !([dict exists $v "driver"]) } {
|
|
continue
|
|
}
|
|
if { [string equal -nocase [dict get $v "driver"] "add_protekmm"] } {
|
|
if { [string equal -nocase $enabled "true" ] || [string equal -nocase $enabled "always"] } {
|
|
if { ![string equal -nocase [SplitReply [environment_simulation]] "false"] } {
|
|
set asyncqueue "null"
|
|
${ns}::sics_log 9 "[environment_simulation] => using null asyncqueue"
|
|
} elseif { [dict exists $v "asyncqueue"] } {
|
|
set asyncqueue [dict get $v "asyncqueue"]
|
|
} else {
|
|
if { [dict exists $v "asyncprotocol"] } {
|
|
set asyncprotocol [dict get $v "asyncprotocol"]
|
|
} else {
|
|
set asyncprotocol ${name}_protocol
|
|
MakeAsyncProtocol ${asyncprotocol}
|
|
if { [dict exists $v "terminator"] } {
|
|
${asyncprotocol} sendterminator "[dict get $v "terminator"]"
|
|
${asyncprotocol} replyterminator "[dict get $v "terminator"]"
|
|
}
|
|
}
|
|
set asyncqueue ${name}_queue
|
|
set IP [dict get $v ip]
|
|
set PORT [dict get $v port]
|
|
MakeAsyncQueue ${asyncqueue} ${asyncprotocol} ${IP} ${PORT}
|
|
if { [dict exists $v "timeout"] } {
|
|
${asyncqueue} timeout "[dict get $v "timeout"]"
|
|
}
|
|
}
|
|
set arg_list [list]
|
|
foreach arg {name ip port datype id scale offset interval cbfunc} {
|
|
if {[dict exists $v $arg]} {
|
|
lappend arg_list "[dict get $v $arg]"
|
|
} else {
|
|
${ns}::sics_log 9 "Missing configuration value $arg"
|
|
error "Missing configuration value $arg"
|
|
}
|
|
}
|
|
add_protekmm ${name} "aqadapter" ${asyncqueue} {*}$arg_list
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch_message ]
|
|
handle_exception ${catch_status} ${catch_message}
|
|
}
|
|
|
|
if { [info exists ::config_dict] } {
|
|
::scobj::add_protekmm::read_config
|
|
} else {
|
|
::scobj::add_protekmm::sics_log 5 "No config dict"
|
|
}
|