Scripts which generate a motor configuration file from CSV data files.
This commit is contained in:
200
site_ansto/instrument/util/genmotconf.tcl
Executable file
200
site_ansto/instrument/util/genmotconf.tcl
Executable file
@@ -0,0 +1,200 @@
|
|||||||
|
#!/usr/bin/env tclsh
|
||||||
|
# @file Generate a motor configuration file from CSV files of name value pairs.
|
||||||
|
#
|
||||||
|
# Input: List of CSV files.
|
||||||
|
# Output files:
|
||||||
|
# generated_motor_configuration.tcl
|
||||||
|
# genmotconf_report.log
|
||||||
|
# genmotconf_errors.log: Lists missing attributes if a spec is incomplete.
|
||||||
|
# missing_attlist.csv
|
||||||
|
#
|
||||||
|
# TODO
|
||||||
|
# Optionally split configuration accross multiple files for cases where
|
||||||
|
# axes are swapped out, eg Eulerian cradles, sample-stick rotation.
|
||||||
|
# This could be done by supplying a file which contains lines as follows,
|
||||||
|
# CFG_NAME1,m1 m2 m3
|
||||||
|
# CFG_NAME2,m4 m5 m6
|
||||||
|
# Where CFG_NAMEn is the name of a config file and mn's are motor names.
|
||||||
|
# In this case generated_motor_configuration.tcl will define the header and asyncqueues.
|
||||||
|
# The motor_configuration.tcl file would then be hand-coded to fileeval the CFG_NAMEn files as required.
|
||||||
|
#
|
||||||
|
source [file dirname $argv0]/genmotconf_procs.tcl
|
||||||
|
|
||||||
|
set ERRCNT 0
|
||||||
|
set MOTCFG_CNT 0
|
||||||
|
set FAILED_MOTCFG_CNT 0
|
||||||
|
# MOT_ATTLIST: Attributes required to configure an axis without an encoder
|
||||||
|
# ENC_ATTLIST: Attributes required to describe an encoder.
|
||||||
|
# NOTE Encoder readings for the limit switch positions are required.
|
||||||
|
# If the encoder "home" reading is not supplied it is set equal to rev_enc_lim
|
||||||
|
# ENCMOT_ATTLIST: Attributes which describe an axis which has both a motor and encoder.
|
||||||
|
# REQ_ATTLIST: List of attributes required to generate a configuration for a motor object.
|
||||||
|
# SICS_CFG_ATTLIST: Extra attributes required to configure a motor object in SICS.
|
||||||
|
# ALL_ATTRIBUTES: List of all attributes recognised by this program.
|
||||||
|
#
|
||||||
|
# Attributes which are not in these lists are assumed to define the encoder
|
||||||
|
# readings for each position on an axis which has a set of meaningful positions
|
||||||
|
# such as apertures or multi-sample tables.
|
||||||
|
set MOT_ATTLIST [lsort {axis mc steps_per_x}]
|
||||||
|
set ENC_ATTLIST [lsort {absenchome cnts_per_x fwd_enc_lim rev_enc_lim}]
|
||||||
|
set ENCMOT_ATTLIST [lsort [concat $MOT_ATTLIST ENCMOT_ATTLIST]]
|
||||||
|
set SICS_CFG_ATTLIST [lsort {home fwd_lim rev_lim maxspeed maxaccel maxdecel part units}]
|
||||||
|
set REQ_ATTLIST [lsort [concat $MOT_ATTLIST $SICS_CFG_ATTLIST]]
|
||||||
|
set ALL_ATTRIBUTES [lsort [concat $ENCMOT_ATTLIST $SICS_CFG_ATTLIST]]
|
||||||
|
|
||||||
|
set scriptname [file tail [file rootname $argv0]]
|
||||||
|
set file_list $argv
|
||||||
|
|
||||||
|
set fhr [open ${scriptname}_report.log "w"]
|
||||||
|
set fhe [open ${scriptname}_errors.log "w"]
|
||||||
|
|
||||||
|
# @brief Generate a default value for missing attributes.
|
||||||
|
# @param mot motor name
|
||||||
|
# @param att attribute name
|
||||||
|
# @return value for attribute or NOATT if no attribute should be generated.
|
||||||
|
proc gen_attval {mot att} {
|
||||||
|
switch $att {
|
||||||
|
"absenchome" {
|
||||||
|
if [info exists ::${mot}_encatts(rev_enc_lim)] {
|
||||||
|
return [set ::${mot}_encatts(rev_enc_lim)]
|
||||||
|
} else {
|
||||||
|
return "NOATT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"maxspeed" {return 1}
|
||||||
|
"maxaccel" {return 1}
|
||||||
|
"maxdecel" {return 1}
|
||||||
|
"rev_lim" {return 0}
|
||||||
|
"home" {return 0}
|
||||||
|
"fwd_lim" {
|
||||||
|
if { [info exists ::${mot}_encatts(fwd_enc_lim)] && [info exists ::${mot}_encatts(cnts_per_x)] } {
|
||||||
|
set fwd_enc_lim_val [set ::${mot}_encatts(fwd_enc_lim)]
|
||||||
|
set cnts_per_x_val [set ::${mot}_encatts(cnts_per_x)]
|
||||||
|
|
||||||
|
if [info exists ::${mot}_encatts(rev_enc_lim)] {
|
||||||
|
set rev_enc_lim_val [set ::${mot}_encatts(rev_enc_lim)]
|
||||||
|
} else {
|
||||||
|
set rev_enc_lim_val 0
|
||||||
|
}
|
||||||
|
if [info exists ::${mot}_encatts(home)] {
|
||||||
|
set home_val [set ::${mot}_encatts(home)]
|
||||||
|
} else {
|
||||||
|
set home_val 0
|
||||||
|
}
|
||||||
|
return [expr {($fwd_enc_lim_val - $rev_enc_lim_val) / $cnts_per_x_val + $home_val}]
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"part" {return "instrument"}
|
||||||
|
"units" {return "xxx"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Parse all files and generate ::mn(matt) and ::controllers(cn)
|
||||||
|
foreach f $file_list {
|
||||||
|
parse_file $f $fhr $fhe
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# GENERATE MOTOR CONFIGURATION FILE
|
||||||
|
puts $fhr "GENERATE MOTOR CONFIGURATIONS"
|
||||||
|
puts $fhe "GENERATE MOTOR CONFIGURATIONS"
|
||||||
|
puts $fhe "Required attributes: $REQ_ATTLIST"
|
||||||
|
|
||||||
|
set fhmc [open "generated_motor_configuration.tcl" "w"]
|
||||||
|
# Write configuration file header and make asyncqueues
|
||||||
|
mk_cfg_header $fhmc
|
||||||
|
puts $fhmc ""
|
||||||
|
|
||||||
|
foreach mn [lsort [array names motor_attcnt]] {
|
||||||
|
set mot_attlist [lsort [array names $mn]]
|
||||||
|
set mot_encattlist [lsort [array names ${mn}_encatts]]
|
||||||
|
set num_encatts [llength $mot_encattlist]
|
||||||
|
set missing_enc_atts [setdiff $ENC_ATTLIST $mot_encattlist]
|
||||||
|
set num_missing_encatts [llength $missing_enc_atts]
|
||||||
|
set posnum 0
|
||||||
|
|
||||||
|
# Decide if a motor configuration should be generated.
|
||||||
|
if [subset $REQ_ATTLIST $mot_attlist] {
|
||||||
|
set mk_config 1
|
||||||
|
} else {
|
||||||
|
if {$num_missing_encatts == 0} {
|
||||||
|
set mk_config 1
|
||||||
|
} else {
|
||||||
|
set mk_config 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Does this motor have an absolute encoder?
|
||||||
|
if {$num_missing_encatts == 0} {
|
||||||
|
set absenc 1
|
||||||
|
} else {
|
||||||
|
set absenc 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Decide if a list missing attributes with default values should be written.
|
||||||
|
if [subset $ENCMOT_ATTLIST $mot_attlist] {
|
||||||
|
set mk_missing_atts 1
|
||||||
|
} elseif [subset $MOT_ATTLIST $mot_attlist] {
|
||||||
|
set mk_missing_atts 1
|
||||||
|
} else {
|
||||||
|
set mk_missing_atts 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assume that the values of any attributes we don't recognise are encoder
|
||||||
|
# readings for a "posit" motor.
|
||||||
|
set posit_list {}
|
||||||
|
foreach att [setdiff $mot_attlist $ALL_ATTRIBUTES] {
|
||||||
|
incr posnum
|
||||||
|
lappend posit_list "posit_${posnum} \$${mn}_$att"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate a motor configuration and/or a list of missing attributes.
|
||||||
|
if ${mk_config} {
|
||||||
|
mk_motconf $mn $fhmc $absenc $posnum posit_list
|
||||||
|
puts $fhr "Configured $mn"
|
||||||
|
incr MOTCFG_CNT
|
||||||
|
} else {
|
||||||
|
set missing_atts [lsort [concat $missing_enc_atts [setdiff $::REQ_ATTLIST [array names $mn]]]]
|
||||||
|
puts $fhe "$mn attributes missing: $missing_atts"
|
||||||
|
incr FAILED_MOTCFG_CNT
|
||||||
|
incr ERRCNT
|
||||||
|
if {$mk_missing_atts} {
|
||||||
|
set undef_atts [setdiff [lsort "absenchome $SICS_CFG_ATTLIST"] $mot_attlist]
|
||||||
|
foreach att $undef_atts {
|
||||||
|
set attval [gen_attval $mn $att]
|
||||||
|
if {$attval != "NOATT"} {
|
||||||
|
lappend missing_attlist "${mn}_$att,$attval"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# If there are any missing attributes then write them to a file.
|
||||||
|
if [info exists missing_attlist] {
|
||||||
|
set fh [open "missing_attlist.csv" "w"]
|
||||||
|
foreach attval [lsort $missing_attlist] {
|
||||||
|
puts $fh $attval
|
||||||
|
}
|
||||||
|
close $fh
|
||||||
|
puts "Generated list of missing attributes in missing_attlist.csv. You should rename this if you want to keep it."
|
||||||
|
}
|
||||||
|
|
||||||
|
# The SICS init code calls motor_set_sobj_attributes. It can be redefined in
|
||||||
|
# the motor_configuration.tcl file.
|
||||||
|
puts $fhmc "proc motor_set_sobj_attributes {} {}"
|
||||||
|
puts stderr "Generated $MOTCFG_CNT motor driver configurations"
|
||||||
|
puts $fhr "Generated $MOTCFG_CNT motor driver configurations"
|
||||||
|
puts stderr "Found $FAILED_MOTCFG_CNT incomplete motor configurations. See ${scriptname}_errors.log and ${scriptname}_report.log"
|
||||||
|
puts $fhe "Found $FAILED_MOTCFG_CNT incomplete motor configurations"
|
||||||
|
|
||||||
|
close $fhmc
|
||||||
|
close $fhr
|
||||||
|
close $fhe
|
||||||
|
|
||||||
|
if {$ERRCNT > 0} {
|
||||||
|
puts stderr "Finished with $::ERRCNT errors"
|
||||||
|
}
|
||||||
231
site_ansto/instrument/util/genmotconf_procs.tcl
Executable file
231
site_ansto/instrument/util/genmotconf_procs.tcl
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
|
||||||
|
# @brief Parse motor attribute file and make list of motor attributes and controllers
|
||||||
|
# @param fname Name of two column csv file of motor attribute names and values
|
||||||
|
# @param fhr Report log file-handle.
|
||||||
|
# @param fhe Error log file-handle.
|
||||||
|
# Requires The global ::ENC_ATTLIST
|
||||||
|
# Creates the following globals,
|
||||||
|
# ::mn(matt) is an array of motor attributes where mn = motor name and matt = the attribute name
|
||||||
|
# ::mn_encatts(matt) is an array of encoder attributes where mn = motor name and matt = an attributed from ENC_ATTLIST
|
||||||
|
# ::controllers(cn) Counts number of axes used on each controller, cn = controller name.
|
||||||
|
# ::motor_attcnt(mn) The keys of this array provide a list of motor names and each value is a count of attributes found for each motor.
|
||||||
|
proc parse_file {fname fhr fhe} {
|
||||||
|
puts $fhr "\nPARSE '$fname'"
|
||||||
|
set lcount 0
|
||||||
|
set fh [open $fname "r"]
|
||||||
|
puts $fhr "Create a per motor attribute dictionary for each attribute in the following list of required attributes,"
|
||||||
|
while {[gets $fh line] >= 0} {
|
||||||
|
incr lcount
|
||||||
|
foreach {name val} [split $line {,}] {}
|
||||||
|
if [regexp {(_?[a-zA-Z][a-zA-Z0-9]*[0-9]*)_([a-zA-Z0-9_]+)} $name rem mn matt] {
|
||||||
|
puts $fhr "Processing '$line'"
|
||||||
|
if {[lsearch $::ENC_ATTLIST $matt] != -1} {
|
||||||
|
puts $fhr "Add ::${mn}_encatts($matt) = $val"
|
||||||
|
set ::${mn}_encatts($matt) $val
|
||||||
|
} else {
|
||||||
|
puts $fhr "Add ::${mn}($matt) = $val"
|
||||||
|
set ::${mn}($matt) $val
|
||||||
|
}
|
||||||
|
incr ::motor_attcnt($mn)
|
||||||
|
if { $matt == "mc" } {
|
||||||
|
incr ::controllers($val)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
incr ::ERRCNT
|
||||||
|
puts $fhe "Failed to match '$line'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close $fh
|
||||||
|
puts -nonewline $fhr "CONTROLLER USAGE COUNT: "
|
||||||
|
foreach {cont count} [array get ::controllers] {
|
||||||
|
puts -nonewline $fhr "$cont count=$count; "
|
||||||
|
}
|
||||||
|
puts $fhr ""
|
||||||
|
puts $fhr "PARSED $lcount lines in '$fname'; failed $::ERRCNT lines"
|
||||||
|
puts $fhe "$::ERRCNT ERRORS IN PARSE STAGE '$fname'. PROCESSED $lcount lines"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @brief Generate motor configuration file header and make asyncqueues
|
||||||
|
# @param fhmc Motor configuration file handle.
|
||||||
|
# Requires the following globals,
|
||||||
|
# ::argv0, ::argv, ::file_list, and ::controllers
|
||||||
|
proc mk_cfg_header {fhmc} {
|
||||||
|
puts $fhmc "#### SICS motor driver configuration ####"
|
||||||
|
puts $fhmc "# Generated by: $::argv0 $::argv"
|
||||||
|
puts $fhmc "# Date: [clock format [clock seconds] -format %Y-%m-%dT%H:%M:%S]"
|
||||||
|
puts $fhmc "# Generated from the following files,"
|
||||||
|
foreach f $::file_list {
|
||||||
|
incr fcntr
|
||||||
|
puts $fhmc "# file$fcntr: $f"
|
||||||
|
}
|
||||||
|
puts $fhmc ""
|
||||||
|
puts $fhmc "# Load motor driver configuration parameters"
|
||||||
|
puts $fhmc "set flist \[list\\"
|
||||||
|
foreach f $::file_list {
|
||||||
|
puts $fhmc " \{$f\}\\"
|
||||||
|
}
|
||||||
|
puts $fhmc " \]"
|
||||||
|
puts $fhmc {
|
||||||
|
foreach fattfile $flist {
|
||||||
|
if [catch {
|
||||||
|
set fattpath config/motors/$fattfile
|
||||||
|
set fh [open $fattpath RDONLY]
|
||||||
|
while {[gets $fh line] >= 0} {
|
||||||
|
eval "set [split $line {,}]"
|
||||||
|
}
|
||||||
|
close $fh
|
||||||
|
} msg] {
|
||||||
|
clientput ERROR: $msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
puts $fhmc {set sim_mode [SplitReply [motor_simulation]]}
|
||||||
|
|
||||||
|
# Setup addresses of Galil DMC2280 controllers.
|
||||||
|
puts $fhmc {
|
||||||
|
if {$sim_mode == true} {
|
||||||
|
set motor_driver_type asim
|
||||||
|
} else {
|
||||||
|
set motor_driver_type DMC2280
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts $fhmc "if {\$sim_mode == false} \{"
|
||||||
|
foreach cont [array names ::controllers] {
|
||||||
|
set index [string toupper $cont]
|
||||||
|
puts $fhmc " [subst -nocommands {MakeAsyncQueue $cont DMC2280 [dict get \$::MOTOR_HOSTPORT $index HOST] [dict get \$::MOTOR_HOSTPORT $index PORT]}]"
|
||||||
|
}
|
||||||
|
puts $fhmc \}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# @brief Generate motor driver configuration
|
||||||
|
# @param mot Motor name
|
||||||
|
# @param fh Motor configuration file handle
|
||||||
|
# @param absEnc boolean, generate absolute encoder configuration if true.
|
||||||
|
# @param posnum Number of discrete positions
|
||||||
|
# @param posit_list Name of global list of name value pairs, "posit_n pos". Can be empty.
|
||||||
|
proc mk_motconf {mot fh absEnc posnum posit_list} {
|
||||||
|
set mc [set ::${mot}(mc)]
|
||||||
|
set axis [set ::${mot}(axis)]
|
||||||
|
set units ${mot}_units
|
||||||
|
set hardlowerlim ${mot}_rev_lim
|
||||||
|
set hardupperlim ${mot}_fwd_lim
|
||||||
|
set softlowerlim ${mot}_rev_lim
|
||||||
|
set softupperlim ${mot}_fwd_lim
|
||||||
|
set maxSpeed ${mot}_maxspeed
|
||||||
|
set maxAccel ${mot}_maxaccel
|
||||||
|
set maxDecel ${mot}_maxdecel
|
||||||
|
set stepsPerX ${mot}_steps_per_x
|
||||||
|
set cntsPerX ${mot}_cnts_per_x
|
||||||
|
set absEncHome ${mot}_absenchome
|
||||||
|
set home ${mot}_home
|
||||||
|
set part ${mot}_part
|
||||||
|
set long_name $mot
|
||||||
|
set description "$mot configuration"
|
||||||
|
|
||||||
|
puts $fh "# $description"
|
||||||
|
puts $fh "Motor $mot \$motor_driver_type \[params\\"
|
||||||
|
puts $fh " asyncqueue $mc\\"
|
||||||
|
puts $fh " axis $axis\\"
|
||||||
|
puts $fh " units \$$units\\"
|
||||||
|
puts $fh " hardlowerlim \$$hardlowerlim\\"
|
||||||
|
puts $fh " hardupperlim \$$hardupperlim\\"
|
||||||
|
puts $fh " maxSpeed \$$maxSpeed\\"
|
||||||
|
puts $fh " maxAccel \$$maxAccel\\"
|
||||||
|
puts $fh " maxDecel \$$maxDecel\\"
|
||||||
|
puts $fh " stepsPerX \$$stepsPerX\\"
|
||||||
|
puts $fh " posit_count $posnum\\"
|
||||||
|
foreach positline $::posit_list {
|
||||||
|
puts $fh " $positline\\"
|
||||||
|
}
|
||||||
|
if {$absEnc} {
|
||||||
|
puts $fh " absEnc 1\\"
|
||||||
|
puts $fh " absEncHome \$$absEncHome\\"
|
||||||
|
puts $fh " cntsPerX \$$cntsPerX\]"
|
||||||
|
} else {
|
||||||
|
puts $fh " absEnc 0\]"
|
||||||
|
}
|
||||||
|
puts $fh "$mot softlowerlim \$$softlowerlim"
|
||||||
|
puts $fh "$mot softupperlim \$$softupperlim"
|
||||||
|
puts $fh "$mot home \$$home"
|
||||||
|
puts $fh "$mot part \$$part"
|
||||||
|
puts $fh "$mot long_name $long_name"
|
||||||
|
puts $fh ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# UTILITY FUNCTIONS
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# @brief Difference between sets A and B, ie A\B
|
||||||
|
# Invalid sets are allowed. Doesn't check for repetition.
|
||||||
|
# @return list of elements in A but not in B
|
||||||
|
proc setdiff {A B} {
|
||||||
|
set diff {}
|
||||||
|
foreach e $A {
|
||||||
|
if { [lsearch $B $e] == -1 } {
|
||||||
|
lappend diff $e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $diff
|
||||||
|
}
|
||||||
|
|
||||||
|
# @brief Tests if A is subset of B
|
||||||
|
# The sets must be sorted
|
||||||
|
# Doesn't check if sets are valid, eg repetition is allowed.
|
||||||
|
proc subset {A B} {
|
||||||
|
set Alen [llength $A]
|
||||||
|
set Blen [llength $B]
|
||||||
|
if { $Alen > $Blen } {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set Aend [expr {$Alen - 1}]
|
||||||
|
set Bend [expr {$Blen - 1}]
|
||||||
|
set asi 0
|
||||||
|
set aei 0
|
||||||
|
set bsi 0
|
||||||
|
set bei 0
|
||||||
|
while { $asi <= [expr {$Aend - $aei}] } {
|
||||||
|
# puts "A = [lrange $A $asi end-$aei]"
|
||||||
|
set firstmatch false
|
||||||
|
set lastmatch false
|
||||||
|
while { $bsi <= [expr {$Bend - $bei}] } {
|
||||||
|
# puts " B = [lrange $B $bsi end-$bei]"
|
||||||
|
if { $firstmatch == false } {
|
||||||
|
set aval [lindex $A $asi]
|
||||||
|
set bval [lindex $B $bsi]
|
||||||
|
if { $aval == $bval} {
|
||||||
|
set firstmatch true
|
||||||
|
# puts " firstmatch = $firstmatch"
|
||||||
|
} elseif {$aval < $bval} {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
incr bsi
|
||||||
|
}
|
||||||
|
if { $lastmatch == false } {
|
||||||
|
set aval [lindex $A end-$aei]
|
||||||
|
set bval [lindex $B end-$bei]
|
||||||
|
if { $aval == $bval } {
|
||||||
|
set lastmatch true
|
||||||
|
# puts " lastmatch = $lastmatch"
|
||||||
|
} elseif {$aval > $bval} {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
incr bei
|
||||||
|
}
|
||||||
|
if { ($firstmatch == true) && ($lastmatch == true) } {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if { ($firstmatch == false) && ($lastmatch == false) } {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
incr asi
|
||||||
|
incr aei
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user