Scripts which generate a motor configuration file from CSV data files.

This commit is contained in:
Ferdi Franceschini
2014-01-15 15:27:19 +11:00
parent e984619d77
commit 2481abb8af
2 changed files with 431 additions and 0 deletions

View 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"
}

View 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
}