387 lines
11 KiB
Tcl
387 lines
11 KiB
Tcl
#
|
|
# Template driver for the Knauer BlueShadow Pump 40P
|
|
# vim: ft=tcl ts=8 sts=2 sw=2 expandtab autoindent smartindent nocindent
|
|
#
|
|
driver knauer_pump = {
|
|
debug_threshold = 0;
|
|
vendor = knauer; device = pump40p; protocol = knauer_ap;
|
|
class = environment;
|
|
simulation_group = environment_simulation;
|
|
#
|
|
group dummy = {
|
|
type = text; readable = 1; data = false; control = false; nxsave = false;
|
|
var status = { read_command = 'STATUS?'; read_function = read_status; property real_data = ' '; }
|
|
var glp = { read_command = 'GLP?'; read_function = read_glp; property real_data = ' '; }
|
|
}
|
|
|
|
group pump = {
|
|
var remote = {
|
|
type = int;
|
|
readable = 1; read_command = 'REMOTE?'; read_function = remote_read;
|
|
writeable = 1; write_function = remote_write;
|
|
allowed = '0,1';
|
|
}
|
|
var state = {
|
|
type = text;
|
|
readable = 1;
|
|
read_command = ' ';
|
|
fetch_function = state_fetch;
|
|
}
|
|
var status = {
|
|
type = text;
|
|
readable = 1;
|
|
read_command = ' ';
|
|
fetch_function = status_fetch;
|
|
}
|
|
group volume = {
|
|
var pval = {
|
|
type = float;
|
|
readable = 1;
|
|
read_command = ' ';
|
|
fetch_function = volume_fetch;
|
|
read_function = volume_read;
|
|
property 'units' = 'mL';
|
|
}
|
|
var setp = {
|
|
type = float;
|
|
writeable = 1;
|
|
write_command = ' ';
|
|
write_function = volume_write;
|
|
check_function = volume_check;
|
|
driveable = pump/volume/pval;
|
|
checkstatus_function = volume_checkstatus;
|
|
halt_function = volume_halt;
|
|
lowerlimit = 0; upperlimit = 100; tolerance = 0.01;
|
|
readable = 1;
|
|
read_command = ' ';
|
|
fetch_function = volume_fsm;
|
|
read_function = volume_store;
|
|
property 'units' = 'mL';
|
|
property this_state = 0;
|
|
}
|
|
}
|
|
group ratio = {
|
|
var pval = {
|
|
type = text;
|
|
readable = 1; read_command = ' '; fetch_function = ratio_fetch;
|
|
property 'units' = 'percent';
|
|
}
|
|
var setp = {
|
|
type = text;
|
|
value = '25/25/25/25';
|
|
writeable = 1; write_command = ' '; write_function = ratio_write; checkrange_function = ratio_check;
|
|
property 'units' = 'percent';
|
|
}
|
|
}
|
|
group flow = {
|
|
var pval = {
|
|
type = float;
|
|
readable = 1; read_command = ' '; fetch_function = flow_fetch;
|
|
property 'units' = 'mL/min';
|
|
}
|
|
var setp = {
|
|
type = float;
|
|
value = 1.0;
|
|
writeable = 1; write_command = ' '; write_function = flow_write;
|
|
lowerlimit = 0; upperlimit = 9.999;
|
|
property 'units' = 'mL/min';
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Ensure the pump starts up in REMOTE mode
|
|
#
|
|
code mkDriver = {%%
|
|
#hset ${scobj_hpath}/pump/remote 1
|
|
%%}
|
|
#
|
|
# These functions handle the real_data returned by the pump for the GLP? command
|
|
#
|
|
code read_glp = {%%
|
|
if { [string equal -nocase -length 6 ${data} "ERROR:"] } {
|
|
} else {
|
|
set dlist [split [lindex [split ${data} ":"] 1] ","]
|
|
sct real_data "[join [lrange ${dlist} 0 end] ,]"
|
|
set data "Hidden in real_data property"
|
|
}
|
|
%%}
|
|
|
|
#
|
|
# These functions handle the real_data returned by the pump for the STATUS? command
|
|
#
|
|
code read_status = {%%
|
|
set dlist [split [lindex [split ${data} ":"] 1] ","]
|
|
sct real_data "[join [lrange ${dlist} 0 end] ,]"
|
|
set data "Hidden in real_data property"
|
|
%%}
|
|
|
|
#
|
|
# Decode the status from the real_data to PUMPING if it is pumping else IDLE
|
|
#
|
|
code status_fetch = {%%
|
|
set index 1
|
|
set data [hgetpropval ${tc_root}/dummy/status real_data]
|
|
set dlist [split ${data} ","]
|
|
set status_code [lindex ${dlist} ${index}]
|
|
set cmd "@@NOSEND@@"
|
|
if { ${status_code} == 3 } {
|
|
sct result "PUMPING"
|
|
} else {
|
|
sct result "IDLE"
|
|
}
|
|
%%}
|
|
|
|
#
|
|
# Decode the state from the real_data to one of the documented strings
|
|
#
|
|
code state_fetch = {%%
|
|
set index 1
|
|
set data [hgetpropval ${tc_root}/dummy/status real_data]
|
|
set dlist [split ${data} ","]
|
|
if { [llength ${dlist}] > ${index} } {
|
|
set state_code [lindex ${dlist} ${index}]
|
|
} else {
|
|
set state_code "0"
|
|
}
|
|
set cmd "@@NOSEND@@"
|
|
if { ${state_code} < 0 || ${state_code} > 9 } {
|
|
sct geterror "Invalid device_state ${state_code}"
|
|
error "[sct geterror]"
|
|
}
|
|
set slist [list "SYS_ST_INITIALIZING" \
|
|
"SYS_ST_OFF" \
|
|
"SYS_ST_IDLE" \
|
|
"SYS_ST_RUN" \
|
|
"SYS_ST_HOLD" \
|
|
"SYS_ST_PURGE" \
|
|
"SYS_ST_STANDBY" \
|
|
"SYS_ST_SEVEN" \
|
|
"SYS_ST_FAILED" \
|
|
"SYS_ST_RUNATEND" \
|
|
]
|
|
sct result [lindex ${slist} ${state_code}]
|
|
%%}
|
|
|
|
#
|
|
#
|
|
code flow_fetch = {%%
|
|
set index 4
|
|
set data [hgetpropval ${tc_root}/dummy/status real_data]
|
|
set dlist [split ${data} ","]
|
|
if { [llength ${dlist}] > ${index} } {
|
|
set flow_pv [lindex ${dlist} 4]
|
|
} else {
|
|
set flow_pv 0.0
|
|
}
|
|
sct result [expr {0.001 * ${flow_pv}}]
|
|
set cmd "@@NOSEND@@"
|
|
%%}
|
|
code flow_write = {%%
|
|
set data [sct target]
|
|
set cmd "@@NOSEND@@"
|
|
set nextState idle
|
|
if { ${data} != [sct oldval] } {
|
|
debug_log ${tc_root} 1 "[sct] changed to new:${data}, from old:[sct oldval]"
|
|
sct oldval ${data}
|
|
sct update ${data}
|
|
sct utime readtime
|
|
}
|
|
%%}
|
|
|
|
#
|
|
#
|
|
code ratio_check = {%%
|
|
set rlist [split ${setpoint} /]
|
|
if { [llength ${rlist}] != 4 } {
|
|
sct geterror "${setpoint} has [llength ${rlist}] components, needs 4"
|
|
error [sct geterror]
|
|
}
|
|
set sum 0
|
|
for {set i 0} {$i < 4} {incr i} {
|
|
set cmp [lindex ${rlist} ${i}]
|
|
if { ![string is integer -strict ${cmp}] } {
|
|
sct geterror "component [expr {${i} + 1}] is not integer: \"${cmp}\""
|
|
error [sct geterror]
|
|
}
|
|
if { !(${cmp} >= 0 && ${cmp} <= 100) } {
|
|
sct geterror "component [expr {${i} + 1}] is not between 0 and 100: \"${cmp}\""
|
|
error [sct geterror]
|
|
}
|
|
set sum [expr {${sum} + ${cmp}}]
|
|
}
|
|
if { ${sum} != 100 } {
|
|
sct geterror "sum of components is ${sum}, must be 100"
|
|
error [sct geterror]
|
|
}
|
|
%%}
|
|
|
|
code ratio_fetch = {%%
|
|
set data [hgetpropval ${tc_root}/dummy/status real_data]
|
|
set dlist [split ${data} ","]
|
|
set ratio_vals "[lindex ${dlist} 5]/[lindex ${dlist} 6]/[lindex ${dlist} 7]/[lindex ${dlist} 8]"
|
|
sct result ${ratio_vals}
|
|
set cmd "@@NOSEND@@"
|
|
%%}
|
|
code ratio_write = {%%
|
|
set data [sct target]
|
|
set cmd "@@NOSEND@@"
|
|
set nextState idle
|
|
if { ${data} != [sct oldval] } {
|
|
debug_log ${tc_root} 1 "[sct] changed to new:${data}, from old:[sct oldval]"
|
|
sct oldval ${data}
|
|
sct update ${data}
|
|
sct utime readtime
|
|
}
|
|
%%}
|
|
|
|
#
|
|
#
|
|
code remote_read = {%%
|
|
if { [string equal -length 7 ${data} "REMOTE:"] } {
|
|
set data [lindex [split ${data} :] 1]
|
|
} else {
|
|
sct geterror "bad response"
|
|
error "[sct geterror]"
|
|
}
|
|
%%}
|
|
|
|
code remote_write = {%%
|
|
if { ${par} == 0 } {
|
|
set cmd "LOCAL"
|
|
} else {
|
|
set cmd "REMOTE"
|
|
}
|
|
%%}
|
|
|
|
#
|
|
#
|
|
code volume_fetch = {%%
|
|
set data [hgetpropval ${tc_root}/dummy/glp real_data]
|
|
set dlist [split ${data} ","]
|
|
if { [llength ${dlist}] > 11 } {
|
|
set pump_volm [lindex ${dlist} 10]
|
|
set pump_voln [lindex ${dlist} 11]
|
|
set pump_volume [expr {${pump_volm} + 0.000001 * ${pump_voln}}]
|
|
} else {
|
|
set pump_volume 0.0
|
|
}
|
|
sct raw_volume ${pump_volume}
|
|
if { [hpropexists [sct] base_volume] } {
|
|
set pump_volume [expr {${pump_volume} - [sct base_volume]}]
|
|
} elseif { [hpropexists [sct] raw_volume] } {
|
|
sct base_volume [sct raw_volume]
|
|
}
|
|
sct result [format "%.2f" ${pump_volume}]
|
|
set cmd "@@NOSEND@@"
|
|
%%}
|
|
|
|
code volume_write = {%%
|
|
hset ${tc_root}/[sct driveable] 0.0
|
|
set cmd "REMOTE"
|
|
sct this_state 1
|
|
sct pumping 1
|
|
set data ${par}
|
|
if { ${data} != [sct oldval] } {
|
|
debug_log ${tc_root} 1 "[sct] changed to new:${data}, from old:[sct oldval]"
|
|
sct oldval ${data}
|
|
sct update ${data}
|
|
sct utime readtime
|
|
}
|
|
%%}
|
|
|
|
code volume_fsm = {%%
|
|
if { [sct this_state] > 0 } {
|
|
set flow_tgt [expr {int(1000.0 * [hval ${tc_root}/pump/flow/setp])}]
|
|
set ratio_tgt [join [split [hval ${tc_root}/pump/ratio/setp] /] ,]
|
|
set time_tgt [expr {int(60000.0 * 1000.0 * [sct target] / ${flow_tgt})}]
|
|
set time_1 [expr {${time_tgt} - 500}]
|
|
set time_2 [expr {${time_tgt} + 500}]
|
|
set saveState ${nextState}
|
|
set nextState "noResponse"
|
|
if { [sct this_state] == 1 } {
|
|
set cmd "GLP?"
|
|
set nextState ${saveState}
|
|
sct this_state [expr {[sct this_state] + 1}]
|
|
} elseif { [sct this_state] == 2 } {
|
|
set cmd "TT_LOAD:1"
|
|
sct this_state [expr {[sct this_state] + 1}]
|
|
} elseif { [sct this_state] == 3 } {
|
|
set cmd "TT_SET:0,0,${flow_tgt},${ratio_tgt},0,0,0,0,0,0,0,0"
|
|
sct this_state [expr {[sct this_state] + 1}]
|
|
} elseif { [sct this_state] == 4 } {
|
|
set cmd "TT_SET:1,${time_1},${flow_tgt},${ratio_tgt},0,0,0,0,0,0,0,0"
|
|
sct this_state [expr {[sct this_state] + 1}]
|
|
} elseif { [sct this_state] == 5 } {
|
|
set cmd "TT_SET:2,${time_2},0,${ratio_tgt},0,0,0,0,0,0,0,0"
|
|
sct this_state [expr {[sct this_state] + 1}]
|
|
} elseif { [sct this_state] == 6 } {
|
|
set cmd "START:1,0"
|
|
sct this_state 0
|
|
} elseif { [sct this_state] == 91 } {
|
|
set cmd "STOP:1,0"
|
|
sct this_state 92
|
|
} elseif { [sct this_state] == 92 } {
|
|
set cmd "STOP:0,0"
|
|
sct this_state 93
|
|
} elseif { [sct this_state] == 93 } {
|
|
if { !([hpropexists ${tc_root}/pump/remote target] && [hgetpropval ${tc_root}/pump/remote target] == 1) } {
|
|
set cmd "LOCAL"
|
|
} else {
|
|
set cmd "@@NOSEND@@"
|
|
}
|
|
sct this_state 0
|
|
} else {
|
|
sct this_state 0
|
|
set cmd "@@NOSEND@@"
|
|
set nextState idle
|
|
}
|
|
} else {
|
|
set cmd "@@NOSEND@@"
|
|
set nextState idle
|
|
if { [hpropexists [sct] pumping] && [sct pumping] } {
|
|
set new_value [hval ${tc_root}/pump/status]
|
|
set old_value [sct oldval]
|
|
if {${new_value} != ${old_value}} {
|
|
sct oldval ${new_value}
|
|
if {${old_value} == "PUMPING" && ${new_value} == "IDLE"} {
|
|
set cmd "STOP:0,0"
|
|
sct this_state 91
|
|
set nextState noResponse
|
|
sct result ""
|
|
sct driving 0
|
|
sct pumping 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
%%}
|
|
|
|
code volume_store = {%%
|
|
if { [sct this_state] == 2 } {
|
|
set ns [namespace current]
|
|
# store the GLP result
|
|
hsetprop ${tc_root}/dummy/glp result "${data}"
|
|
sct with ${tc_root}/dummy/glp "${ns}::read_glp ${tc_root}"
|
|
# extract the volume
|
|
sct with ${tc_root}/[sct driveable] "${ns}::volume_fetch ${tc_root} ${nextState} @@NOSEND@@"
|
|
sct with ${tc_root}/[sct driveable] "${ns}::volume_read ${tc_root}"
|
|
# copy it to base_volume
|
|
hsetprop ${tc_root}/[sct driveable] base_volume [hgetpropval ${tc_root}/[sct driveable] raw_volume]
|
|
}
|
|
if { [hpropexists [sct] target] } {
|
|
set data [sct target]
|
|
} else {
|
|
set data 0.0
|
|
}
|
|
%%}
|
|
|
|
code volume_halt = {%%
|
|
set cmd "STOP:0,0"
|
|
sct this_state 91
|
|
debug_log ${tc_root} 1 "volume_halt sct send ${cmd}"
|
|
sct send ${cmd}
|
|
%%}
|
|
|
|
}
|