Import LinuxCNC source tree

This commit is contained in:
Wang Dequan
2026-08-20 06:17:39 +08:00
commit 2c814634c3
9429 changed files with 10556013 additions and 0 deletions

70
lib/hallib/README Normal file
View File

@@ -0,0 +1,70 @@
This directory contains halfiles (*.hal and *.tcl) that are available
using the LinuxCNC halfile search path or by an explicit directive.
The HALLIB_PATH is '.:HALLIB_DIR'
The '.' specifies the directory containing the INI file.
HALIB_DIR specifies this directory.
The explicit directive uses a prefix (LIB:) to identify halfiles:
[HAL]
HALFILE = LIB:filename.[hal|tcl]
The LinuxCNC startup script (linuxcnc) finds each halfile using the
HALLIB_PATH unless the explicit directive is used.
Also included in this directory are files associated with included
system halfiles. For example, the button layout files
(xhc-hb04-layout*.cfg) that are used with the halfile xhc-hb04.tcl.
------------------------------------------------------------------------
Hal files (*.hal) Notes
----------------------- ---------------------------------------------
axis_manualtoolchange.hal setup for using hal_manualtoolchange
(for axis or other guis)
core_sim.hal xyz simulator config
core_servo.hal xyz servo (pid) config
core_sim9.hal 9 axis (xyzabcuvw) simulation config
core_stepper.hal xyz stepper config
gantrysim.hal gantrykins config, 4 joints: X Y Y Z
lathe.hal simulate spindle with encoder,sim_encoder
locking_indexer.hal simulate locking rotary using timedelay comp
wheeljogpins.tcl enable wheel jog pins for joints & axes
moveoff_external.hal Simulate external control connections for
a moveoff component
servo_sim.hal simulate servo (pid) xyz using lowpass comp
sim_spindle_encoder.hal simulate spindle with lowpass filter
simulated-gantry-home.hal simulate gantry home switches (4 joints)
simulated_home.hal simulate xyz home switches
simulated_limits.hal simulate xyz limit switches
tripodsim.hal simulated tripodkins system
Haltcl Files (*.tcl) Notes
----------------------- ---------------------------------------------
basic_sim.tcl set up a sim config (arbitrary no. of axes)
var_show.tcl show INI variables and context
hookup_moveoff.tcl make connections for a moveoff component
plasmac.tcl common connections for the plasmac component
xhc-hb04.tcl Configuration builder for xhc-hb04 pendant
xhc-hb04-layout1.cfg button layout 1 for xhc-hb04.tcl
xhc-hb04-layout2.cfg button layout 2 for xhc-hb04.tcl
halcheck.tcl Report: 1) functions without addf
2) signals with no inputs
3) signals with no output
Haltcl libs (*_lib.tcl) Notes
----------------------- ---------------------------------------------
sim_lib.tcl simulator config procedures
procs:
core_sim (arbitrary axes)
make-ddts (arbitrary axes)
simulated_home (arbitrary axes)
use_hal_manualtoolchange
sim_spindle
util_lib.tcl utility procedures
procs:
show_context (calling parms)
show_ini (INI file settings)
show_env (environmental vars)

View File

@@ -0,0 +1,10 @@
loadusr -W hal_manualtoolchange
# in case they were linked already
unlinkp iocontrol.0.tool-change
unlinkp iocontrol.0.tool-changed
net tool-change hal_manualtoolchange.change iocontrol.0.tool-change
net tool-changed hal_manualtoolchange.changed iocontrol.0.tool-changed
net tool-prep-number hal_manualtoolchange.number iocontrol.0.tool-prep-number

71
lib/hallib/basic_sim.tcl Normal file
View File

@@ -0,0 +1,71 @@
# basic_sim.tcl
#
# Provide common hal components and connections for a simulated machine.
# By default, the script makes and connects ddts, simulated_home,
# spindle, and hal_manualtoolchange components.
#
# Options are available to
# a) disable specific hal components and connections
# b) save a halfile equivalent to the hal commands executed by
# this script (and any prior executed hal commands)
#
# Coordinate letters and number_of_joints are determined from the usual
# INI file settings.
#
# INI file usage:
# [HAL]HALFILE = basic_sim.tcl [Options]
# Options:
# -no_make_ddts
# -no_simulated_home
# -no_use_hal_manualtoolchange
# -no_sim_spindle
#----------------------------------------------------------------------
# Notes:
# 1) ::env() is a global associative array of environmental variables
# as exported by the main LinuxCNC script (linuxcnc)
# 2) Settings from the INI file are available as global associative
# arrays named: ::SECTION(varname)
# example: ::EMCMOT(SERVO_PERIOD)
# 3) procs are from sim_lib.tcl
#begin-----------------------------------------------------------------
source [file join $::env(HALLIB_DIR) sim_lib.tcl]
set save_options {}
if [catch {check_ini_items} msg] {
puts "\nbasic_sim.tcl ERROR: $msg\n"
exit 1
}
set axes [get_traj_coordinates]
set number_of_joints $::KINS(JOINTS)
set base_period 0 ;# 0 means no thread
if [info exists ::EMCMOT(BASE_PERIOD)] {
set base_period $::EMCMOT(BASE_PERIOD)
}
core_sim $axes \
$number_of_joints \
$::EMCMOT(SERVO_PERIOD) \
$base_period \
$::EMCMOT(EMCMOT)
if {[lsearch -nocase $::argv -no_make_ddts] < 0} {
make_ddts $number_of_joints
}
if {[lsearch -nocase $::argv -no_simulated_home] < 0} {
simulated_home $number_of_joints
}
if {[lsearch -nocase $::argv -no_use_hal_manualtoolchange] < 0} {
use_hal_manualtoolchange
lappend save_options use_hal_manualtoolchange
}
if {[lsearch -nocase $::argv -no_sim_spindle] < 0} {
sim_spindle
}
# make a halfile (inifilename_cmds.hal) with equivalent hal commands
set savefilename \
./[file rootname [file tail $::env(INI_FILE_NAME)]]_cmds.hal
save_hal_cmds $savefilename $save_options

View File

@@ -0,0 +1,63 @@
# HAL config file to check vel/acc constraints
#
loadrt wcomp names=wcomp_xacc,wcomp_xvel,wcomp_yacc,wcomp_yvel,wcomp_zacc,wcomp_zvel
addf wcomp_xacc servo-thread
addf wcomp_xvel servo-thread
addf wcomp_yacc servo-thread
addf wcomp_yvel servo-thread
addf wcomp_zacc servo-thread
addf wcomp_zvel servo-thread
net Xacc => wcomp_xacc.in
net Xvel => wcomp_xvel.in
net Yacc => wcomp_yacc.in
net Yvel => wcomp_yvel.in
net Zacc => wcomp_zacc.in
net Zvel => wcomp_zvel.in
net acc-ok-x <= wcomp_xacc.out
net vel-ok-x <= wcomp_xvel.out
net acc-ok-y <= wcomp_yacc.out
net vel-ok-y <= wcomp_yvel.out
net acc-ok-z <= wcomp_zacc.out
net vel-ok-z <= wcomp_zvel.out
setp wcomp_xacc.max 50.001
setp wcomp_xacc.min -50.001
setp wcomp_xvel.max 5.001
setp wcomp_xvel.min -5.001
setp wcomp_yacc.max 50.001
setp wcomp_yacc.min -50.001
setp wcomp_yvel.max 5.001
setp wcomp_yvel.min -5.001
setp wcomp_zacc.max 50.001
setp wcomp_zacc.min -50.001
setp wcomp_zvel.max 5.001
setp wcomp_zvel.min -5.001
loadrt match8 names=match_all
addf match_all servo-thread
net acc-ok-x => match_all.a0
setp match_all.b0 1
net vel-ok-x => match_all.a1
setp match_all.b1 1
net acc-ok-y => match_all.a2
setp match_all.b2 1
net vel-ok-y => match_all.a3
setp match_all.b3 1
net acc-ok-z => match_all.a4
setp match_all.b4 1
net vel-ok-z => match_all.a5
setp match_all.b5 1
setp match_all.a6 0
setp match_all.a7 0
setp match_all.b6 0
setp match_all.b7 0
setp match_all.in 1
net constraints-ok <= match_all.out

74
lib/hallib/core_servo.hal Normal file
View File

@@ -0,0 +1,74 @@
# core HAL config file for servos
# first load the core RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# PID module, for three PID loops
loadrt pid num_chan=3
# hook functions to realtime thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
addf pid.0.do-pid-calcs servo-thread
addf pid.1.do-pid-calcs servo-thread
addf pid.2.do-pid-calcs servo-thread
# connect position feedback
net Xpos-fb joint.0.motor-pos-fb => pid.0.feedback
net Ypos-fb joint.1.motor-pos-fb => pid.1.feedback
net Zpos-fb joint.2.motor-pos-fb => pid.2.feedback
# create PID to DAC output signals
net Xoutput <= pid.0.output
net Youtput <= pid.1.output
net Zoutput <= pid.2.output
# set PID loop output limits to +/-1.00
setp pid.0.maxoutput [JOINT_0]MAX_VELOCITY
setp pid.1.maxoutput [JOINT_1]MAX_VELOCITY
setp pid.2.maxoutput [JOINT_2]MAX_VELOCITY
# set PID loop gains from INI file
# the values below come from the INI
setp pid.0.Pgain [JOINT_0]P
setp pid.0.Igain [JOINT_0]I
setp pid.0.Dgain [JOINT_0]D
setp pid.0.bias [JOINT_0]BIAS
setp pid.0.FF0 [JOINT_0]FF0
setp pid.0.FF1 [JOINT_0]FF1
setp pid.0.FF2 [JOINT_0]FF2
# deadband should be just over 1 count
setp pid.0.deadband [JOINT_0]DEADBAND
setp pid.1.Pgain [JOINT_1]P
setp pid.1.Igain [JOINT_1]I
setp pid.1.Dgain [JOINT_1]D
setp pid.1.bias [JOINT_1]BIAS
setp pid.1.FF0 [JOINT_1]FF0
setp pid.1.FF1 [JOINT_1]FF1
setp pid.1.FF2 [JOINT_1]FF2
# deadband should be just over 1 count
setp pid.1.deadband [JOINT_1]DEADBAND
setp pid.2.Pgain [JOINT_2]P
setp pid.2.Igain [JOINT_2]I
setp pid.2.Dgain [JOINT_2]D
setp pid.2.bias [JOINT_2]BIAS
setp pid.2.FF0 [JOINT_2]FF0
setp pid.2.FF1 [JOINT_2]FF1
setp pid.2.FF2 [JOINT_2]FF2
# deadband should be just over 1 count
setp pid.2.deadband [JOINT_2]DEADBAND
# position command signals
net Xpos-cmd joint.0.motor-pos-cmd => pid.0.command
net Ypos-cmd joint.1.motor-pos-cmd => pid.1.command
net Zpos-cmd joint.2.motor-pos-cmd => pid.2.command
# joint enable signals
net Xenable joint.0.amp-enable-out => pid.0.enable
net Yenable joint.1.amp-enable-out => pid.1.enable
net Zenable joint.2.amp-enable-out => pid.2.enable

51
lib/hallib/core_sim.hal Normal file
View File

@@ -0,0 +1,51 @@
# core HAL config file for simulation
# first load all the RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# load 6 differentiators (for velocity and accel signals
loadrt ddt names=ddt_x,ddt_xv,ddt_y,ddt_yv,ddt_z,ddt_zv
# load additional blocks
loadrt hypot names=vel_xy,vel_xyz
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# link the differentiator functions into the code
addf ddt_x servo-thread
addf ddt_xv servo-thread
addf ddt_y servo-thread
addf ddt_yv servo-thread
addf ddt_z servo-thread
addf ddt_zv servo-thread
addf vel_xy servo-thread
addf vel_xyz servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net Xpos joint.0.motor-pos-cmd => joint.0.motor-pos-fb ddt_x.in
net Ypos joint.1.motor-pos-cmd => joint.1.motor-pos-fb ddt_y.in
net Zpos joint.2.motor-pos-cmd => joint.2.motor-pos-fb ddt_z.in
# send the position commands thru differentiators to
# generate velocity and accel signals
net Xvel ddt_x.out => ddt_xv.in vel_xy.in0
net Xacc <= ddt_xv.out
net Yvel ddt_y.out => ddt_yv.in vel_xy.in1
net Yacc <= ddt_yv.out
net Zvel ddt_z.out => ddt_zv.in vel_xyz.in0
net Zacc <= ddt_zv.out
# Cartesian 2- and 3-axis velocities
net XYvel vel_xy.out => vel_xyz.in1
net XYZvel <= vel_xyz.out
# estop loopback
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed

66
lib/hallib/core_sim9.hal Normal file
View File

@@ -0,0 +1,66 @@
#!!! DECLINING USAGE use: HALFILE=LIB:basic_sim.tcl to automatically
#!!! make the components and connections required for sim configs
# core HAL config file for simulation
# first load all the RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# load 6 differentiators (for velocity and accel signals
loadrt ddt names=ddt_x,ddt_xv,ddt_y,ddt_yv,ddt_z,ddt_zv
# load additional blocks
loadrt hypot names=vel_xy,vel_xyz
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# link the differentiator functions into the code
addf ddt_x servo-thread
addf ddt_xv servo-thread
addf ddt_y servo-thread
addf ddt_yv servo-thread
addf ddt_z servo-thread
addf ddt_zv servo-thread
addf vel_xy servo-thread
addf vel_xyz servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net Xpos joint.0.motor-pos-cmd => joint.0.motor-pos-fb ddt_x.in
net Ypos joint.1.motor-pos-cmd => joint.1.motor-pos-fb ddt_y.in
net Zpos joint.2.motor-pos-cmd => joint.2.motor-pos-fb ddt_z.in
net Apos joint.3.motor-pos-cmd => joint.3.motor-pos-fb
net Bpos joint.4.motor-pos-cmd => joint.4.motor-pos-fb
net Cpos joint.5.motor-pos-cmd => joint.5.motor-pos-fb
net Upos joint.6.motor-pos-cmd => joint.6.motor-pos-fb
net Vpos joint.7.motor-pos-cmd => joint.7.motor-pos-fb
net Wpos joint.8.motor-pos-cmd => joint.8.motor-pos-fb
# send the position commands thru differentiators to
# generate velocity and accel signals
net Xvel ddt_x.out => ddt_xv.in vel_xy.in0
net Xacc <= ddt_xv.out
net Yvel ddt_y.out => ddt_yv.in vel_xy.in1
net Yacc <= ddt_yv.out
net Zvel ddt_z.out => ddt_zv.in vel_xyz.in0
net Zacc <= ddt_zv.out
# Cartesian 2- and 3-axis velocities
net XYvel vel_xy.out => vel_xyz.in1
net XYZvel <= vel_xyz.out
# estop loopback
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed
net spindle-fwd spindle.0.forward
net spindle-rev spindle.0.reverse
#net spindle-speed spindle.0.speed-out
net flood iocontrol.0.coolant-flood
net mist iocontrol.0.coolant-mist

View File

@@ -0,0 +1,53 @@
# core HAL config file for steppers
# first load the core RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# stepper module, three step generators, all three using step/dir
loadrt stepgen step_type=0,0,0
# hook functions to base thread (high speed thread for step generation)
addf stepgen.make-pulses base-thread
# hook functions to servo thread
addf stepgen.capture-position servo-thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
addf stepgen.update-freq servo-thread
# connect position commands from motion module to step generator
net Xpos-cmd joint.0.motor-pos-cmd => stepgen.0.position-cmd
net Ypos-cmd joint.1.motor-pos-cmd => stepgen.1.position-cmd
net Zpos-cmd joint.2.motor-pos-cmd => stepgen.2.position-cmd
# connect position feedback from step generators
# to motion module
net Xpos-fb stepgen.0.position-fb => joint.0.motor-pos-fb
net Ypos-fb stepgen.1.position-fb => joint.1.motor-pos-fb
net Zpos-fb stepgen.2.position-fb => joint.2.motor-pos-fb
# connect enable signals for step generators
net Xen joint.0.amp-enable-out => stepgen.0.enable
net Yen joint.1.amp-enable-out => stepgen.1.enable
net Zen joint.2.amp-enable-out => stepgen.2.enable
# connect signals to step pulse generator outputs
net Xstep <= stepgen.0.step
net Xdir <= stepgen.0.dir
net Ystep <= stepgen.1.step
net Ydir <= stepgen.1.dir
net Zstep <= stepgen.2.step
net Zdir <= stepgen.2.dir
# set stepgen module scaling - get values from INI file
setp stepgen.0.position-scale [JOINT_0]SCALE
setp stepgen.1.position-scale [JOINT_1]SCALE
setp stepgen.2.position-scale [JOINT_2]SCALE
# set stepgen module accel limits - get values from INI file
setp stepgen.0.maxaccel [JOINT_0]STEPGEN_MAXACCEL
setp stepgen.1.maxaccel [JOINT_1]STEPGEN_MAXACCEL
setp stepgen.2.maxaccel [JOINT_2]STEPGEN_MAXACCEL

35
lib/hallib/gantrysim.hal Normal file
View File

@@ -0,0 +1,35 @@
loadrt [KINS]KINEMATICS
show comp
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net J0pos joint.0.motor-pos-cmd => joint.0.motor-pos-fb
net J1pos joint.1.motor-pos-cmd => joint.1.motor-pos-fb
net J2pos joint.2.motor-pos-cmd => joint.2.motor-pos-fb
net J3pos joint.3.motor-pos-cmd => joint.3.motor-pos-fb
# estop loopback
net estop-loop iocontrol.0.user-enable-out => iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare => iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change => iocontrol.0.tool-changed
# amp control - these nets are not used, but are placeholders for
# converting this sample config to actual machines
net J0ena <= joint.0.amp-enable-out
net J1ena <= joint.1.amp-enable-out
net J2ena <= joint.2.amp-enable-out
net J3ena <= joint.3.amp-enable-out
net J0flt => joint.0.amp-fault-in
net J1flt => joint.1.amp-fault-in
net J2flt => joint.2.amp-fault-in
net J3flt => joint.3.amp-fault-in

View File

@@ -0,0 +1,190 @@
# hal_procs_lib.tcl
package require Hal ;# provides hal commands
proc pin_exists {name} {
# return 1 if pin exists
if { [lindex [hal list pin "$name"] 0] == "$name"} {return 1}
return 0
} ;# pin_exists
proc connection_info {result_name pinname } {
# return 0 if pinname not found, else 1
# result_name is associative array with details
# owner type direction value separator signame
upvar $result_name ay
set ans [hal show pin $pinname]
set lines [split $ans \n]
set sig ""
set sep ""
set ct [scan [lindex $lines 2] "%s %s %s %s %s %s %s" \
owner type dir val name sep sig]
if {$ct <0} {
return 0
}
set ay(owner) $owner
set ay(type) $type
set ay(direction) $dir
set ay(value) $val
set ay(separator) $sep
set ay(signame) $sig
return 1
} ;# connection_info
proc is_connected {pinname {signame {} } } {
# return is_input or is_output or is_io or not_connected
# set signame to signal name if connected
upvar $signame thesig
set ans [hal show pin $pinname]
set lines [split $ans \n]
set thesig ""
set sep ""
set ct [scan [lindex $lines 2] "%s %s %s %s %s %s %s" \
owner type dir val name sep thesig]
if {$ct <0} {return not_connected}
if {"$sep" == "<=="} {return is_input}
if {"$sep" == "==>"} {return is_output}
if {"$sep" == "<=>"} {return is_io}
return "not_connected"
} ;# is_connected
proc thread_info {ay_name} {
# return details about threads in associative array ay_name
# items for each $threadname:
# ($threadname,$componentname) $threadname index for $componentname
# ($threadname,fp) $threadname uses floatingpoint
# ($threadname,period) $threadname period
# ($threadname,index,last) $threadname last index used
#
# motion specific items:
# (motion-command-handler,threadname) threadname
# (motion-command-handler,index) index
# (motion-controller,threadname) threadname
# (motion-controller,index) index
# other:
# (threadnames) list of all threanames
#
# return string is $ay(motion-controller,threadname)
upvar $ay_name ay
set ans [hal show thread]
set lines [split $ans \n]
set header_len 2
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set remainder ""
set ct 0
foreach line $lines {
catch {unset f1 f2 f3 f4}
scan [lindex $lines $ct] \
"%s %s %s %s" \
f1 f2 f3 f4
if ![info exists f3] {
set index $f1; set compname $f2
set ay($threadname,$compname) $index
set ay($threadname,index,last) $index
if {"$compname" == "motion-command-handler"} {
set ay(motion-command-handler,threadname) $threadname
set ay(motion-command-handler,index) $index
}
if {"$compname" == "motion-controller"} {
set ay(motion-controller,threadname) $threadname
set ay(motion-controller,index) $index
}
} else {
set period $f1; set fp $f2; set threadname $f3
lappend ay(threadnames) $threadname
set ay($threadname,fp) $fp
set ay($threadname,period) $period
}
incr ct
}
if [info exists ay(motion-controller,threadname)] {
if [info exists ay(motion-command-handler,threadname)] {
if { "$ay(motion-controller,threadname)" \
!= "$ay(motion-command-handler,threadname)" \
} {
return -code error "thread_info: mot funcs on separate threads"
if { $ay(motion-command-handler,index) \
> $ay(motion-controller,index) \
} {
return -code error "thread_info: mot funcs OUT-OF-SEQUENCE"
}
}
return $ay(motion-controller,threadname)
} else {
return -code error "thread_info: motion-controller not found"
}
}
} ;# thread_info
proc get_netlist {inpins_name outpin_name iopins_name signame} {
# input: signame
# outputs:
# inpins_name list of input pins for signame or ""
# iopins_name list of io pins for signame or ""
# outpin_name output pin for signame or ""
upvar $inpins_name inpins
upvar $iopins_name iopins
upvar $outpin_name outpin
set inpins ""
set iopins ""
set outpin ""
set header_len 2
set lines [split [hal show signal $signame] \n]
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
foreach line $lines {set l($ct) [string trim $line];incr ct}
set ct_max $ct
set ct 0
for {set ct 0} {$ct < $ct_max} {incr ct} {
set v0 [lindex $l($ct) 0]
set v1 [lindex $l($ct) 1]
set v2 [lindex $l($ct) 2]
switch $v0 {
float -
bit -
u32 -
s32 {set sname $v2} ;# set on 1st non-header line
"==>" {}
"<==" {}
"<=>" {}
* {return -code error "get_netlist: Unexpected <$line">}
}
if {"$signame"=="$sname"} {
switch $v0 {
"==>" {lappend inpins $v1}
"<==" {lappend outpin $v1}
"<=>" {lappend iopins $v1}
}
}
}
} ;# get_netlist
proc find_file_in_hallib_path {filename {inifile .}} {
# find halfile using path HALLIB_PATH=.:HALLIB_DIR (see scripts/linuxcnc.in)
set libtag LIB: ;# special prefix for explicit use of hallib file
set halname [lindex $filename end]
if {[string first "$libtag" $halname] == 0} {
# explicit $libtag used
set halname [string range $halname [string len $libtag] end]
set usehalname [file join $::env(HALLIB_DIR) $halname]
} else {
if {[file pathtype $filename] == "absolute"} {
set usehalname $filename
} else {
# relative file specifier (relative to INI file directory)
set usehalname [file join [file dirname $inifile] $halname]
if ![file readable $usehalname] {
# use ::env(HALLIB_DIR)
set usehalname [file join $::env(HALLIB_DIR) $halname]
}
}
}
if [file readable $usehalname] {
return $usehalname
}
return -code error "find_file_in_hallib_path: cannot find: <$filename>"
} ;# find_file_in_hallib_path

171
lib/hallib/halcheck.tcl Normal file
View File

@@ -0,0 +1,171 @@
# halcheck.tcl: a halfile to report:
# 1) functions with no addf (usually an error)
# 2) signals with no inputs (not an error)
# 3) signals with no output (not an error)
#
# Usage in INI file (must be the last HALFILE):
# [HAL]
# ...
# HALFILE = LIB:halcheck.tcl
#
# Note: connections in a POSTGUI_HALFILE are not checked
#
# config items:
set ::popup 1 ;# default: enable popup (in addition to prints)
set ::bigfont {Helvetica 12 bold} ;# {Family size weight}
#----------------------------------------------------------------------
set ::prog halcheck
# test for ::argv items on halfile line (requires 2.7)
if [info exists ::argv] {
if {[lsearch $::argv nopopup] >= 0} {
set ::popup 0 ;# disable popup
}
}
#----------------------------------------------------------------------
proc gui_message { type items } {
if { "$items" == "" } return
# twopass processing uses built-ins only (no Tk)
if {$::popup && [catch {package require Tk} msg]} {
set ::popup 0
}
if { !$::popup } return
if { ![winfo exists .b] } {
wm title . "HAL checks"
pack [button .b \
-text Dismiss \
-command {destroy .} \
] -side bottom
}
set items [string map {" " \n} $items]
switch $type {
no_addfs {
pack [label .noa1 \
-text "Functions with no addf: " \
-font $::bigfont \
-relief ridge -bd 3 \
] -side top -fill x -expand 1
pack [label .noa2 \
-text "$items" \
-justify left \
-anchor w \
-relief sunken -bd 3 \
] -side top -fill x -expand 1
}
no_inputs {
pack [label .noi1 \
-text "Signals with no inputs: " \
-font $::bigfont \
-relief ridge -bd 3 \
] -side top -fill x -expand 1
pack [label .noi2 \
-text "$items" \
-justify left \
-anchor w \
-relief sunken -bd 3 \
] -side top -fill x -expand 1
}
no_output {
pack [label .noo1 \
-text "Signals with no output: " \
-font $::bigfont \
-relief ridge -bd 3 \
] -side top -fill x -expand 1
pack [label .noo2 \
-text "$items" \
-justify left \
-anchor w \
-relief sunken -bd 3 \
] -side top -fill x -expand 1
}
}
} ;# gui_message
proc check_function_usage {} {
set ans [hal show function]
set lines [split $ans \n]
set header_len 2
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
set no_addfs ""
foreach line $lines {
catch {unset f1 f2 f3 f4}
scan [lindex $lines $ct] \
"%s %s %s %s %s %s" \
owner codeaddr arg fp users name
if {"$users" == 0} {
puts "$::prog: Functions with no addf: $name"
lappend no_addfs $name
}
incr ct
}
gui_message no_addfs $no_addfs
} ;# check_function_usage
proc check_signal_usage {} {
set ans [hal show signal]
set lines [split $ans \n]
set header_len 2
set lines [lreplace $lines 0 [expr $header_len -1]]
set lines [lreplace $lines end end]
set ct 0
foreach line $lines {
set howmany [scan $line "%s%s%s" fld1 fld2 fld3]
if {$howmany == 3} {
set signame $fld3
set ::SIG($signame,type) $fld1
set ::SIG($signame,value) $fld2
set ::SIG($signame,inputs) ""
set ::SIG($signame,output) ""
set ::SIG($signame,ios) ""
continue
}
switch $fld1 {
"==>" {lappend ::SIG($signame,inputs) $fld2}
"<==" {lappend ::SIG($signame,output) $fld2}
"<=>" {lappend ::SIG($signame,ios) $fld2}
default {return -code error "check-signal_usage: unrecognized <$line>"}
}
incr ct
}
set no_inputs ""
foreach iname [array names ::SIG *,inputs] {
set signame [lindex [split $iname ,] 0]
if {$::SIG($iname) == ""} {
set msg "$::prog: Signal: $signame <$::SIG($signame,value)> NO inputs"
if {$::SIG($signame,ios) != ""} {
set msg "$msg (i/o:$::SIG($signame,ios))"
} else {
lappend no_inputs $signame
}
puts "$msg"
}
}
set no_output ""
foreach oname [array names ::SIG *,output] {
set signame [lindex [split $oname ,] 0]
if {$::SIG($oname) == ""} {
set msg "$::prog: Signal: $signame <$::SIG($signame,value)> NO output"
if {$::SIG($signame,ios) != ""} {
set msg "$msg (i/o:$::SIG($signame,ios))"
} else {
lappend no_output $signame
}
puts "$msg"
}
}
foreach iname [array names ::SIG *,ios] {
if { $::SIG($iname) == ""} {unset ::SIG($iname)}
}
gui_message no_inputs $no_inputs
gui_message no_output $no_output
} ;# check_signal_usage
#----------------------------------------------------------------------
# begin
check_function_usage
check_signal_usage

View File

@@ -0,0 +1,316 @@
# hallib procs:
source [file join $::env(HALLIB_DIR) hal_procs_lib.tcl]
#
# hookup_moveoff.tcl -- HALFILE to:
# 1) disconnect initial pos-cmd and pos-fb pin connections
# 2) loadrt the moveoff component with name=mv
# 3) addf the moveoff component functions in required sequence
# 4) reconnect the pos-cmd and pos-fb pins to use the component
#
# Support for demo type INI files where the pos-cmd and pos-fb pins are
# 'shortcircuit' connected together is included.
#
# The moveoff component may be initialized with settings from the INI file
#
# Usage:
# 1) Specify this file in the INI file as [HAL]HALFILE
# Its position must follow halfiles that connect the pos-cmd and
# pos-fb pins.
# [HAL]
# ...
# HALFILE = hookup_moveoff.tcl
# ...
#
# 2) Include INI file entries for moveoff component settings:
# [MOVEOFF]
# EPSILON =
# WAYPOINT_SAMPLE_SECS =
# WAYPOINT_THRESHOLD =
# BACKTRACK_ENABLE =
#
# If these settings are not found in the INI file, the moveoff
# component defaults will be used.
#
# 3) Include INI file entries for the per-joint settings
# [MOVEOFF_n]
# MAX_VELOCITY =
# MAX_ACCELERATION =
# MAX_LIMIT =
# MIN_LIMIT =
#
# If settings are not found in the INI file, the items
# [JOINT_n]
# MAX_VELOCITY =
# MAX_ACCELERATION =
# MAX_LIMIT =
# MIN_LIMIT =
# are used. If these are not defined, the moveoff component
# defaults are used.
#
# To use the (optional) demonstration gui named moveoff_gui,
# include an INI entry:
#
# [APPLICATIONS]
# APP = moveoff_gui option1 option2 ...
#
# For available options, Use:
# $ moveoff_gui --help
#
# The moveoff_gui will provide a display and control for
# enabling offsetting if the pin mv.move-enable is not connected
# when moveoff_gui is started.
#
# If the mv.move-enable pin is connected when moveoff_gui
# is started, then it will provide a display but no control.
# This mode supports hal connections for a jog wheel or other
# methods of controlling the offset input pins (mv.offset-M)
#-----------------------------------------------------------------------
# Copyright: 2014
# Authors: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
proc do_hal {args} {
if {[info exists ::noexecute] && $::noexecute} {
puts "((do_hal))$args"
} else {
if {$::HU(verbose)} {puts do_hal:$args}
eval hal $args
}
} ;# do_hal
proc setup_pinnames {} {
# Note: works for standard names but a custom proc is needed
# here if the Hal alias command is used on the names
# Identify JOINT_n stanzas
for {set a 0} {$a < 9} {incr a} {
if [info exists ::JOINT_[set a](TYPE)] {
lappend ::HU(axes) $a
set ::HU(highest_joint_num) $a
}
}
foreach a $::HU(axes) {
set ::HU($a,pos,pinname) joint.${a}.motor-pos-cmd
set ::HU($a,fb,pinname) joint.${a}.motor-pos-fb
}
} ;# setup_pinnames
proc install_moveoff {} {
# note expected name for moveoff_gui is $::m
set pnumber [expr 1 + $::HU(highest_joint_num)]
do_hal loadrt moveoff personality=$pnumber names=$::m
set mot_thread $::HU(motion-controller,threadname)
if [info exists ::HU($mot_thread,motion-controller)] {
set write_index [expr 1 + $::HU($mot_thread,motion-controller)]
} else {
set write_index [expr 1 + $::HU($mot_thread,index,last)]
}
do_hal addf $::m.write-outputs $mot_thread $write_index
if [info exists ::HU($mot_thread,motion-command-handler)] {
if {1 == $::HU($mot_thread,motion-command-handler) } {
set read_index 1
} else {
set read_index [expr -1 + $::HU($mot_thread,motion-command-handler)]
}
} else {
set read_index 1
}
do_hal addf $::m.read-inputs $mot_thread $read_index
} ;# install_moveoff
proc disconnect_pos_from_motion {a} {
set pinname $::HU($a,pos,pinname)
set inpins {}; set outpin {}
if [connection_info tmp $pinname] {
if {$tmp(signame) != ""} {
get_netlist inpins outpin iopins $tmp(signame)
set ::HU($a,pos,signame) $tmp(signame)
set ::HU($a,pos,inputs) $inpins
set ::HU($a,pos,output) $outpin
}
} else {
return -code error "<$pinname> not connected as expected"
}
do_hal unlinkp $pinname
} ;# disconnect_pos_from_motion
proc disconnect_fb_to_motion {a} {
set pinname $::HU($a,fb,pinname)
set inpins {}; set outpin {}
if [connection_info tmp $pinname] {
if {$tmp(signame) != ""} {
get_netlist inpins outpin iopins $tmp(signame)
set ::HU($a,fb,signame) $tmp(signame)
set ::HU($a,fb,inputs) $inpins
set ::HU($a,fb,output) $outpin
}
} else {
return -code error "<$pinname> not connected as expected"
}
do_hal unlinkp $pinname
} ;# disconnect_fb_to_motion
proc check_for_short_circuit {a} {
if {$::HU($a,fb,signame) == $::HU($a,pos,signame)} {
set ::HU($a,shortcircuit) 1
} else {
set ::HU($a,shortcircuit) 0
}
} ;# check_for_short_circuit
proc new_connect_pos_to_moveoff {a} {
do_hal delsig $::HU($a,pos,signame)
do_hal net hu:pos-$a <= $::HU($a,pos,pinname)
do_hal net hu:pos-$a => $::m.pos-$a
} ;# new_connect_pos_to_moveoff
proc new_connect_plus_offset {a} {
if $::HU($a,shortcircuit) return
do_hal net hu:plus-$a <= $::m.pos-plusoffset-$a
foreach in_pinname $::HU($a,pos,inputs) {
if {"$in_pinname" == "$::HU($a,fb,pinname)"} continue
do_hal unlinkp $in_pinname
do_hal net hu:plus-$a => $in_pinname
}
} ;# new_connect_plus_offset
proc new_connect_minus_offset {a} {
do_hal net hu:minus-$a <= $::m.fb-minusoffset-$a
do_hal net hu:minus-$a => $::HU($a,fb,pinname)
} ;# new_connect_plus_offset
proc new_connect_fb_to_moveoff {a} {
if $::HU($a,shortcircuit) return
do_hal delsig $::HU($a,fb,signame)
do_hal net hu:fb-$a => $::m.fb-$a
do_hal net hu:fb-$a <= $::HU($a,fb,output)
foreach in_pinname $::HU($a,fb,inputs) {
if {"$in_pinname" == "$::HU($a,fb,pinname)"} continue
do_hal unlinkp $in_pinname
do_hal net hu:fb-$a => $in_pinname
}
} ;# new_connect_fb_to_moveoff
proc reconnect_short_circuit {a} {
do_hal net hu:shortcircuit-$a <= $::m.pos-plusoffset-$a
do_hal net hu:shortcircuit-$a => $::m.fb-$a
foreach in_pinname $::HU($a,pos,inputs) {
if {"$in_pinname" == "$::HU($a,fb,pinname)"} {continue}
do_hal unlinkp $in_pinname
do_hal net hu:shortcircuit-$a => $in_pinname
}
} ;# reconnect_short_circuit
proc set_moveoff_inputs {a} {
foreach {pin ininame} { offset-vel MAX_VELOCITY \
offset-accel MAX_ACCELERATION \
offset-max MAX_LIMIT \
offset-min MIN_LIMIT \
} {
if [info exists ::MOVEOFF_[set a]($ininame)] {
set ::HU($a,$pin) [set ::MOVEOFF_[set a]($ininame)]
# lindex is used in case there are duplicate entries
set ::HU($a,$pin) [lindex $::HU($a,$pin) end]
} elseif { [info exists ::JOINT_[set a]($ininame)] } {
set ::HU($a,$pin) [set ::JOINT_[set a]($ininame)]
# lindex is used in case there are duplicate entries
set ::HU($a,$pin) [lindex $::HU($a,$pin) end]
puts "hookup_moveoff.tcl:use \[JOINT_$a\]$ininame=$::HU($a,$pin)"
}
if [info exists ::HU($a,$pin)] {
do_hal setp $::m.$pin-$a $::HU($a,$pin)
}
}
} ;# set_moveoff_inputs
proc set_moveoff_parms {} {
foreach {pin ininame} { epsilon EPSILON \
waypoint-sample-secs WAYPOINT_SAMPLE_SECS \
waypoint-threshold WAYPOINT_THRESHOLD \
backtrack-enable BACKTRACK_ENABLE \
} {
if {[info exists ::MOVEOFF($ininame)]} {
# lindex is used in case there are duplicate entries
set ::HU($pin) [lindex $::MOVEOFF($ininame) end]
do_hal setp $::m.$pin $::HU($pin)
}
}
} ;# set_moveoff_parms
proc set_moveoff_controls {} {
return
# provision for future offset control connections
} ;# set_moveoff_controls
# begin-----------------------------------------------------------------
set ::m mv ;# moveoff component name
#(must agree with the (optionaL) gui moveoff_gui)
# debugging items
set ::HU(verbose) 0
set ::noexecute 0
# Provision for twopass compatibility
# (::tp is the namespace for [HAL]TWOPASS processing)
if [namespace exists ::tp] {
set passno [::tp::passnumber]
if {$passno == 0} {
# With twopass processing, the initial pass0 only collects
# loadrt and loadusr commands with no execution.
# So the checks etc herein cannot work until pass1
# Note that this file only uses loadrt once for the moveoff
# component so it manages its loadrt exclusively
puts "hookup_moveoff.tcl: twopass active, pass $passno skipped"
return
} else {
puts "hookup_moveoff.tcl: twopass active, pass $passno active"
}
}
if [catch {
set ::HU(cmd) "thread_info ::HU"; eval $::HU(cmd)
set ::HU(cmd) setup_pinnames; eval $::HU(cmd)
set ::HU(cmd) install_moveoff; eval $::HU(cmd)
foreach a $::HU(axes) {
set ::HU(cmd) "disconnect_pos_from_motion $a"; eval $::HU(cmd)
set ::HU(cmd) "disconnect_fb_to_motion $a"; eval $::HU(cmd)
set ::HU(cmd) "check_for_short_circuit $a"; eval $::HU(cmd)
set ::HU(cmd) "new_connect_pos_to_moveoff $a"; eval $::HU(cmd)
set ::HU(cmd) "new_connect_plus_offset $a"; eval $::HU(cmd)
set ::HU(cmd) "new_connect_minus_offset $a"; eval $::HU(cmd)
set ::HU(cmd) "new_connect_fb_to_moveoff $a"; eval $::HU(cmd)
if $::HU($a,shortcircuit) {
set ::HU(cmd) "reconnect_short_circuit $a "; eval $::HU(cmd)
}
set ::HU(cmd) "set_moveoff_inputs $a"; eval $::HU(cmd)
}
set ::HU(cmd) "set_moveoff_parms"; eval $::HU(cmd)
set ::HU(cmd) "set_moveoff_controls"; eval $::HU(cmd)
if $::HU(verbose) {parray ::HU}
} msg ] {
puts "\n\n$::argv0 $::argv"
parray ::HU
catch {puts filename=$filename}
puts "\n failing cmd=$::HU(cmd)"
puts msg=<$msg>
return -code error
}

69
lib/hallib/lathe.hal Normal file
View File

@@ -0,0 +1,69 @@
# counting the spindle encoder in software
loadrt encoder names=encoder_0
# simulate the encoder
loadrt sim_encoder names=sim_encoder_0
loadrt limit2 names=limit_speed
addf limit_speed servo-thread
#######################################################
# Beginning of threading related stuff
#######################################################
# spindle speed control
net spindle-speed-cmd spindle.0.speed-out => limit_speed.in
net spindle-speed-limited limit_speed.out => sim_encoder_0.speed
# simulate spindle mass
setp limit_speed.maxv 500.0 # rpm/second
# spindle encoder
# connect encoder signals to encoder counter
net spindle-phase-A sim_encoder_0.phase-A => encoder_0.phase-A
net spindle-phase-B sim_encoder_0.phase-B => encoder_0.phase-B
net spindle-phase-Z sim_encoder_0.phase-Z => encoder_0.phase-Z
# assume 120 ppr = 480 counts/rev for the spindle
setp sim_encoder_0.ppr 120
# iocontrol output is in rpm, but sim-encoder speed is rps
setp sim_encoder_0.scale 60
# scale encoder output to read in revolutions
# (that way thread pitches can be straightforward,
# a 20 tpi thread would multiply the encoder output
# by 1/20, etc)
setp encoder_0.position-scale 480
# encoder reset control
# hook up motion controller's sync output
net spindle-index-enable spindle.0.index-enable <=> encoder_0.index-enable
# report our revolution count to the motion controller
net spindle-pos encoder_0.position => spindle.0.revs
# for spindle velocity estimate
loadrt lowpass names=lowpass_velocity
loadrt scale names=scale_to_rpm
net spindle-rps-raw encoder_0.velocity lowpass_velocity.in
net spindle-rps-filtered lowpass_velocity.out scale_to_rpm.in spindle.0.speed-in
net spindle-rpm-filtered scale_to_rpm.out
setp scale_to_rpm.gain 60
setp lowpass_velocity.gain .07
addf lowpass_velocity servo-thread
addf scale_to_rpm servo-thread
# for at-speed detection
loadrt near names=near_speed
addf near_speed servo-thread
setp near_speed.scale 1.1
setp near_speed.difference 10
net spindle-speed-cmd => near_speed.in1
net spindle-rpm-filtered => near_speed.in2
net spindle-at-speed near_speed.out spindle.0.at-speed
net spindle-fwd <= spindle.0.forward
addf encoder.capture-position servo-thread
addf sim-encoder.update-speed servo-thread
addf sim-encoder.make-pulses base-thread
addf encoder.update-counters base-thread

668
lib/hallib/lh_chart.tcl Normal file
View File

@@ -0,0 +1,668 @@
# lh_chart.tcl: minimal canvas-based bar chart + window capture helper.
#
#-----------------------------------------------------------------------
# Copyright: 2012-2016 (lh_chart by Dewey Garrett <dgarrett@panix.com>
# and later contributors)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# lh_chart: minimal canvas-based bar chart implementing the subset of
# blt::barchart used by latency-histogram and hal-histogram. Goal:
# visual + behavioral parity without depending on BLT (which has no
# Tcl/Tk 9 port).
#
# Public API (used at $w via command rename + dispatch):
# $w axis configure x|y -min -max -majorticks -logscale -hide -showticks
# $w element create|configure NAME -xdata -ydata -fg -bg -barwidth -stipple
# $w element exists NAME
# $w legend configure -hide 0|1
# Constructor:
# lh_chart::create $w -title T -width W -height H \
# -plotbackground COLOR -cursor C
#-----------------------------------------------------------------------
namespace eval lh_chart {
variable state
variable stipple_map
array set stipple_map {}
}
proc lh_chart::install_stipples {} {
variable stipple_map
if {[info exists stipple_map(_installed)]} return
set dir /tmp/lh_chart_stipples_[pid]
catch {file mkdir $dir}
# Tk's XBM reader is picky: needs static char (signed, not unsigned)
# and the data block in K&R-style continuation form with a trailing
# closing-brace-semicolon on its own line. One-liners are rejected.
foreach {name bits} {
pbmap {0xe3 0xf1 0xf8 0x7c 0x3e 0x1f 0x8f 0xc7}
nbmap {0xc7 0x8f 0x1f 0x3e 0x7c 0xf8 0xf1 0xe3}
} {
set fp [open $dir/$name.xbm w]
puts $fp "#define ${name}_width 8"
puts $fp "#define ${name}_height 8"
puts $fp "static char ${name}_bits\[\] = \{"
puts $fp " [join $bits {, }]\};"
close $fp
set stipple_map($name) "@$dir/$name.xbm"
}
set stipple_map(_installed) 1
}
proc lh_chart::create {w args} {
variable state
install_stipples
array set opts {
-title ""
-width 480
-height 384
-plotbackground honeydew1
-cursor arrow
}
array set opts $args
canvas $w \
-width $opts(-width) \
-height $opts(-height) \
-bg "#d9d9d9" \
-bd 0 -highlightthickness 0 \
-cursor $opts(-cursor)
set state($w,title) $opts(-title)
set state($w,width) $opts(-width)
set state($w,height) $opts(-height)
set state($w,plotbg) $opts(-plotbackground)
set state($w,xmin) -1.0
set state($w,xmax) 1.0
set state($w,ymin) 0.0
set state($w,ymax) 1.0
set state($w,ylogscale) 0
set state($w,xticks) {}
set state($w,elements) {}
set state($w,legend_hide) 1
set state($w,dirty) 1
set state($w,redraw_pending) 0
bind $w <Configure> [list lh_chart::on_configure $w]
rename ::$w ::lh_chart::_orig_$w
proc ::$w {args} "::lh_chart::dispatch $w {*}\$args"
lh_chart::schedule_redraw $w
return $w
}
proc lh_chart::dispatch {w args} {
set sub [lindex $args 0]
set rest [lrange $args 1 end]
switch -- $sub {
axis { return [lh_chart::cmd_axis $w {*}$rest] }
element { return [lh_chart::cmd_element $w {*}$rest] }
legend { return [lh_chart::cmd_legend $w {*}$rest] }
default { return [::lh_chart::_orig_$w {*}$args] }
}
}
proc lh_chart::cmd_axis {w sub which args} {
variable state
if {$sub ne "configure"} { error "lh_chart axis: unsupported subcommand $sub" }
array set opts $args
if {$which eq "x"} {
if {[info exists opts(-min)]} { set state($w,xmin) $opts(-min) }
if {[info exists opts(-max)]} { set state($w,xmax) $opts(-max) }
if {[info exists opts(-majorticks)]} { set state($w,xticks) $opts(-majorticks) }
} elseif {$which eq "y"} {
if {[info exists opts(-logscale)]} { set state($w,ylogscale) $opts(-logscale) }
if {[info exists opts(-min)]} { set state($w,ymin) $opts(-min) }
if {[info exists opts(-max)]} { set state($w,ymax) $opts(-max) }
}
schedule_redraw $w
}
proc lh_chart::cmd_element {w op name args} {
variable state
variable stipple_map
switch -- $op {
exists {
return [expr {[lsearch -exact $state($w,elements) $name] >= 0}]
}
create {
if {[lsearch -exact $state($w,elements) $name] < 0} {
lappend state($w,elements) $name
}
set state($w,el,$name,xdata) {}
set state($w,el,$name,ydata) {}
set state($w,el,$name,fg) black
set state($w,el,$name,bg) lightblue
set state($w,el,$name,barwidth) 1.0
set state($w,el,$name,stipple) {}
cmd_element_apply $w $name $args
}
configure {
cmd_element_apply $w $name $args
}
default { error "lh_chart element: unsupported op $op" }
}
schedule_redraw $w
}
proc lh_chart::cmd_element_apply {w name optlist} {
variable state
variable stipple_map
array set opts $optlist
foreach {k storekey} {
-xdata xdata
-ydata ydata
-fg fg
-bg bg
-barwidth barwidth
} {
if {[info exists opts($k)]} {
set state($w,el,$name,$storekey) $opts($k)
}
}
if {[info exists opts(-stipple)]} {
set s $opts(-stipple)
if {[info exists stipple_map($s)]} { set s $stipple_map($s) }
set state($w,el,$name,stipple) $s
}
if {[info exists opts(-fg)] || [info exists opts(-bg)] \
|| [info exists opts(-stipple)]} {
# Style changed: repaint existing bar items on the next redraw.
set state($w,el,$name,restyle) 1
}
}
proc lh_chart::cmd_legend {w sub args} {
variable state
if {$sub eq "configure"} {
array set opts $args
if {[info exists opts(-hide)]} { set state($w,legend_hide) $opts(-hide) }
}
}
proc lh_chart::on_configure {w} {
variable state
if {![info exists state($w,width)]} return
set state($w,width) [winfo width $w]
set state($w,height) [winfo height $w]
schedule_redraw $w
}
proc lh_chart::schedule_redraw {w} {
variable state
set state($w,dirty) 1
if {$state($w,redraw_pending)} return
set state($w,redraw_pending) 1
after idle [list lh_chart::redraw $w]
}
proc lh_chart::redraw {w} {
variable state
set state($w,redraw_pending) 0
if {![winfo exists $w]} return
if {!$state($w,dirty)} return
set state($w,dirty) 0
set W $state($w,width)
set H $state($w,height)
set ml 55 ; set mr 18 ; set mt 20 ; set mb 42
set pw [expr {$W - $ml - $mr}]
set ph [expr {$H - $mt - $mb}]
if {$pw < 50 || $ph < 50} return
# Data inset: keep off-chart end bars (red-stippled at xmin/xmax)
# visibly inward from the border, matching BLT's plot margin.
set pad 8
set ml_d [expr {$ml + $pad}]
set mt_d [expr {$mt + $pad}]
set pw_d [expr {$pw - 2*$pad}]
set ph_d [expr {$ph - 2*$pad}]
set xmin $state($w,xmin)
set xmax $state($w,xmax)
set xrange [expr {double($xmax - $xmin)}]
if {$xrange == 0} { set xrange 1.0 }
# auto-scale Y from element data
set ymax_data 1.0
foreach name $state($w,elements) {
foreach v $state($w,el,$name,ydata) {
if {$v > $ymax_data} { set ymax_data $v }
}
}
if {$state($w,ylogscale)} {
set ymin 1.0
if {$ymax_data < 10} {
set ymax 10.0
} else {
# Nice ceiling: 1, 2, 5 times the decade — keeps Y range
# tight so a small data growth doesn't jump a full decade.
set decade [expr {pow(10, floor(log10($ymax_data)))}]
set ratio [expr {$ymax_data / $decade}]
if {$ratio <= 1.0} {
set ymax $decade
} elseif {$ratio <= 2.0} {
set ymax [expr {2.0 * $decade}]
} elseif {$ratio <= 5.0} {
set ymax [expr {5.0 * $decade}]
} else {
set ymax [expr {10.0 * $decade}]
}
}
} else {
set ymin 0.0
# Nice ceiling for ymax: pick 1, 2, 2.5, 5, or 10 times a decade
# so the 5 equal ticks become round numbers (e.g. 0, 600, 1200,
# 1800, 2400, 3000) instead of (0, 580, 1160, ...).
if {$ymax_data <= 0} {
set ymax 1.0
} else {
set goal [expr {$ymax_data * 1.05}]
set decade [expr {pow(10, floor(log10($goal)))}]
set ratio [expr {$goal / $decade}]
if {$ratio <= 1.0} {
set ymax $decade
} elseif {$ratio <= 2.0} {
set ymax [expr {2.0 * $decade}]
} elseif {$ratio <= 2.5} {
set ymax [expr {2.5 * $decade}]
} elseif {$ratio <= 5.0} {
set ymax [expr {5.0 * $decade}]
} else {
set ymax [expr {10.0 * $decade}]
}
}
}
set state($w,ymin) $ymin
set state($w,ymax) $ymax
# Tk 9 leaks memory for every deleted canvas item (Tk 8.6 does not),
# so steady-state redraws must not delete anything: bars are
# persistent items repositioned via `coords` (which does not leak),
# and the static scene (gridlines, axes, tick labels) is recreated
# only when the layout signature changes (Y auto-scale, resize).
# Bar items are recreated only when a bar count/stipple structure
# changes. See create_bars/layout/update_bars.
set lsig [list $W $H $xmin $xmax $ymin $ymax $state($w,ylogscale) \
$state($w,xticks) $state($w,title) $state($w,plotbg)]
set bsig {}
foreach name $state($w,elements) {
lappend bsig $name \
[llength $state($w,el,$name,xdata)] \
[expr {$state($w,el,$name,stipple) ne ""}]
}
if {![info exists state($w,layout_sig)] || $lsig ne $state($w,layout_sig)} {
set rebars [expr {![info exists state($w,bar_sig)] \
|| $bsig ne $state($w,bar_sig)}]
set state($w,layout_sig) $lsig
set state($w,bar_sig) $bsig
layout $w $ml $mt $pw $ph $ml_d $mt_d $pw_d $ph_d \
$xmin $xmax $xrange $rebars
} elseif {![info exists state($w,bar_sig)] || $bsig ne $state($w,bar_sig)} {
set state($w,bar_sig) $bsig
create_bars $w
}
update_bars $w $ml_d $mt_d $pw_d $ph_d $xmin $xmax $xrange
}
# Create the persistent bar items for all elements: one rectangle per
# bar (degenerate offscreen coords; update_bars positions them), plus a
# 1 px baseline line per element. Stippled bars get a bg item plus a
# stippled fg overlay item. All items carry the `bars` tag. Bars must
# stack above the gridlines but below the frame/axes/ticks (tag `top`);
# when `top` items already exist the new bars are lowered beneath them
# (during a full layout `top` is drawn after this, same stacking).
proc lh_chart::create_bars {w} {
variable state
set c ::lh_chart::_orig_$w
$c delete bars
foreach name $state($w,elements) {
set state($w,el,$name,restyle) 0
set state($w,el,$name,lastcoords) {}
set fg $state($w,el,$name,fg)
set bg $state($w,el,$name,bg)
set st $state($w,el,$name,stipple)
set ids {}
set n [llength $state($w,el,$name,xdata)]
for {set k 0} {$k < $n} {incr k} {
if {$st ne ""} {
set bgid [$c create rectangle -10 -10 -10 -10 \
-fill $bg -outline $bg -width 0 -tags bars]
set fgid [$c create rectangle -10 -10 -10 -10 \
-fill $fg -outline $fg -width 0 -stipple $st -tags bars]
lappend ids [list $bgid $fgid]
} else {
lappend ids [list [$c create rectangle -10 -10 -10 -10 \
-fill $fg -outline $fg -width 0 -tags bars]]
}
}
set state($w,el,$name,barids) $ids
# Continuous baseline: 1 px line in the element's fg color along
# the bottom of the data area, so the bottom doesn't look broken
# where bins have zero counts. Coords set by update_bars.
set state($w,el,$name,baseid) \
[$c create line -10 -10 -10 -10 -fill $fg -tags bars]
}
if {[llength [$c find withtag top]] > 0} {
$c lower bars top
}
}
# Rebuild the static scene (background, gridlines, frame, axes, tick
# labels): delete `static`-tagged items and redraw them. Persistent
# bars are kept unless $rebars (bar count/structure changed) and are
# re-stacked between gridlines and frame. Called on layout-signature
# change; see redraw().
proc lh_chart::layout {w ml mt pw ph ml_d mt_d pw_d ph_d xmin xmax xrange rebars} {
variable state
set c ::lh_chart::_orig_$w
$c delete static
set ymin $state($w,ymin)
set ymax $state($w,ymax)
# plot area background (no border yet, axis lines drawn last)
$c create rectangle $ml $mt [expr {$ml+$pw}] [expr {$mt+$ph}] \
-fill $state($w,plotbg) -outline "" -tags static
# title
if {$state($w,title) ne ""} {
$c create text [expr {$ml + $pw/2}] [expr {$mt - 9}] \
-text $state($w,title) -anchor center -font {Helvetica -12} \
-tags static
}
# Y axis: build tick lists + draw gridlines now (ticks/labels at end).
# Major ticks get a label and a long tick line; minor ticks at every
# sub-decade gridline get a short tick line only (matches BLT).
set y_ticks {} ;# list of {value label} — major
set y_minor_ticks {} ;# list of values — minor
if {$state($w,ylogscale)} {
# Minor gridlines at 2..9 within each decade
set d 1.0
while {$d < $ymax + 0.1} {
for {set k 2} {$k <= 9} {incr k} {
set v [expr {$d * $k}]
if {$v > $ymax + 0.1} break
set y [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax $v]
$c create line $ml $y [expr {$ml+$pw}] $y \
-fill gray70 -dash {1 1} -tags static
lappend y_minor_ticks $v
}
set d [expr {$d * 10}]
}
# Major gridlines at each decade
set d 1.0
set exp 0
while {$d <= $ymax + 0.001} {
set y [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax $d]
$c create line $ml $y [expr {$ml+$pw}] $y -fill gray70 -dash {1 1} \
-tags static
lappend y_ticks [list $d "1E$exp"]
set d [expr {$d * 10}]
incr exp
}
# Cap tick at ymax if it sits between decades (2x or 5x of decade)
set top_decade [expr {pow(10, floor(log10($ymax) + 1e-9))}]
set top_ratio [expr {$ymax / $top_decade}]
if {$top_ratio > 1.5} {
set top_exp [expr {int(floor(log10($ymax) + 1e-9))}]
set top_mant [expr {int(round($top_ratio))}]
lappend y_ticks [list $ymax "${top_mant}E$top_exp"]
}
} else {
set steps 5
for {set i 0} {$i <= $steps} {incr i} {
set v [expr {$ymin + ($ymax - $ymin) * $i / double($steps)}]
set y [lh_chart::ymap_lin $mt_d $ph_d $ymin $ymax $v]
$c create line $ml $y [expr {$ml+$pw}] $y -fill gray80 -dash {2 2} \
-tags static
lappend y_ticks [list $v [lh_chart::fmt_num $v]]
}
}
# Bars stack above the background + gridlines drawn so far: raise
# the persistent bars back, or recreate them on a structure change
# (new items land on top, which is the right level at this point).
if {$rebars} {
create_bars $w
} else {
$c raise bars static
}
# Plot frame: 3D raised look. Only TOP and LEFT have a black outline
# (the lit edges); BOTTOM and RIGHT are left without an outer black
# line. Inside, top+left have a darker shadow line and bottom+right
# a lighter highlight, giving the panel-edge relief BLT used.
# Frame, axes and ticks stay on top of the bars; the `top` tag lets
# create_bars lower later-recreated bars back beneath them.
set xR [expr {$ml+$pw}]
set yB [expr {$mt+$ph}]
$c create line $ml $mt $xR $mt -fill black -tags {static top}
$c create line $ml $mt $ml $yB -fill black -tags {static top}
$c create line [expr {$ml+1}] [expr {$mt+1}] [expr {$xR-1}] [expr {$mt+1}] \
-fill gray45 -tags {static top}
$c create line [expr {$ml+1}] [expr {$mt+1}] [expr {$ml+1}] [expr {$yB-1}] \
-fill gray45 -tags {static top}
$c create line [expr {$ml+1}] [expr {$yB-1}] [expr {$xR-1}] [expr {$yB-1}] \
-fill white -tags {static top}
$c create line [expr {$xR-1}] [expr {$mt+1}] [expr {$xR-1}] [expr {$yB-1}] \
-fill white -tags {static top}
# Axis line: separate black line OUTSIDE the plot border, with a
# small gap between them. Spans only the data-inset range so its
# endpoints sit right at the topmost and bottommost ticks (BLT
# behavior — the axis "ends with the last tick").
set axis_gap 4
set tick_long 10
set tick_short 5
set axis_x [expr {$ml - $axis_gap}] ;# left axis (Y)
set axis_y [expr {$yB + $axis_gap}] ;# bottom axis (X)
set axis_top $mt_d ;# = $mt + pad
set axis_bottom [expr {$mt_d + $ph_d}]
set axis_left $ml_d ;# = $ml + pad
set axis_right [expr {$ml_d + $pw_d}]
$c create line $axis_x $axis_top $axis_x $axis_bottom -fill black \
-tags {static top}
$c create line $axis_left $axis_y $axis_right $axis_y -fill black \
-tags {static top}
# Tick marks attach to (touch) the axis line and point OUTWARD
# toward the labels. Major ticks long, minor ticks (Y only) short.
foreach v $y_minor_ticks {
set y [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax $v]
$c create line [expr {$axis_x - $tick_short}] $y $axis_x $y \
-fill black -tags {static top}
}
foreach pair $y_ticks {
lassign $pair v label
if {$state($w,ylogscale)} {
set y [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax $v]
} else {
set y [lh_chart::ymap_lin $mt_d $ph_d $ymin $ymax $v]
}
$c create line [expr {$axis_x - $tick_long}] $y $axis_x $y \
-fill black -tags {static top}
$c create text [expr {$axis_x - $tick_long - 2}] $y \
-text $label -anchor e -font {Helvetica -10} -tags {static top}
}
set xticks $state($w,xticks)
if {[llength $xticks] == 0} {
set xticks [list $xmin [expr {($xmin+$xmax)/2.0}] $xmax]
}
foreach t $xticks {
if {$t < $xmin - 1e-9 || $t > $xmax + 1e-9} continue
set x [lh_chart::xmap $ml_d $pw_d $xmin $xrange $t]
$c create line $x $axis_y $x [expr {$axis_y + $tick_long}] \
-fill black -tags {static top}
$c create text $x [expr {$axis_y + $tick_long + 2}] \
-text [format %g $t] -anchor n -font {Helvetica -10} \
-tags {static top}
}
}
# Position the persistent bar items from current element data. Pure
# `coords` (+ rare `itemconfigure` on style change) — no item deletion,
# so this is safe to run at the full data update rate on Tk 9.
proc lh_chart::update_bars {w ml_d mt_d pw_d ph_d xmin xmax xrange} {
variable state
set c ::lh_chart::_orig_$w
set ymin $state($w,ymin)
set ymax $state($w,ymax)
set y0 [expr {$state($w,ylogscale) \
? [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax 1.0] \
: [lh_chart::ymap_lin $mt_d $ph_d $ymin $ymax 0.0]}]
foreach name $state($w,elements) {
set xd $state($w,el,$name,xdata)
set yd $state($w,el,$name,ydata)
set bw $state($w,el,$name,barwidth)
set hbw [expr {$bw / 2.0}]
# Baseline follows the layout (y0), not the data, so update it
# every pass. An element with no data has no baseline.
if {[llength $xd] > 0} {
$c coords $state($w,el,$name,baseid) \
[lh_chart::xmap $ml_d $pw_d $xmin $xrange $xmin] $y0 \
[lh_chart::xmap $ml_d $pw_d $xmin $xrange $xmax] $y0
} else {
$c coords $state($w,el,$name,baseid) -10 -10 -10 -10
}
# One coord tuple per bar; zero/out-of-range bars get degenerate
# offscreen coords so the item count stays constant.
set newcoords {}
foreach x $xd y $yd {
if {$y <= 0} {
lappend newcoords {-10 -10 -10 -10}
continue
}
if {$state($w,ylogscale) && $y < $ymin} {
lappend newcoords {-10 -10 -10 -10}
continue
}
set xa [expr {$x - $hbw}]
set xb [expr {$x + $hbw}]
if {$xb < $xmin || $xa > $xmax} {
lappend newcoords {-10 -10 -10 -10}
continue
}
if {$xa < $xmin} { set xa $xmin }
if {$xb > $xmax} { set xb $xmax }
set pxa [lh_chart::xmap $ml_d $pw_d $xmin $xrange $xa]
set pxb [lh_chart::xmap $ml_d $pw_d $xmin $xrange $xb]
# Pixel-snap so sub-pixel bars (e.g. 0.1us bins at ~1.2 px each)
# always paint at least one full pixel and adjacent bars touch.
set pxa [expr {int(floor($pxa))}]
set pxb [expr {int(ceil($pxb))}]
if {$pxb <= $pxa} { set pxb [expr {$pxa + 1}] }
# Off-chart (stippled) end-of-range bars: minimum 2 px so the
# stipple pattern is actually visible and matches BLT.
if {$state($w,el,$name,stipple) ne "" && [expr {$pxb - $pxa}] < 2} {
set pxb [expr {$pxa + 2}]
}
if {$state($w,ylogscale)} {
set py [lh_chart::ymap_log $mt_d $ph_d $ymin $ymax $y]
} else {
set py [lh_chart::ymap_lin $mt_d $ph_d $ymin $ymax $y]
}
lappend newcoords [list $pxa $py $pxb $y0]
}
set ids $state($w,el,$name,barids)
# Skip the canvas calls when coords and style are unchanged
# (most redraws change nothing: zero bins stay zero).
if {[info exists state($w,el,$name,lastcoords)] \
&& $state($w,el,$name,lastcoords) eq $newcoords \
&& !$state($w,el,$name,restyle)} {
continue
}
set state($w,el,$name,lastcoords) $newcoords
if {$state($w,el,$name,restyle)} {
set state($w,el,$name,restyle) 0
set fg $state($w,el,$name,fg)
set bg $state($w,el,$name,bg)
set st $state($w,el,$name,stipple)
foreach entry $ids {
if {[llength $entry] == 2} {
lassign $entry bgid fgid
$c itemconfigure $bgid -fill $bg -outline $bg
$c itemconfigure $fgid -fill $fg -outline $fg -stipple $st
} else {
$c itemconfigure [lindex $entry 0] -fill $fg -outline $fg
}
}
}
foreach entry $ids co $newcoords {
foreach id $entry {
$c coords $id {*}$co
}
}
}
}
proc lh_chart::fmt_num {v} {
# Format a number for axis labels. Switch to sci notation (e.g.
# 2E5, 1.5E6) once we'd otherwise need 5+ digits, so labels stay
# narrow enough to fit in the left margin.
if {$v == 0} { return "0" }
set av [expr {abs($v)}]
if {$av >= 10000 || $av < 0.01} {
set exp [expr {int(floor(log10($av) + 1e-9))}]
set mant [expr {$v / pow(10, $exp)}]
if {abs($mant - round($mant)) < 0.05} {
return [format "%dE%d" [expr {int(round($mant))}] $exp]
} else {
return [format "%.1fE%d" $mant $exp]
}
}
if {$v == int($v)} { return [format %.0f $v] }
return [format %g $v]
}
proc lh_chart::xmap {ml pw xmin xrange v} {
return [expr {$ml + ($v - $xmin) / $xrange * $pw}]
}
proc lh_chart::ymap_lin {mt ph ymin ymax v} {
set r [expr {$ymax - $ymin}]
if {$r == 0} { set r 1 }
return [expr {$mt + $ph - ($v - $ymin) / double($r) * $ph}]
}
proc lh_chart::ymap_log {mt ph ymin ymax v} {
if {$v < $ymin} { set v $ymin }
set lmin [expr {log10($ymin)}]
set lmax [expr {log10($ymax)}]
set r [expr {$lmax - $lmin}]
if {$r == 0} { set r 1 }
set lv [expr {log10($v)}]
return [expr {$mt + $ph - ($lv - $lmin) / $r * $ph}]
}
#------------------------------------------------------------------
# Window capture without BLT. Strategy:
# 1. Tk 8.7+/9: image create photo -format window can grab a window.
# 2. Else: use ImageMagick `import -window <id>` to a temp PNG.
# Returns a tk photo image; caller is responsible for `image delete`.
proc lh_chart::capture_window {win} {
update idletasks
set img [image create photo]
if {![catch {$img read $win -format window} err]} {
return $img
}
image delete $img
set wid [winfo id $win]
set tmp /tmp/lh_snap_[pid]_[clock clicks].png
if {[catch {exec import -window $wid $tmp} err]} {
catch {file delete $tmp}
error "screenshot needs ImageMagick (apt install imagemagick): $err"
}
set img [image create photo -file $tmp]
catch {file delete $tmp}
return $img
}
#------------------------------------------------------------------

View File

@@ -0,0 +1,11 @@
# simulate a locking rotary axis B
loadrt timedelay names=timedelay_unlock
addf timedelay_unlock servo-thread
net B-unlock joint.4.unlock => timedelay_unlock.in
net B-is-unlocked timedelay_unlock.out => joint.4.is-unlocked
setp timedelay_unlock.on-delay 0.75
setp timedelay_unlock.off-delay 0.5

View File

@@ -0,0 +1,28 @@
# moveoff_external.hal (for use with moveoff_gui)
# This halfile demonstrates how a connection to
# mv.move-enable
# allows moveoff_gui to interface with other connections that control:
# mv.move-enable
# mv.offset-in-M
# This halfile should follow halfiles which loadrt the moveoff component
#-----------------------------------------------------------------------
# Overall enable for the component:
# 0 ==> external control of mv.move-enable required
setp mv.move-enable 0
# When the mv.move-enable pin is connected to a signal, moveoff_gui
# will provide display but no controls.
# An external app must connect the mv.move-enable pin to control:
net external_enable mv.move-enable
# An external app must connect to the mv.offset-in-M pins to control:
net external_offset_0 mv.offset-in-0
net external_offset_1 mv.offset-in-1
net external_offset_2 mv.offset-in-2
# An external app may optionally connect the mv.backtrack-enable pin
# to manage backtracking:
net external_backtrack_en mv.backtrack-enable

80
lib/hallib/plasmac.tcl Normal file
View File

@@ -0,0 +1,80 @@
# ***** THIS FILE IS USED ONLY BY CONFIGS THAT HAVE BEEN MIGRATED FROM A PLASMAC CONFIGURATION *****
# common connections for the plasmac component
# PLASMAC COMPONENT ##########################################################
loadrt plasmac
addf plasmac servo-thread
# COMPONENT INPUTS ###########################################################
net plasmac:axis-x-position axis.x.pos-cmd => plasmac.axis-x-position
net plasmac:axis-y-position axis.y.pos-cmd => plasmac.axis-y-position
net plasmac:current-velocity motion.current-vel => plasmac.current-velocity
net plasmac:cutting-start spindle.0.on => plasmac.cutting-start
net plasmac:feed-override halui.feed-override.value => plasmac.feed-override
net plasmac:feed-reduction motion.analog-out-03 => plasmac.feed-reduction
net plasmac:feed-upm motion.feed-upm => plasmac.feed-upm
net plasmac:homed motion.is-all-homed => plasmac.homed
net plasmac:ignore-arc-ok-0 motion.digital-out-01 => plasmac.ignore-arc-ok-0
net plasmac:motion-type motion.motion-type => plasmac.motion-type
net plasmac:offsets-active motion.eoffset-active => plasmac.offsets-active
net plasmac:offsets-limited motion.eoffset-limited => plasmac.offsets-limited
net plasmac:program-is-idle halui.program.is-idle => plasmac.program-is-idle
net plasmac:program-is-paused halui.program.is-paused => plasmac.program-is-paused
net plasmac:program-is-running halui.program.is-running => plasmac.program-is-running
net plasmac:thc-disable motion.digital-out-02 => plasmac.thc-disable
net plasmac:torch-off motion.digital-out-03 => plasmac.torch-off
net plasmac:units-per-mm halui.machine.units-per-mm => plasmac.units-per-mm
net plasmac:x-offset-current axis.x.eoffset => plasmac.x-offset-current
net plasmac:y-offset-current axis.y.eoffset => plasmac.y-offset-current
net plasmac:z-offset-current axis.z.eoffset => plasmac.z-offset-current
# use existing machine-is-on signal from pncconf if it exists
if {[hal list sig machine-is-on] != {}} {
net machine-is-on => plasmac.machine-is-on
} else {
net machine-is-on halui.machine.is-on => plasmac.machine-is-on
}
# if no new dbounce then use old debounce component for legacy plasmac conversions
if {[hal list pin db_float.out] != {}} {
net plasmac:arc-ok db_arc-ok.out => plasmac.arc-ok-in
net plasmac:breakaway-switch-out db_breakaway.out => plasmac.breakaway
net plasmac:float-switch-out db_float.out => plasmac.float-switch
net plasmac:ohmic-probe-out db_ohmic.out => plasmac.ohmic-probe
} else {
net plasmac:breakaway-switch-out debounce.0.1.out => plasmac.breakaway
net plasmac:float-switch-out debounce.0.0.out => plasmac.float-switch
net plasmac:ohmic-probe-out debounce.0.2.out => plasmac.ohmic-probe
}
# COMPONENT OUTPUTS ##########################################################
net plasmac:adaptive-feed plasmac.adaptive-feed => motion.adaptive-feed
net plasmac:cutting-stop halui.spindle.0.stop => plasmac.cutting-stop
net plasmac:feed-hold plasmac.feed-hold => motion.feed-hold
net plasmac:jog-inhibit plasmac.jog-inhibit => motion.jog-inhibit motion.jog-stop
net plasmac:offset-scale plasmac.offset-scale => axis.x.eoffset-scale axis.y.eoffset-scale axis.z.eoffset-scale
net plasmac:program-pause plasmac.program-pause => halui.program.pause
net plasmac:program-resume plasmac.program-resume => halui.program.resume
net plasmac:program-run plasmac.program-run => halui.program.run
net plasmac:program-stop plasmac.program-stop => halui.program.stop
net plasmac:torch-on plasmac.torch-on
net plasmac:x-offset-counts plasmac.x-offset-counts => axis.x.eoffset-counts
net plasmac:y-offset-counts plasmac.y-offset-counts => axis.y.eoffset-counts
net plasmac:xy-offset-enable plasmac.xy-offset-enable => axis.x.eoffset-enable axis.y.eoffset-enable
net plasmac:z-offset-counts plasmac.z-offset-counts => axis.z.eoffset-counts
net plasmac:z-offset-enable plasmac.z-offset-enable => axis.z.eoffset-enable
# multiple spindles
if [info exists ::TRAJ(SPINDLES)] {
set num_spindles [lindex $::TRAJ(SPINDLES) 0]
if {$num_spindles > 1} {net plasmac:scribe-start spindle.1.on => plasmac.scribe-start}
if {$num_spindles > 2} {net plasmac:spotting-start spindle.2.on => plasmac.spotting-start}
}
# powermax serial communications
# for qtplasmac
if [info exists ::QTPLASMAC(PM_PORT)] {loadusr -Wn pmx485 pmx485 [lindex $::QTPLASMAC(PM_PORT) 0]}
# for plasmac
if [info exists ::PLASMAC(PM_PORT)] {loadusr -Wn pmx485 plasmac/pmx485.py [lindex $::PLASMAC(PM_PORT) 0]}

View File

@@ -0,0 +1,52 @@
# ***** plasmac component connections for a QtPlasmaC configuration *****
# do not make any changes to this file, it may be overwritten by future updates
# make all customizations in custom.hal or custom_postgui.hal
# ---PLASMAC COMPONENT INPUTS---
net plasmac:arc-ok db_arc-ok.out => plasmac.arc-ok-in
net plasmac:axis-x-position axis.x.pos-cmd => plasmac.axis-x-position
net plasmac:axis-y-position axis.y.pos-cmd => plasmac.axis-y-position
net plasmac:breakaway-switch-out db_breakaway.out => plasmac.breakaway
net plasmac:current-velocity motion.current-vel => plasmac.current-velocity
net plasmac:cutting-start spindle.0.on => plasmac.cutting-start
net plasmac:cutting-stop halui.spindle.0.stop => plasmac.cutting-stop
net plasmac:feed-override halui.feed-override.value => plasmac.feed-override
net plasmac:feed-reduction motion.analog-out-03 => plasmac.feed-reduction
net plasmac:float-switch-out db_float.out => plasmac.float-switch
net plasmac:feed-upm motion.feed-upm => plasmac.feed-upm
net plasmac:homed motion.is-all-homed => plasmac.homed
net plasmac:ignore-arc-ok-0 motion.digital-out-01 => plasmac.ignore-arc-ok-0
net machine-is-on halui.machine.is-on => plasmac.machine-is-on
net plasmac:motion-type motion.motion-type => plasmac.motion-type
net plasmac:offsets-active motion.eoffset-active => plasmac.offsets-active
net plasmac:ohmic-probe-out db_ohmic.out => plasmac.ohmic-probe
net plasmac:program-is-idle halui.program.is-idle => plasmac.program-is-idle
net plasmac:program-is-paused halui.program.is-paused => plasmac.program-is-paused
net plasmac:program-is-running halui.program.is-running => plasmac.program-is-running
net plasmac:scribe-start spindle.1.on => plasmac.scribe-start
net plasmac:spotting-start spindle.2.on => plasmac.spotting-start
net plasmac:thc-disable motion.digital-out-02 => plasmac.thc-disable
net plasmac:torch-off motion.digital-out-03 => plasmac.torch-off
net plasmac:units-per-mm halui.machine.units-per-mm => plasmac.units-per-mm
net plasmac:x-offset-current axis.x.eoffset => plasmac.x-offset-current
net plasmac:y-offset-current axis.y.eoffset => plasmac.y-offset-current
net plasmac:z-offset-current axis.z.eoffset => plasmac.z-offset-current
# ---PLASMAC COMPONENT OUTPUTS---
net plasmac:adaptive-feed plasmac.adaptive-feed => motion.adaptive-feed
net plasmac:feed-hold plasmac.feed-hold => motion.feed-hold
net plasmac:jog-inhibit plasmac.jog-inhibit => motion.jog-inhibit motion.jog-stop
net plasmac:offset-scale plasmac.offset-scale => axis.x.eoffset-scale axis.y.eoffset-scale axis.z.eoffset-scale
net plasmac:probe-out plasmac.probe-out => motion.probe-input
net plasmac:program-pause plasmac.program-pause => halui.program.pause
net plasmac:program-resume plasmac.program-resume => halui.program.resume
net plasmac:program-run plasmac.program-run => halui.program.run
net plasmac:program-stop plasmac.program-stop => halui.program.stop
net plasmac:torch-on plasmac.torch-on
net plasmac:x-offset-counts plasmac.x-offset-counts => axis.x.eoffset-counts
net plasmac:y-offset-counts plasmac.y-offset-counts => axis.y.eoffset-counts
net plasmac:xy-offset-enable plasmac.xy-offset-enable => axis.x.eoffset-enable axis.y.eoffset-enable
net plasmac:z-offset-counts plasmac.z-offset-counts => axis.z.eoffset-counts
net plasmac:z-offset-enable plasmac.z-offset-enable => axis.z.eoffset-enable

285
lib/hallib/servo_sim.hal Normal file
View File

@@ -0,0 +1,285 @@
# HAL config file for simulated servo machine
# first load all the RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT base_period_nsec=[EMCMOT]BASE_PERIOD servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
# PID module, for three PID loops
loadrt pid names=pid_x,pid_y,pid_z
# 6 differentiators (for velocity and accel sigs)
loadrt ddt names=ddt_x,ddt_xv,ddt_y,ddt_yv,ddt_z,ddt_zv
# three scale blocks (to simulate motor and leadscrew scaling)
loadrt scale names=scale_x,scale_y,scale_z
# three lowpass filters (to simulate motor inertia), and nine
loadrt lowpass names=lowpass_x,lowpass_y,lowpass_z
# window comparators (to simulate limit and home switches)
loadrt wcomp names=wcomp_xneg,wcomp_xpos,wcomp_xhome,wcomp_yneg,wcomp_ypos,wcomp_yhome,wcomp_zneg,wcomp_zpos,wcomp_zhome
# simulated encoders
loadrt sim_encoder names=sim_encoder_px,sim_encoder_py,sim_encoder_pz
# software encoder counters, 3 for feedback, 3 for actual axis pos
loadrt encoder names=encoder_px,encoder_py,encoder_pz,encoder_x,encoder_y,encoder_z
# add encoder counter and simulator functions to high speed thread
addf sim-encoder.make-pulses base-thread
addf encoder.update-counters base-thread
# add all required functions to servo thread
addf encoder.capture-position servo-thread
addf wcomp_xneg servo-thread
addf wcomp_xpos servo-thread
addf wcomp_xhome servo-thread
addf wcomp_yneg servo-thread
addf wcomp_ypos servo-thread
addf wcomp_yhome servo-thread
addf wcomp_zneg servo-thread
addf wcomp_zpos servo-thread
addf wcomp_zhome servo-thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
addf pid_x.do-pid-calcs servo-thread
addf pid_y.do-pid-calcs servo-thread
addf pid_z.do-pid-calcs servo-thread
addf scale_x servo-thread
addf scale_y servo-thread
addf scale_z servo-thread
addf lowpass_x servo-thread
addf lowpass_y servo-thread
addf lowpass_z servo-thread
addf sim-encoder.update-speed servo-thread
# link the differentiator functions into the code
addf ddt_x servo-thread
addf ddt_xv servo-thread
addf ddt_y servo-thread
addf ddt_yv servo-thread
addf ddt_z servo-thread
addf ddt_zv servo-thread
# get position feedback from encoder module
# connect position feedback to PID loop and motion module
net Xpos-fb encoder_px.position => pid_x.feedback joint.0.motor-pos-fb
net Ypos-fb encoder_py.position => pid_y.feedback joint.1.motor-pos-fb
net Zpos-fb encoder_pz.position => pid_z.feedback joint.2.motor-pos-fb
# set position feedback scaling
setp encoder_px.position-scale [JOINT_0]INPUT_SCALE
setp encoder_py.position-scale [JOINT_1]INPUT_SCALE
setp encoder_pz.position-scale [JOINT_2]INPUT_SCALE
# connect encoder index-enables for homing on index
net Xindex-enable encoder_px.index-enable <=> joint.0.index-enable pid_x.index-enable
net Yindex-enable encoder_py.index-enable <=> joint.1.index-enable
net Zindex-enable encoder_pz.index-enable <=> joint.2.index-enable
# connect position commands from motion controller to PID input
net Xpos-cmd <= joint.0.motor-pos-cmd => pid_x.command
net Ypos-cmd <= joint.1.motor-pos-cmd => pid_y.command
net Zpos-cmd <= joint.2.motor-pos-cmd => pid_z.command
# connect motion controller enables to PID blocks
net Xenable joint.0.amp-enable-out => pid_x.enable
net Yenable joint.1.amp-enable-out => pid_y.enable
net Zenable joint.2.amp-enable-out => pid_z.enable
# connect PID loops to scale blocks that translate to motor revs per sec
net Xoutput pid_x.output => scale_x.in
net Youtput pid_y.output => scale_y.in
net Zoutput pid_z.output => scale_z.in
# set scaling, number of motor revs needed to
# travel one inch
setp scale_x.gain [JOINT_0]DRIVE_RATIO
setp scale_y.gain [JOINT_1]DRIVE_RATIO
setp scale_z.gain [JOINT_2]DRIVE_RATIO
# motor speed command sigs come from scale blocks
# motor speed commands go thru lowpass filters
# to simulate motor inertia
net Xmtr-cmd scale_x.out => lowpass_x.in
net Ymtr-cmd scale_y.out => lowpass_y.in
net Zmtr-cmd scale_z.out => lowpass_z.in
# set "inertia" here, probably by trial and error
setp lowpass_x.gain 0.1
setp lowpass_y.gain 0.1
setp lowpass_z.gain 0.1
# "actual" motor speed signals
# output of lowpass is simulated motor speed
# speed goes to simulated encoders
net Xmtr-spd lowpass_x.out => sim_encoder_px.speed
net Ymtr-spd lowpass_y.out => sim_encoder_py.speed
net Zmtr-spd lowpass_z.out => sim_encoder_pz.speed
# set simulated encoder scaling
setp sim_encoder_px.ppr [JOINT_0]MOTOR_PPR
setp sim_encoder_py.ppr [JOINT_1]MOTOR_PPR
setp sim_encoder_pz.ppr [JOINT_2]MOTOR_PPR
# simulated encoder output signals
# connect them up
net XphA sim_encoder_px.phase-A => encoder_px.phase-A
net XphB sim_encoder_px.phase-B => encoder_px.phase-B
net XphZ sim_encoder_px.phase-Z => encoder_px.phase-Z
net YphA sim_encoder_py.phase-A => encoder_py.phase-A
net YphB sim_encoder_py.phase-B => encoder_py.phase-B
net YphZ sim_encoder_py.phase-Z => encoder_py.phase-Z
net ZphA sim_encoder_pz.phase-A => encoder_pz.phase-A
net ZphB sim_encoder_pz.phase-B => encoder_pz.phase-B
net ZphZ sim_encoder_pz.phase-Z => encoder_pz.phase-Z
# set PID loop output limits to max velocity
setp pid_x.maxoutput [JOINT_0]MAX_VELOCITY
setp pid_y.maxoutput [JOINT_1]MAX_VELOCITY
setp pid_z.maxoutput [JOINT_2]MAX_VELOCITY
# set PID loop gains
# NOTE: eventually these will be non-zero values as
# needed to tune the performance of each axis. The
# initial values shown here are extremely conservative
# to prevent unexpected behavior. After this file
# has been "executed" by halcmd, the gains can be
# interactively adjusted using commands like
# "halcmd setp pid.<channel>.Pgain <value>"
# Once the axis has been tuned to your satisfaction,
# do "halcmd show param | grep pid" to get a listing
# of the tuning parameters, and enter those values here.
# the values below come from the INI
setp pid_x.Pgain [JOINT_0]PGAIN
setp pid_x.Igain [JOINT_0]IGAIN
setp pid_x.Dgain [JOINT_0]DGAIN
setp pid_x.bias [JOINT_0]BIAS
setp pid_x.FF0 [JOINT_0]FF0
setp pid_x.FF1 [JOINT_0]FF1
setp pid_x.FF2 [JOINT_0]FF2
# deadband should be just over 1 count
setp pid_x.deadband [JOINT_0]DEADBAND
setp pid_y.Pgain [JOINT_1]PGAIN
setp pid_y.Igain [JOINT_1]IGAIN
setp pid_y.Dgain [JOINT_1]DGAIN
setp pid_y.bias [JOINT_1]BIAS
setp pid_y.FF0 [JOINT_1]FF0
setp pid_y.FF1 [JOINT_1]FF1
setp pid_y.FF2 [JOINT_1]FF2
# deadband should be just over 1 count
setp pid_y.deadband [JOINT_1]DEADBAND
setp pid_z.Pgain [JOINT_2]PGAIN
setp pid_z.Igain [JOINT_2]IGAIN
setp pid_z.Dgain [JOINT_2]DGAIN
setp pid_z.bias [JOINT_2]BIAS
setp pid_z.FF0 [JOINT_2]FF0
setp pid_z.FF1 [JOINT_2]FF1
setp pid_z.FF2 [JOINT_2]FF2
# deadband should be just over 1 count
setp pid_z.deadband [JOINT_2]DEADBAND
# send the position commands thru differentiators to
# generate velocity and accel signals
net Xvel ddt_x.out => ddt_xv.in
net Xacc <= ddt_xv.out
net Yvel ddt_y.out => ddt_yv.in
net Yacc <= ddt_yv.out
net Zvel ddt_z.out => ddt_zv.in
net Zacc <= ddt_zv.out
# estop loopback
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed
net xflt => joint.0.amp-fault-in
net yflt => joint.1.amp-fault-in
net zflt => joint.2.amp-fault-in
# a second set of encoder counters keeps track of position
net XphA => encoder_x.phase-A
net XphB => encoder_x.phase-B
net YphA => encoder_y.phase-A
net YphB => encoder_y.phase-B
net ZphA => encoder_z.phase-A
net ZphB => encoder_z.phase-B
setp encoder_x.position-scale [JOINT_0]INPUT_SCALE
setp encoder_y.position-scale [JOINT_1]INPUT_SCALE
setp encoder_z.position-scale [JOINT_2]INPUT_SCALE
# connect "actual" position from encoders
# to window comparators
net Xaxis-pos encoder_x.position => wcomp_xneg.in wcomp_xpos.in wcomp_xhome.in
net Yaxis-pos encoder_y.position => wcomp_yneg.in wcomp_ypos.in wcomp_yhome.in
net Zaxis-pos encoder_z.position => wcomp_zneg.in wcomp_zpos.in wcomp_zhome.in
# connect simulated switch outputs to motion controller
net Xminlim wcomp_xneg.out => joint.0.neg-lim-sw-in
net Xmaxlim wcomp_xpos.out => joint.0.pos-lim-sw-in
net Xhome wcomp_xhome.out => joint.0.home-sw-in
net Yminlim wcomp_yneg.out => joint.1.neg-lim-sw-in
net Ymaxlim wcomp_ypos.out => joint.1.pos-lim-sw-in
net Yhome wcomp_yhome.out => joint.1.home-sw-in
net Zminlim wcomp_zneg.out => joint.2.neg-lim-sw-in
net Zmaxlim wcomp_zpos.out => joint.2.pos-lim-sw-in
net Zhome wcomp_zhome.out => joint.2.home-sw-in
# configure the points at which the simulated switches trip
# X axis first
# min limit switch
setp wcomp_xneg.max [JOINT_0]MIN_HARD_LIMIT
setp wcomp_xneg.min [JOINT_0]MIN_HARD_LIMIT_RELEASE
# max limit switch
setp wcomp_xpos.min [JOINT_0]MAX_HARD_LIMIT
setp wcomp_xpos.max [JOINT_0]MAX_HARD_LIMIT_RELEASE
# home switch
setp wcomp_xhome.min [JOINT_0]HOME_SW_MIN
setp wcomp_xhome.max [JOINT_0]HOME_SW_MAX
# Y axis
# min limit switch
setp wcomp_yneg.max [JOINT_1]MIN_HARD_LIMIT
setp wcomp_yneg.min [JOINT_1]MIN_HARD_LIMIT_RELEASE
# max limit switch
setp wcomp_ypos.min [JOINT_1]MAX_HARD_LIMIT
setp wcomp_ypos.max [JOINT_1]MAX_HARD_LIMIT_RELEASE
# home switch
setp wcomp_yhome.min [JOINT_1]HOME_SW_MIN
setp wcomp_yhome.max [JOINT_1]HOME_SW_MAX
# Z axis
# min limit switch
setp wcomp_zneg.max [JOINT_2]MIN_HARD_LIMIT
setp wcomp_zneg.min [JOINT_2]MIN_HARD_LIMIT_RELEASE
# max limit switch
setp wcomp_zpos.min [JOINT_2]MAX_HARD_LIMIT
setp wcomp_zpos.max [JOINT_2]MAX_HARD_LIMIT_RELEASE
# home switch
setp wcomp_zhome.min [JOINT_2]HOME_SW_MIN
setp wcomp_zhome.max [JOINT_2]HOME_SW_MAX
# Configure fake probing
loadrt sphereprobe names=sphereprobe
addf sphereprobe base-thread 2
setp sphereprobe.cx -2811 # this is where it ends up after homing
setp sphereprobe.cz -6000 # this is where it ends up after homing
setp sphereprobe.r 5000 # 5/6 inch
net px encoder_px.rawcounts => sphereprobe.px
net py encoder_py.rawcounts => sphereprobe.py
net pz encoder_pz.rawcounts => sphereprobe.pz
net probe-out sphereprobe.probe-out => motion.probe-input
net probe-out => encoder_px.latch-input encoder_py.latch-input
net probe-out => encoder_pz.latch-input
setp encoder_px.latch-rising 1
setp encoder_px.latch-falling 1
setp encoder_py.latch-rising 1
setp encoder_py.latch-falling 1
setp encoder_pz.latch-rising 1
setp encoder_pz.latch-falling 1

42
lib/hallib/sim_ldelta.hal Normal file
View File

@@ -0,0 +1,42 @@
# core HAL config file for simulation
# first load all the RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
#loadrt lineardeltakins
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
loadusr -W lineardelta MIN_JOINT=-420
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net J0pos joint.0.motor-pos-cmd => joint.0.motor-pos-fb lineardelta.joint0
net J1pos joint.1.motor-pos-cmd => joint.1.motor-pos-fb lineardelta.joint1
net J2pos joint.2.motor-pos-cmd => joint.2.motor-pos-fb lineardelta.joint2
net Apos joint.3.motor-pos-cmd => joint.3.motor-pos-fb
net L lineardeltakins.L => lineardelta.L
net R lineardeltakins.R => lineardelta.R
sets L 269
sets R 130.25
# estop loopback
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed
net spindle-fwd spindle.0.forward
net spindle-rev spindle.0.reverse
#net spindle-speed spindle.0.speed-out
net flood iocontrol.0.coolant-flood
net mist iocontrol.0.coolant-mist

498
lib/hallib/sim_lib.tcl Normal file
View File

@@ -0,0 +1,498 @@
# sim_lib.tcl: haltcl procs for sim configurations
#----------------------------------------------------------------------
# Notes (Joints-Axes):
# 1) if ::KINS(KINEMATICS) exists:
# loadrt the kins using any included parameters
# example: for inifile item:
# [KINS]KINEMATICS = trivkins coordinates=XZ kinstype=BOTH
# use:
# loadrt trivkins coordinates=xz kinstype=BOTH
# else:
# loadrt trivkins
#
# 2) NB: If $::KINS(KINEMATICS) specifies coordinates=, the
# coordinates must agree with ::TRAJ(COORDINATES)
#
# 3) if known kins (trivkins) and xyz, make hypotenuse velocity
# pins for xy,xyz
#----------------------------------------------------------------------
proc indices_for_trivkins {axes} {
# ref: src/emc/kinematics/trivkins.c
if {"$axes" == ""} {set axes {x y z a b c u v w}}
set i 0
foreach a [string tolower $axes] {
# assign to consecutive joints:
set ::SIM_LIB(jointidx,$a) $i
incr i
}
} ;# indices_for_trivkins
proc get_traj_coordinates {} {
# initraj.cc: coordinates may be with or without spaces X Z or XZ
# convert either form to list like {x z}
set coordinates [lindex $::TRAJ(COORDINATES) 0]
set coordinates [string map {" " "" "\t" ""} $coordinates]
set coordinates [split $coordinates ""]
return [string tolower $coordinates]
} ;# get_traj_coordinates
proc check_ini_items {} {
foreach {section item} {KINS KINEMATICS
KINS JOINTS
TRAJ COORDINATES
} {
if ![info exists ::${section}($item)] {
return -code error "Missing inifile item: \[$section\]$item"
}
}
if { [info exists ::DISPLAY(LATHE)]
&& [lsearch $::KINS(KINEMATICS) trivkins] >= 0
} {
# reject historical lathe config using default trivkins coordinates (all)
if {[string first "=" $::KINS(KINEMATICS)] < 0} {
set msg "trivkins lathe config must specify coordinates= "
set msg "$msg\n(typically use \[KINS]KINEMATICS trivkins coordinates=XZ)"
return -code error "$msg"
}
}
set n_extrajoints 0
if [info exists ::EMCMOT(EMCMOT)] {
set mot [split [lindex $::EMCMOT(EMCMOT) 0]]
if {[string first motmod $mot] >= 0} {
foreach item $mot {
if {[string first num_extrajoints= $item] < 0} continue
set n_extrajoints [lindex [split $item =] end]
} ;# foreach
}
}
set kins [split [lindex $::KINS(KINEMATICS) 0]]
if {[string first trivkins $kins] >= 0} {
foreach item $kins {
if {[string first coordinates= $item] < 0} continue
set tcoords [lindex [split $item =] end]
set len_tcoords [string len $tcoords]
set expected_joints [expr $len_tcoords + $n_extrajoints]
if {$expected_joints != $::KINS(JOINTS)} {
set m "\ncheck_ini_items: WARNING:\n"
set m "$m trivkins coordinates=$tcoords specifies $len_tcoords joints\n"
set m "$m trivkins extrajoints=$n_extrajoints\n"
set m "$m trivkins totaljoints=$expected_joints\n"
set m "$m !!! but \[KINS\]JOINTS=$::KINS(JOINTS)\n"
puts stderr $m
}
} ;# foreach
}
return
} ;# check_ini_items
proc setup_kins {axes} {
if ![info exists ::KINS(KINEMATICS)] {
puts stderr "setup_kins: NO \[KINS\]KINEMATICS, trying default trivkins"
loadrt trivkins
return
}
set kins_kinematics [lindex $::KINS(KINEMATICS) end]
set cmd "loadrt $kins_kinematics" ;# may include parms
set kins_module [lindex $kins_kinematics 0]
puts stderr "setup_kins: cmd=$cmd"
if [catch {eval $cmd} msg] {
puts stderr "\nmsg=$msg\n"
}
# set up axis indices for known kins
switch $kins_module {
trivkins {indices_for_trivkins $axes}
default {
puts stderr "setup_kins: unknown \[KINS\]KINEMATICS=<$::KINS(KINEMATICS)>"
}
}
} ;# setup_kins
proc core_sim {axes
number_of_joints
servo_period
{base_period 0}
{emcmot motmod}
} {
# adapted as haltcl proc from core_sim.hal
# note: with default emcmot==motmot,
# thread will not be added for (default) base_pariod == 0
setup_kins $axes
if {"$emcmot" == "motmod"} {
if [catch {loadrt $emcmot \
base_period_nsec=$base_period \
servo_period_nsec=$servo_period \
num_joints=$number_of_joints} msg
] {
# typ: too many joints attempted
puts stderr "\n"
puts stderr "core_sim: loadrt $emcmot FAIL"
puts stderr " msg=$msg\n"
exit 1
}
} else {
# known special case with additional parameter:
# unlock_joint_mask=0xNN
# num_extrajoints=n
set module [split [lindex $emcmot 0]]
set modname [lindex $module 0]
set modparm [lreplace $module 0 0]
if [catch {eval loadrt $modname $modparm \
base_period_nsec=$base_period \
servo_period_nsec=$servo_period \
num_joints=$number_of_joints} msg
] {
puts stderr "\n"
puts stderr "core_sim:unhandled emcmot<$emcmot>"
puts stderr " modname=$modname"
puts stderr " modparm=$modparm"
puts stderr " msg=$msg\n"
exit 1
}
}
addf motion-command-handler servo-thread
addf motion-controller servo-thread
set pid_names ""
set mux_names ""
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
set pid_names "${pid_names},J${jno}_pid"
set mux_names "${mux_names},J${jno}_mux"
}
set pid_names [string trimleft $pid_names ,]
set mux_names [string trimleft $mux_names ,]
loadrt pid names=$pid_names
loadrt mux2 names=$mux_names
# pid components
# The pid comp is used as a pass-thru device (FF0=1,all other gains=0)
# to emulate the connectivity of a servo system
# (e.g., no short-circuit connection of motor-pos-cmd to motor-pos-fb)
foreach cname [split $pid_names ,] {
addf ${cname}.do-pid-calcs servo-thread
# FF0 == 1 for pass-thru, all others 0
do_setp ${cname}.FF0 1.0
foreach pin {Pgain Dgain Igain FF1 FF2} { do_setp ${cname}.$pin 0 }
}
# mux components
# The mux comp is used as a sample-hold to simulate a machine
# with encoders that measure output position when power
# is not applied to the motors or controllers
foreach cname [split $mux_names ,] {
addf $cname servo-thread
}
# signal connections:
net estop:loop <= iocontrol.0.user-enable-out
net estop:loop => iocontrol.0.emc-enable-in
net tool:prep-loop <= iocontrol.0.tool-prepare
net tool:prep-loop => iocontrol.0.tool-prepared
net tool:change-loop <= iocontrol.0.tool-change
net tool:change-loop => iocontrol.0.tool-changed
net sample:enable <= motion.motion-enabled
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
net sample:enable => J${jno}_mux.sel
net J${jno}:enable <= joint.$jno.amp-enable-out
net J${jno}:enable => J${jno}_pid.enable
net J${jno}:pos-cmd <= joint.$jno.motor-pos-cmd
net J${jno}:pos-cmd => J${jno}_pid.command
net J${jno}:on-pos <= J${jno}_pid.output
net J${jno}:on-pos => J${jno}_mux.in1 ;# pass thru when motion-enabled
net J${jno}:pos-fb <= J${jno}_mux.out
net J${jno}:pos-fb => J${jno}_mux.in0 ;# hold position when !motion-enabled
net J${jno}:pos-fb => joint.$jno.motor-pos-fb
}
} ;# core_sim
proc make_ddts {number_of_joints} {
# make vel,accel ddts and signals for all joints
# if xyz, make hypotenuse xy,xyz vels
set ddt_names ""
set ddt_ct 0
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
incr ddt_ct 2
set ddt_names "${ddt_names},J${jno}_vel,J${jno}_accel"
}
set ddt_names [string trimleft $ddt_names ,]
loadrt ddt names=$ddt_names
foreach cname [split $ddt_names ,] {
addf $cname servo-thread
}
# joint vel,accel signal connections:
set ddt_ct 0
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
incr ddt_ct 2
net J${jno}:pos-fb => J${jno}_vel.in ;# net presumed to exist
net J${jno}:vel <= J${jno}_vel.out
net J${jno}:vel => J${jno}_accel.in
net J${jno}:acc <= J${jno}_accel.out
}
set has_xyz 1
foreach letter {x y z} {
if ![info exists ::SIM_LIB(jointidx,$letter)] {
set has_xyz 0
break
}
}
if $has_xyz {
loadrt hypot names=hyp_xy,hyp_xyz ;# vector velocities
addf hyp_xy servo-thread
addf hyp_xyz servo-thread
net J$::SIM_LIB(jointidx,x):vel <= J$::SIM_LIB(jointidx,x)_vel.out
net J$::SIM_LIB(jointidx,x):vel => hyp_xy.in0
net J$::SIM_LIB(jointidx,x):vel => hyp_xyz.in0
net J$::SIM_LIB(jointidx,y):vel <= J$::SIM_LIB(jointidx,y)_vel.out
net J$::SIM_LIB(jointidx,y):vel => hyp_xy.in1
net J$::SIM_LIB(jointidx,y):vel => hyp_xyz.in1
net J$::SIM_LIB(jointidx,z):vel <= J$::SIM_LIB(jointidx,z)_vel.out
net J$::SIM_LIB(jointidx,z):vel => hyp_xyz.in2
net xy:vel => hyp_xy.out
net xyz:vel <= hyp_xyz.out
}
} ;# make_ddts
proc use_hal_manualtoolchange {} {
# adapted as haltcl proc from axis_manualtoolchange.hal
loadusr -W hal_manualtoolchange
# disconnect if previously connected:
unlinkp iocontrol.0.tool-change
unlinkp iocontrol.0.tool-changed
# remove signal with no connections:
delsig tool:change-loop
net tool:change <= iocontrol.0.tool-change
net tool:change => hal_manualtoolchange.change
net tool:changed <= hal_manualtoolchange.changed
net tool:changed => iocontrol.0.tool-changed
net tool:prep-number <= hal_manualtoolchange.number
net tool:prep-number => iocontrol.0.tool-prep-number
} ;# use_hal_manualtoolchange
proc simulated_home {number_of_joints} {
# uses sim_home_switch component
set switch_names ""
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
set switch_names "${switch_names},J${jno}_switch"
}
set switch_names [string trimleft $switch_names ,]
loadrt sim_home_switch names=$switch_names
foreach cname [split $switch_names ,] {
addf $cname servo-thread
}
for {set jno 0} {$jno < $number_of_joints} {incr jno} {
# add pin to pre-existing signal:
net J${jno}:pos-fb => J${jno}_switch.cur-pos
net J${jno}:homesw <= J${jno}_switch.home-sw
net J${jno}:homesw => joint.$jno.home-sw-in
# set sim_home_switch .hysteresis,.home-pos pins
# according to traj units and joint type
if ![info exists ::JOINT_[set jno](TYPE)] {
# use component defaults
} else {
if {"[set ::JOINT_[set jno](TYPE)]" == "ANGULAR"} {
# use component defaults
} else {
if ![info exists ::TRAJ(LINEAR_UNITS)] {
# use component defaults
} else {
switch $::TRAJ(LINEAR_UNITS) {
in - inch - imperial {
do_setp J${jno}_switch.hysteresis 0.05
do_setp J${jno}_switch.home-pos 0.10
}
default { # use component default }
}
}
if { [info exists ::JOINT_[set jno](HOME_SEARCH_VEL)]
&& [set ::JOINT_[set jno](HOME_SEARCH_VEL)] < 0} {
do_setp J${jno}_switch.home-pos -[getp J${jno}_switch.home-pos]
}
}
} ;# type
if [info exists ::JOINT_[set jno](HOME_USE_INDEX)] {
if [set ::JOINT_[set jno](HOME_USE_INDEX)] {
# Note: use default for joint.${jno}.index-delay-ms
net J${jno}:index-enable <= joint.${jno}.index-enable
net J${jno}:index-enable => J${jno}_switch.index-enable
}
}
} ;# for
} ;# simulated_home
proc sim_spindle {} {
# adapted as haltcl proc from sim_spindle_encoder.hal
# simulated spindle encoder (for spindle-synced moves)
loadrt sim_spindle names=sim_spindle
do_setp sim_spindle.scale 0.01666667
loadrt limit2 names=limit_speed
loadrt lowpass names=spindle_mass
loadrt near names=near_speed
loadrt scale names=rpm_rps
setp rpm_rps.gain .0167
# this limit doesn't make any sense to me:
do_setp limit_speed.maxv 5000.0 ;# rpm/second
# encoder reset control
# hook up motion controller's sync output
net spindle-index-enable <=> spindle.0.index-enable
net spindle-index-enable <=> sim_spindle.index-enable
# report our revolution count to the motion controller
net spindle-pos <= sim_spindle.position-fb
net spindle-pos => spindle.0.revs
# simulate spindle mass
do_setp spindle_mass.gain .07
# spindle speed control
net spindle-speed-cmd <= spindle.0.speed-out
net spindle-speed-cmd => limit_speed.in
net spindle-speed-cmd => near_speed.in1
net spindle-speed-limited <= limit_speed.out
net spindle-speed-limited => sim_spindle.velocity-cmd
net spindle-speed-limited => spindle_mass.in
# for spindle velocity estimate
net spindle-rpm-filtered <= spindle_mass.out
net spindle-rpm-filtered rpm_rps.in
net spindle-rps-filtered rpm_rps.out spindle.0.speed-in
net spindle-rpm-filtered => near_speed.in2
# at-speed detection
do_setp near_speed.scale 1.1
do_setp near_speed.difference 10
net spindle-at-speed <= near_speed.out
net spindle-at-speed => spindle.0.at-speed
net spindle-orient <= spindle.0.orient
net spindle-orient <= spindle.0.is-oriented
addf limit_speed servo-thread
addf spindle_mass servo-thread
addf rpm_rps servo-thread
addf near_speed servo-thread
addf sim_spindle servo-thread
} ;# sim_spindle
proc save_hal_cmds {savefilename {options ""} } {
set tmpfile /tmp/save_hal_cmds_tmp
set date [clock format [clock seconds]]
set script [info script]
set save_arg all ;# suffices if only basic_sim in use
if {[llength $::HAL(HALFILE)] > 1} {
set save_arg allu ;# do *all* unconnected pins including
;# pins from other HALFILEs
}
set fd [open $savefilename w] ;# overwrite any existing file
puts $fd "# $date
#
# This file: $savefilename
# Created by: $script
# With options: $::argv
# From inifile: $::env(INI_FILE_NAME)
# Halfiles: $::HAL(HALFILE)
#
# This file contains the hal commands produced by [file tail $script]
# (and any hal commands executed prior to its execution).
# ------------------------------------------------------------------
# To use $savefilename in the original inifile (or a copy of it),
# edit to change:
# \[HAL\]
# HALFILE = LIB:basic_sim.tcl parameters
# to:
# \[HAL\]
# HALFILE = $savefilename
#
# Notes:
# 1) Inifile Variables substitutions specified in the inifile
# and interpreted by halcmd are automatically substituted
# in the created halfile ($savefilename).
# 2) Input pins connected to a signal with no writer are
# not included in the setp listings herein so must be added
# manually
#
"
if {[lsearch $options use_hal_manualtoolchange] >= 0} {
puts $fd "# user space components"
puts $fd "loadusr -W hal_manualtoolchange"
puts $fd ""
}
hal save $save_arg $tmpfile
set ftmp [open $tmpfile r]
set gave_msg 0
set setp_fmt "%-3s %-30s %s"
while {![eof $ftmp]} {
gets $ftmp line
if {([string first "unconnected pin values" $line] >0) && !$gave_msg} {
set gave_msg 1
puts $fd "# Note: ALL unconnected pins follow"
puts $fd "# (includes pins using default with no explicit setp command)"
} else {
scan $line "%s %s %s" cmd arg1 remainder
switch $cmd {
setp {puts $fd [format $setp_fmt $cmd $arg1 $remainder]}
loadrt {
if { [string first tpmod [list $line]] >= 0
|| [string first homemod [list $line]] >= 0
} {
puts $fd "#preloaded module: $line"
} else {
puts $fd $line
}
}
default {puts $fd $line}
}
}
} ;# while
close $ftmp
file delete $tmpfile
if {("$save_arg" != "allu") && [info exists ::SIM_LIB(setp_list)]} {
puts $fd "# setp commands for unconnected input pins"
foreach {pname value} $::SIM_LIB(setp_list) {
puts $fd [format $setp_fmt setp $pname $value]
}
}
close $fd
} ;# save_hal_cmds
proc do_setp {pname value} {
setp $pname $value
lappend ::SIM_LIB(setp_list) $pname $value
} ;# do_setp

42
lib/hallib/sim_rdelta.hal Normal file
View File

@@ -0,0 +1,42 @@
loadrt [KINS]KINEMATICS
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD num_joints=[KINS]JOINTS
loadusr -W rotarydelta
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net J0pos joint.0.motor-pos-cmd => joint.0.motor-pos-fb
net J1pos joint.1.motor-pos-cmd => joint.1.motor-pos-fb
net J2pos joint.2.motor-pos-cmd => joint.2.motor-pos-fb
net J3pos joint.3.motor-pos-cmd => joint.3.motor-pos-fb
net J4pos joint.4.motor-pos-cmd => joint.4.motor-pos-fb
net J5pos joint.5.motor-pos-cmd => joint.5.motor-pos-fb
net J6pos joint.6.motor-pos-cmd => joint.6.motor-pos-fb
net J7pos joint.7.motor-pos-cmd => joint.7.motor-pos-fb
net J8pos joint.8.motor-pos-cmd => joint.8.motor-pos-fb
net J0cmd joint.0.pos-cmd => rotarydelta.joint0
net J1cmd joint.1.pos-cmd => rotarydelta.joint1
net J2cmd joint.2.pos-cmd => rotarydelta.joint2
net pfr rotarydeltakins.platformradius => rotarydelta.pfr
net tl rotarydeltakins.thighlength => rotarydelta.tl
net sl rotarydeltakins.shinlength => rotarydelta.sl
net fr rotarydeltakins.footradius => rotarydelta.fr
sets pfr 10
sets tl 10
sets sl 18
sets fr 5
# estop loopback
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed

View File

@@ -0,0 +1,45 @@
# simulated spindle encoder (for spindle-synced moves)
loadrt sim_spindle names=sim_spindle
setp sim_spindle.scale 0.01666667
loadrt limit2 names=limit_speed
loadrt lowpass names=spindle_mass
loadrt near names=near_speed
loadrt scale names=rpm_rps
setp rpm_rps.gain .0167
# this limit doesn't make any sense to me:
setp limit_speed.maxv 5000.0 # rpm/second
# encoder reset control
# hook up motion controller's sync output
net spindle-index-enable spindle.0.index-enable <=> sim_spindle.index-enable
# report our revolution count to the motion controller
net spindle-pos sim_spindle.position-fb => spindle.0.revs
# simulate spindle mass
setp spindle_mass.gain .07
# spindle speed control
net spindle-speed-cmd spindle.0.speed-out => limit_speed.in
net spindle-speed-limited limit_speed.out => sim_spindle.velocity-cmd spindle_mass.in
# for spindle velocity estimate
net spindle-rpm-filtered spindle_mass.out near_speed.in2
net spindle-rpm-filtered rpm_rps.in
net spindle-rps-filtered rpm_rps.out spindle.0.speed-in
# at-speed detection
setp near_speed.scale 1.1
setp near_speed.difference 10
net spindle-speed-cmd => near_speed.in1
net spindle-at-speed near_speed.out spindle.0.at-speed
addf limit_speed servo-thread
addf spindle_mass servo-thread
addf rpm_rps servo-thread
addf near_speed servo-thread
addf sim_spindle servo-thread

View File

@@ -0,0 +1,47 @@
loadrt comp names=comp_j0,comp_j1,comp_j2,comp_j3
loadrt or2 names=or_homesws
# Joint 0 = X axis, home switch is on negative end
# Joint 1 = Y1 axis, home switch is on negative end
# Joint 2 = Z axis, home switch is on positive end
# Joint 3 = Y2 axis, home switch is on negative end
net J0homeswpos => comp_j0.in1
net J1homeswpos => comp_j1.in1
net J2homeswpos => comp_j2.in0
net J3homeswpos => comp_j3.in1
sets J0homeswpos -0.1
sets J1homeswpos -0.1
sets J2homeswpos 0.1
sets J3homeswpos -0.1
net J0pos => comp_j0.in0
net J1pos => comp_j1.in0
net J2pos => comp_j2.in1
net J3pos => comp_j3.in0
setp comp_j0.hyst .02
setp comp_j1.hyst .02
setp comp_j2.hyst .02
setp comp_j3.hyst .02
# the X and Z joints share a home switch
# the Y joints each have their own home switches
net J0homesw <= comp_j0.out
net J1homesw <= comp_j1.out => joint.1.home-sw-in
net J2homesw <= comp_j2.out
net J3homesw <= comp_j3.out => joint.3.home-sw-in
net J0homesw => or_homesws.in0
net J2homesw => or_homesws.in1
net J0.J1homesw or_homesws.out => joint.0.home-sw-in joint.2.home-sw-in
addf comp_j0 servo-thread
addf comp_j1 servo-thread
addf comp_j2 servo-thread
addf comp_j3 servo-thread
addf or_homesws servo-thread

View File

@@ -0,0 +1,46 @@
loadrt comp names=comp_j0,comp_j1,comp_j2,comp_j3
loadrt or2 names=or_homesws
# Joint 0 = X axis, home switch is on negative end
# Joint 1 = Y1 axis, home switch is on negative end
# Joint 2 = Y2 axis, home switch is on negative end
# Joint 3 = Z axis, home switch is on positive end
net J0homeswpos => comp_j0.in1
net J1homeswpos => comp_j1.in1
net J2homeswpos => comp_j2.in1
net J3homeswpos => comp_j3.in0
sets J0homeswpos -0.1
sets J1homeswpos -0.1
sets J2homeswpos -0.1
sets J3homeswpos 0.1
net J0pos => comp_j0.in0
net J1pos => comp_j1.in0
net J2pos => comp_j2.in0
net J3pos => comp_j3.in1
setp comp_j0.hyst .02
setp comp_j1.hyst .02
setp comp_j2.hyst .02
setp comp_j3.hyst .02
# the X and Z joints share a home switch
# the Y joints each have their own home switches
net J0homesw <= comp_j0.out
net J1homesw <= comp_j1.out => joint.1.home-sw-in
net J2homesw <= comp_j2.out => joint.2.home-sw-in
net J3homesw <= comp_j3.out
net J0homesw => or_homesws.in0
net J3homesw => or_homesws.in1
net J0.J1homesw or_homesws.out => joint.0.home-sw-in joint.3.home-sw-in
addf comp_j0 servo-thread
addf comp_j1 servo-thread
addf comp_j2 servo-thread
addf comp_j3 servo-thread
addf or_homesws servo-thread

View File

@@ -0,0 +1,29 @@
# This hal file simulates home switches
loadrt comp names=comp_x,comp_y,comp_z
# set fixed value for position of virtual switches (use 'in0' for positive and 'in1' for negative values)
net Xhomeswpos => comp_x.in0
net Yhomeswpos => comp_y.in0
net Zhomeswpos => comp_z.in0
sets Xhomeswpos 1
sets Yhomeswpos .5
sets Zhomeswpos 2
# link current position if simulated machine to the other input of the comp functions
net Xpos => comp_x.in1
net Ypos => comp_y.in1
net Zpos => comp_z.in1
# set hysteresis of virtual home switches
setp comp_x.hyst .02
setp comp_y.hyst .02
setp comp_z.hyst .02
net Xhomesw <= comp_x.out => joint.0.home-sw-in
net Yhomesw <= comp_y.out => joint.1.home-sw-in
net Zhomesw <= comp_z.out => joint.2.home-sw-in
addf comp_x servo-thread
addf comp_y servo-thread
addf comp_z servo-thread

View File

@@ -0,0 +1,76 @@
# HAL config file to simulate limit switches using window comparators
#
# first install nine comparators
loadrt wcomp names=wcomp_xmin,wcomp_xmax,wcomp_xhome,wcomp_ymin,wcomp_ymax,wcomp_yhome,wcomp_zmin,wcomp_zmax,wcomp_zhome
# add comparators to servo thread so they will be evaluated
# every servo period
addf wcomp_xmin servo-thread
addf wcomp_xmax servo-thread
addf wcomp_xhome servo-thread
addf wcomp_ymin servo-thread
addf wcomp_ymax servo-thread
addf wcomp_yhome servo-thread
addf wcomp_zmin servo-thread
addf wcomp_zmax servo-thread
addf wcomp_zhome servo-thread
# connect position feedback from step generators
# to window comparators
net Xpos-fb => wcomp_xmin.in
net Xpos-fb => wcomp_xmax.in
net Xpos-fb => wcomp_xhome.in
net Ypos-fb => wcomp_ymin.in
net Ypos-fb => wcomp_ymax.in
net Ypos-fb => wcomp_yhome.in
net Zpos-fb => wcomp_zmin.in
net Zpos-fb => wcomp_zmax.in
net Zpos-fb => wcomp_zhome.in
# connect simulated switch outputs to motion controller
net Xminlim wcomp_xmin.out => joint.0.neg-lim-sw-in
net Xmaxlim wcomp_xmax.out => joint.0.pos-lim-sw-in
net Xhome wcomp_xhome.out => joint.0.home-sw-in
net Yminlim wcomp_ymin.out => joint.1.neg-lim-sw-in
net Ymaxlim wcomp_ymax.out => joint.1.pos-lim-sw-in
net Yhome wcomp_yhome.out => joint.1.home-sw-in
net Zminlim wcomp_zmin.out => joint.2.neg-lim-sw-in
net Zmaxlim wcomp_zmax.out => joint.2.pos-lim-sw-in
net Zhome wcomp_zhome.out => joint.2.home-sw-in
# configure the points at which the simulated switches trip
# X axis first
# set min limit switch to trip at -10.2, release at -1000
setp wcomp_xmin.max -10.2
setp wcomp_xmin.min -1000
# set max limit switch to trip at +10.2, release at +10.21
setp wcomp_xmax.min 10.20
setp wcomp_xmax.max 10.21
# set home switch to trip at 9.5 and release at 9.75
setp wcomp_xhome.min 9.5
setp wcomp_xhome.max 9.75
# Y axis
# set min limit switch to trip at -10.2, release at -1000
setp wcomp_ymin.max -10.2
setp wcomp_ymin.min -1000
# set max limit switch to trip at +10.2, release at +10.21
setp wcomp_ymax.min 10.20
setp wcomp_ymax.max 10.21
# set home switch to trip at 9.5 and release at 9.75
setp wcomp_yhome.min 9.5
setp wcomp_yhome.max 9.75
# Z axis
# set min limit switch to trip at -2.1, release at -1000
setp wcomp_zmin.max -2.1
setp wcomp_zmin.min -1000
# set max limit switch to trip at +4.05,release at +5
setp wcomp_zmax.min 4.05
setp wcomp_zmax.max 5
# set home switch to trip at 3.9 and release at 4.1
setp wcomp_zhome.min 3.9
setp wcomp_zhome.max 4.1

55
lib/hallib/tripodsim.hal Normal file
View File

@@ -0,0 +1,55 @@
# core HAL config file for simulation
# first load all the RT modules that will be needed
# kinematics
loadrt [KINS]KINEMATICS
setp tripodkins.Bx 10
setp tripodkins.Cx 5
setp tripodkins.Cy 7.071
# motion controller, get name and thread periods from INI file
loadrt [EMCMOT]EMCMOT servo_period_nsec=[EMCMOT]SERVO_PERIOD
# 6 differentiators (for velocity and accel sigs)
loadrt ddt names=ddt_j0,ddt_j1,ddt_j2,ddt_j0v,ddt_j1v,ddt_j2v
# add motion controller functions to servo thread
addf motion-command-handler servo-thread
addf motion-controller servo-thread
# link the differentiator functions into the code
addf ddt_j0 servo-thread
addf ddt_j0v servo-thread
addf ddt_j1 servo-thread
addf ddt_j1v servo-thread
addf ddt_j2 servo-thread
addf ddt_j2v servo-thread
# create HAL signals for position commands from motion module
# loop position commands back to motion module feedback
net J0pos joint.0.motor-pos-cmd => joint.0.motor-pos-fb ddt_j0.in
net J1pos joint.1.motor-pos-cmd => joint.1.motor-pos-fb ddt_j1.in
net J2pos joint.2.motor-pos-cmd => joint.2.motor-pos-fb ddt_j2.in
# send the position commands thru differentiators to
# generate velocity and accel signals
net J0vel ddt_j0.out => ddt_j0v.in
net J0acc <= ddt_j0v.out
net J1vel ddt_j1.out => ddt_j1v.in
net J1acc <= ddt_j1v.out
net J2vel ddt_j2.out => ddt_j2v.in
net J2acc <= ddt_j2v.out
# estop loopback
net estop-loop iocontrol.0.user-enable-out => iocontrol.0.emc-enable-in
# create signals for tool loading loopback
net tool-prep-loop iocontrol.0.tool-prepare => iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change => iocontrol.0.tool-changed
# amp control
net J0ena <= joint.0.amp-enable-out
net J1ena <= joint.1.amp-enable-out
net J2ena <= joint.2.amp-enable-out
net J0flt => joint.0.amp-fault-in
net J1flt => joint.1.amp-fault-in
net J2flt => joint.2.amp-fault-in

62
lib/hallib/util_lib.tcl Normal file
View File

@@ -0,0 +1,62 @@
# util_lib.tcl -- utilities
set ::util_lib_quiet 0
# ::tp is the namespace for [HAL]TWOPASS processing
# quiet irrelevant messages in pass0
if { [namespace exists ::tp] \
&& ([::tp::passnumber] == 0) } { set ::util_lib_quiet 1 }
proc show_context {} {
if { $::util_lib_quiet } { return }
set pname show_context
puts "$pname: argv0=$::argv0"
puts "$pname: argv=$::argv"
foreach arg $::argv {
puts "$pname: arg=$arg"
}
puts "$pname: INI_FILE_NAME=$::env(INI_FILE_NAME)"
} ;# show_context
proc show_ini {} {
if { $::util_lib_quiet } { return }
set vars [uplevel #0 {info vars}]
set exclude_list {TP auto_index tcl_platform env}
foreach v $vars {
if { [lsearch $exclude_list $v] >= 0 } {continue}
set vg ::$v
if [array exists $vg] {
parray $vg
}
}
} ;# show_ini_info
proc show_env {} {
parray ::env
} ;# show_env
proc joint_number_for_axis {axis_letter} {
# For JOINTS_AXES:
# Apply rule for known kins with KINEMATICS_IDENTITY
set kinematics [lindex $::env(KINS_KINEMATICS) 0]
set coordinates $::env(TRAJ_COORDINATES)
if {[ string first " " $coordinates] < 0} {
set coordinates [split $coordinates ""]
}
set coordinates [string toupper $coordinates]
set axis_letter [string toupper $axis_letter]
# rules for known kinematics types
switch $kinematics {
trivkins {set joint_number [lsearch $coordinates $axis_letter]}
default {return -code error \
"joint_number_for_axis: <$axis_letter> unavailable for kinematics:\
<$::env(KINS_KINEMATICS)>"
}
}
#puts stderr "kins=$kinematics coords=$coordinates a=$axis_letter j=$joint_number"
if { $joint_number < 0} {
return -code error \
"joint_number_for_axis <$axis_letter> not in $::env(TRAJ_COORDINATES)"
}
return $joint_number
} ;# joint_number_for_axis

14
lib/hallib/var_show.tcl Normal file
View File

@@ -0,0 +1,14 @@
# var_show.tcl
# this halfile can be used to show context and
# INI variable arrays available to tcl halfiles
# example: [HAL]LIB:var_show.tcl arg1 arg2
#begin-----------------------------------------------------------------
source [file join $::env(HALLIB_DIR) util_lib.tcl]
show_ini
show_context
puts ::argv=$::argv
puts ::arglen=[llength $::argv]

View File

@@ -0,0 +1,20 @@
# tcl file to enable jog pins
# for all axis letters
# for up to 9 joints
# errors are ignored
# scale defaults to 1, use ::argv otherwise for all
set scalevalue 1 ;# default
if {[llength $::argv] == 1} {
set scalevalue $::argv
}
catch {
foreach l {x y z a b c u v w} {
setp axis.$l.jog-enable 1
setp axis.$l.jog-scale $::scalevalue
}
for {set i 0} {$i < 9} {incr i} {
setp joint.$i.jog-enable 1
setp joint.$i.jog-scale $::scalevalue
}
}

View File

@@ -0,0 +1,17 @@
[XHC-HB04]
BUTTON=01:button-stop
BUTTON=02:button-start-pause
BUTTON=03:button-rewind
BUTTON=04:button-safe-z
BUTTON=05:button-home
BUTTON=06:button-x2
BUTTON=07:button-y2
BUTTON=08:button-probe-z
BUTTON=09:button-x0
BUTTON=0A:button-y0
BUTTON=0B:button-z0
BUTTON=0C:button-goto-zero
BUTTON=0D:button-step
BUTTON=0E:button-mode
BUTTON=0F:button-spindle
BUTTON=17:button-reset

View File

@@ -0,0 +1,19 @@
[XHC-HB04]
BUTTON=01:button-goto-zero
BUTTON=02:button-start-pause
BUTTON=03:button-rewind
BUTTON=04:button-probe-z
BUTTON=05:button-macro-3
BUTTON=06:button-half
BUTTON=07:button-zero
BUTTON=08:button-safe-z
BUTTON=09:button-home
BUTTON=0A:button-macro-1
BUTTON=0B:button-macro-2
BUTTON=0C:button-spindle
BUTTON=0D:button-step
BUTTON=0E:button-mode
BUTTON=0F:button-macro-6
BUTTON=10:button-macro-7
BUTTON=16:button-stop
BUTTON=17:button-reset

594
lib/hallib/xhc-hb04.tcl Normal file
View File

@@ -0,0 +1,594 @@
# xhc-hb04.tcl: HALFILE for xhc-hb04 pendant
# library procs:
source [file join $::env(HALLIB_DIR) hal_procs_lib.tcl]
source [file join $::env(HALLIB_DIR) util_lib.tcl]
# Usage:
# In INI file, include:
# [HAL]
# HALFILE = existing halfiles
# ...
# HALFILE = xhc-hb04.tcl
#
# [XHC_HB04_CONFIG]
# layout = 2 (required: 1|2 are supported)
# coords = x y z a (up to 4 unique letters from x y z a b c u v w)
# coefs = 1 1 1 1 (optional, filter coefs, 0 < coef < 1, not usually reqd)
# scales = 1 1 1 1 (optional)
# mpg_accels = 50 75 30 75 (optional, less than axis max accel)
# threadname = servo-thread (optional)
# sequence = 1 (optional: 1|2)
# jogmode = normal (optional: normal|vnormal)
# require_pendant = yes (optional: yes|no)
# inch_or_mm = in (optional: in|mm, default is mm)
# [XHC_HB04_BUTTONS]
# name = pin (connect button to hal pin)
# name = "" (no connect button)
# special cases:
# start-pause = std_start_pause (for usual behavior)
# step = xhc-hb04.stepsize-up (for usual behavior)
# (see INI files for more exanples)
# Notes:
# 1) the 'start-pause' pin can be set to "std_start_pause" to
# implement default behavior
# 2) the 'step' pin is normally connected to xhc-hb04.stepsize-up
# 3) non-root access to the usb device requires an additional
# udev rule. Typically, create /etc/udev/rules.d/90-xhc.rules:
# SYSFS{idProduct}=="eb70", SYSFS{idVendor}=="10ce", MODE="666", OWNER="root", GROUP="users"
# or (for ubuntu12 and up):
# ATTR{idProduct}=="eb70", ATTR{idVendor}=="10ce", MODE="666", OWNER="root", GROUP="users"
# 4) For jogmode==vnormal (man motion -- see joint.N.jog-vel-mode),
# the max movement is limited by the machine velocity and acceleration limits
# such that delta_x = 0.5 * vmax**2/accelmx
# so for sim example:
# inch: vmax= 1.2 accelmax= 20 delta_x=0.036
# mm: vmax=30.48 acclemax=508 delta_x=0.9144
# Typically:
# (-s1) sequence 1 (1,10,100,1000) is ok for mm-based machines
# (-s2) sequence 2 (1,5,10,20) is ok for inch-based machines
#
# 5) updated for joints_axes: support only configs with known kins
# and they must be KINEMATICS_IDENTITY (trivkins)
# a) connect axis.L.jog-counts to joint.N.jog-counts
# axis.L.jog-scale to joint.L.jog-scale
# c) use [AXIS_N]MAX_ACCELERATION for both axis.L, joint.N
#-----------------------------------------------------------------------
# Copyright: 2014-16
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#-----------------------------------------------------------------------
proc is_uniq {list_name} {
set tmp(xxxxxxxx) "" ;# make an array first
foreach item $list_name {
if {[array names tmp $item] == $item} {
return 0 ;# not unique
}
set tmp($item) $item
}
return 1 ;# unique
} ;# is_uniq
proc connect_pins {} {
foreach bname [lsort [array names ::XHC_HB04_BUTTONS]] {
set thepin $::XHC_HB04_BUTTONS($bname)
set thepin [lindex $thepin 0]
if {"$thepin" == "\"\""} {
#puts stderr "$::progname: no pin defined for <$bname>"
continue
}
# this pin is can specify std behavior
if { ([string tolower $bname] == "start-pause")
&& ([string tolower $thepin] == "std_start_pause")
} {
std_start_pause_button
puts stderr "$::progname: using std_start_pause_button"
continue
}
# these are warnings in the INI file examples but aren't real pins
if {[string tolower "$thepin"] == "caution"} {
puts stderr "$::progname: skipping button $bname marked <$thepin>"
continue
}
set fullbname xhc-hb04.button-$bname
if !$::xhc_hb04_quiet {
if ![pin_exists $fullbname] {
puts stderr "$::progname: !!! <$fullbname> pin does not exist, continuing"
continue
}
if ![pin_exists $thepin] {
puts stderr "$::progname: !!! <$thepin> target pin does not exist, continuing"
continue
}
}
makenet pendant:$bname <= $fullbname => $thepin
}
} ;# connect_pins
proc wheel_setup {jogmode} {
if [info exists ::XHC_HB04_CONFIG(mpg_accels)] {
set idx 0
foreach g $::XHC_HB04_CONFIG(mpg_accels) {
set g1 $g
if {$g < 0} {
set g [expr -1 * $g]
puts stderr "$::progname: mpg_accel #$idx must be positive was:$g1, is:$g"
}
set ::XHC_HB04_CONFIG(accel,$idx) [format %f $g] ;# ensure floatingpt
incr idx
}
}
# defaults if not in INI file:
set ::XHC_HB04_CONFIG(coef,0) 1.0
set ::XHC_HB04_CONFIG(coef,1) 1.0
set ::XHC_HB04_CONFIG(coef,2) 1.0
set ::XHC_HB04_CONFIG(coef,3) 1.0
if [info exists ::XHC_HB04_CONFIG(coefs)] {
set idx 0
foreach g $::XHC_HB04_CONFIG(coefs) {
set g1 $g
if {$g < 0} {
set g [expr -1 * $g]
puts stderr "$::progname: coef #$idx must be positive was:$g1, is:$g"
}
if {$g > 1} {
set g .5
puts stderr "$::progname: coef #$idx must < 1 coef was:$g1, is:$g"
}
set ::XHC_HB04_CONFIG(coef,$idx) $g
incr idx
}
}
# defaults if not in INI file:
set ::XHC_HB04_CONFIG(scale,0) 1.0
set ::XHC_HB04_CONFIG(scale,1) 1.0
set ::XHC_HB04_CONFIG(scale,2) 1.0
set ::XHC_HB04_CONFIG(scale,3) 1.0
if [info exists ::XHC_HB04_CONFIG(scales)] {
set idx 0
foreach g $::XHC_HB04_CONFIG(scales) {
set ::XHC_HB04_CONFIG(scale,$idx) $g
incr idx
}
}
# to support fractional scales:
# divide the xhc-hb04.jog.scale by $kvalue
# and
# multiply the pendant_util.scale$idx by $kvalue
# to manipulate the integer (s32) joint.N.jog-counts
set kvalue 100.0; # allow fractional scales (.1, .01)
# Note: larger values not advised as the
# jog-counts are type s32 (~ +/-2e9)
setp pendant_util.k $kvalue
makenet pendant:jog-prescale <= xhc-hb04.jog.scale
makenet pendant:jog-prescale => pendant_util.divide-by-k-in
makenet pendant:jog-scale <= pendant_util.divide-by-k-out
# pendant:jog-scale connects to each:
# and axis.$coord.jog-scale
# joint.$jnum.jog-scale (if applicable)
makenet pendant:wheel-counts <= xhc-hb04.jog.counts
makenet pendant:wheel-counts-neg <= xhc-hb04.jog.counts-neg
set anames {x y z a}
set available_idx {0 1 2 3}
# The pendant has fixed labels and displays for letters: x y z a
# and xhc-hb04.cc hardcodes pin names for these letters: x y z a
# Herein: Use label corresponding to coord if possible.
# Otherwise, use next available label. When this occurs,
# pin names will be a little confusing but the signal names will
# align correctly.
#
# With this method, any coord (xyzabcuvw) can be controlled by
# the wheel (providing it exists)
#
set unassigned_coords {}
foreach coord $::XHC_HB04_CONFIG(coords) {
set i [lsearch $anames $coord]
if {$i >= 0} {
set i [lsearch $available_idx $i]
# use idx corresponding to coord
set use_idx($coord) [lindex $available_idx $i]
set available_idx [lreplace $available_idx $i $i]
} else {
lappend unassigned_coords $coord
}
}
foreach coord $unassigned_coords {
# use next available_idx
set use_idx($coord) [lindex $available_idx 0]
set available_idx [lreplace $available_idx 0 0]
}
set mapmsg ""
foreach coord $::XHC_HB04_CONFIG(coords) {
if [catch {set jnum [joint_number_for_axis $coord]} msg] {
puts stderr "$::progname: $msg"
set has_jnum 0
} else {
set has_jnum 1
}
set use_lbl($coord) [lindex $anames $use_idx($coord)]
set idx $use_idx($coord)
if {"$use_lbl($coord)" != "$coord"} {
set mapmsg "$mapmsg\
coord: $coord is mapped to pendant switch/display:\
$use_lbl($coord) index: $idx\n"
}
setp pendant_util.coef$idx $::XHC_HB04_CONFIG(coef,$idx)
setp pendant_util.scale$idx [expr $kvalue * $::XHC_HB04_CONFIG(scale,$idx)]
set acoord [lindex $anames $idx]
# accommodate existing signames for halui outpins:
makenet [existing_outpin_signame halui.axis.$coord.pos-feedback pendant:pos-$coord] \
<= halui.axis.$coord.pos-feedback \
=> xhc-hb04.$acoord.pos-absolute
makenet [existing_outpin_signame halui.axis.$coord.pos-relative pendant:pos-rel-$coord] \
<= halui.axis.$coord.pos-relative \
=> xhc-hb04.$acoord.pos-relative
makenet pendant:jog-scale => axis.$coord.jog-scale
makenet pendant:wheel-counts => pendant_util.in$idx
makenet pendant:wheel-counts-$coord-filtered <= pendant_util.out$idx \
=> axis.$coord.jog-counts
set COORD [string toupper $coord]
makenet pendant:jog-$coord <= xhc-hb04.jog.enable-$acoord \
=> axis.$coord.jog-enable
switch $jogmode {
vnormal {
setp axis.$coord.jog-vel-mode 1
}
}
set afraction 1.0 ;# default
# Only calculate an afraction IF we have specified mpg_accels in the INI file
if [info exists ::XHC_HB04_CONFIG(mpg_accels)] {
if [catch {
set afraction [expr $::XHC_HB04_CONFIG(accel,$idx)\
/[set ::AXIS_[set COORD](MAX_ACCELERATION)] ]
} msg] {
err_exit "<$msg>\n\nMissing INI setting: \[AXIS_$COORD\]MAX_ACCELERATION"
}
}
setp axis.$coord.jog-accel-fraction $afraction
# connect for joint pins if known (trivkins)
if $has_jnum {
if ![pin_exists joint.$jnum.jog-scale] {
err_exit "Not configured for coords = $::XHC_HB04_CONFIG(coords),\
missing joint.$jnum.* pins"
}
makenet pendant:jog-scale => joint.$jnum.jog-scale
makenet pendant:wheel-counts-$coord-filtered => joint.$jnum.jog-counts
if [catch {set std_accel [set ::JOINT_[set jnum](MAX_ACCELERATION)]} msg] {
err_exit "Error: missing \[JOINT_[set jnum]\]MAX_ACCELERATION"
}
if { [set ::JOINT_[set jnum](MAX_ACCELERATION)]
!= [set ::AXIS_[set COORD](MAX_ACCELERATION)] } {
puts stderr "$::progname: Warning accel values differ:"
puts stderr " \[JOINT_[set jnum]\]MAX_ACCELERATION=[set ::JOINT_[set jnum](MAX_ACCELERATION)]"
puts stderr " \[AXIS_[set COORD]\]MAX_ACCELERATION=[set ::AXIS_[set COORD](MAX_ACCELERATION)]"
}
makenet pendant:jog-$coord => joint.$jnum.jog-enable
set jfraction 1.0 ;# default
if [info exists ::XHC_HB04_CONFIG(mpg_accels)] {
if [catch {
set jfraction [expr $::XHC_HB04_CONFIG(accel,$idx)\
/[set ::JOINT_[set jnum](MAX_ACCELERATION)] ]
} msg] {
err_exit "<$msg>\n\nMissing INI setting: \[JOINT_$jnum\]MAX_ACCELERATION"
}
}
setp joint.$jnum.jog-accel-fraction $jfraction
switch $jogmode {
vnormal {
setp joint.$jnum.jog-vel-mode 1
}
}
}
} ;# for coord
if {"$mapmsg" != ""} {
puts "\n$::progname:\n$mapmsg"
}
setp halui.feed-override.scale 0.01
makenet pendant:wheel-counts => halui.feed-override.counts
setp halui.spindle.0.override.scale 0.01
makenet pendant:wheel-counts => halui.spindle.0.override.counts
makenet pendant:feed-override-enable => halui.feed-override.count-enable \
<= xhc-hb04.jog.enable-feed-override
makenet pendant:spindle-override-enable => halui.spindle.0.override.count-enable \
<= xhc-hb04.jog.enable-spindle-override
# accommodate existing signames for motion outpins
makenet [existing_outpin_signame motion.current-vel pendant:feed-value] \
<= motion.current-vel \
=> xhc-hb04.feed-value
makenet [existing_outpin_signame spindle.0.speed-out-rps-abs pendant:spindle-rps] \
<= spindle.0.speed-out-rps-abs \
=> xhc-hb04.spindle-rps
# accommodate existing signames for halui outpins:
makenet [existing_outpin_signame halui.max-velocity.value pendant:jog-speed] \
<= halui.max-velocity.value
makenet [existing_outpin_signame halui.feed-override.value pendant:feed-override] \
<= halui.feed-override.value \
=> xhc-hb04.feed-override
makenet [existing_outpin_signame halui.spindle.0.override.value pendant:spindle-override] \
<= halui.spindle.0.override.value \
=> xhc-hb04.spindle-override
} ;# wheel_setup
proc existing_outpin_signame {pinname newsigname} {
# return existing outpin signame if it exists, else newsigname
set answer [is_connected $pinname signame]
switch $answer {
not_connected {return $newsigname}
is_output {puts "$::progname: Using existing outpin signame: $signame"
return "$signame"
}
default {return -code error \
"existing_outpin_signame:UNEXPECTED $answer"
}
}
} ;# existing_outpin_signame
proc std_start_pause_button {} {
# hardcoded setup for button-start-pause
makenet pendant:start-or-pause <= xhc-hb04.button-start-pause \
=> pendant_util.start-or-pause
makenet pendant:program-resume <= pendant_util.resume \
=> halui.program.resume
makenet pendant:program-pause <= pendant_util.pause \
=> halui.program.pause
makenet pendant:program-run <= pendant_util.run \
=> halui.program.run \
=> halui.mode.auto
# accommodate existing signames for halui outpins:
makenet [existing_outpin_signame halui.program.is-idle pendant:is-idle] \
<= halui.program.is-idle \
=> pendant_util.is-idle
makenet [existing_outpin_signame halui.program.is-paused pendant:is-paused] \
<= halui.program.is-paused \
=> pendant_util.is-paused
makenet [existing_outpin_signame halui.program.is-running pendant:is-running] \
<= halui.program.is-running \
=> pendant_util.is-running
} ;# std_start_pause_button
proc popup_msg {msg} {
puts stderr "$msg"
if [catch {package require Tk
wm withdraw .
tk_messageBox \
-title "$::progname" \
-type ok \
-message "$msg"
destroy .
} msg] {
puts stderr "$msg"
}
} ;# popup_msg
proc err_exit {msg} {
popup_msg $msg
puts stderr "\n$::progname: $msg\n"
exit 1
} ;# err_exit
proc makenet {args} {
if [catch {eval net $args} msg] {
if ![info exists ::makenet_msg] {
set ::makenet_msg \
"Failed to make some required connections"
}
set sig "Signal: <[lindex $args 0]>:"
set ::makenet_msg "$::makenet_msg\n\n$sig\n$msg"
}
} ;# makenet
# begin------------------------------------------------------------------------
set ::progname "xhc-hb04.tcl"
set ::xhc_hb04_quiet 0
# ::tp is the namespace for [HAL]TWOPASS processing
if { [namespace exists ::tp] && ([::tp::passnumber] == 0) } {
set ::xhc_hb04_quiet 1
puts "$::progname: suppressing messages in twopass pass0"
}
set libtag "LIB:"
set cfg ${libtag}xhc-hb04-layout2.cfg ;# default
if ![info exists ::HAL(HALUI)] {
err_exit "\[HAL\]HALUI is not set"
}
if {[array names ::XHC_HB04_CONFIG] == ""} {
err_exit "Missing stanza: \[XHC_HB04_CONFIG\]"
}
if {[array names ::XHC_HB04_BUTTONS] == ""} {
err_exit "Missing stanza: \[XHC_HB04_BUTTONS\]"
}
foreach name [array names ::XHC_HB04_CONFIG] {
set ::XHC_HB04_CONFIG($name) [string trim $::XHC_HB04_CONFIG($name) "{}"]
}
if [info exists ::XHC_HB04_CONFIG(layout)] {
switch ${::XHC_HB04_CONFIG(layout)} {
1 {set cfg ${libtag}xhc-hb04-layout1.cfg}
2 {set cfg ${libtag}xhc-hb04-layout2.cfg}
default {
set msg "Nonstandard layout:<$::XHC_HB04_CONFIG(layout)>"
set cfg $::XHC_HB04_CONFIG(layout)
set msg "$msg\ntrying: $cfg"
popup_msg "$msg"
# keep going
}
}
}
# .cfg files use same search path as halfiles
set cfg [find_file_in_hallib_path $cfg]
if ![file exists $cfg] {
set msg "Cannot find file: <$cfg>\nCannot configure pendant\n"
set msg "$msg\nContinuing without xhc-hb04"
popup_msg "$msg"
return ;# not an exit
}
# handle keywords with either upper or lower case
foreach name [array names ::XHC_HB04_CONFIG] {
set lname [string tolower $name]
set uname [string toupper $name]
if ![info exists ::XHC_HB04_CONFIG($uname)] {
continue
} else {
if { [info exists ::XHC_HB04_CONFIG($lname)]
&& "$::XHC_HB04_CONFIG($lname)" != "$::XHC_HB04_CONFIG($uname)"} {
puts "duplicated item: \[XHC_HB04_CONFIG\]$uname = $::XHC_HB04_CONFIG($uname)"
puts "superseded with: \[XHC_HB04_CONFIG\]$lname = $::XHC_HB04_CONFIG($lname)"
unset ::XHC_HB04_CONFIG($uname)
}
}
}
foreach name [array names ::XHC_HB04_BUTTONS] {
set lname [string tolower $name]
set uname [string toupper $name]
if ![info exists ::XHC_HB04_BUTTONS($uname)] {
continue
} else {
if { [info exists ::XHC_HB04_BUTTONS($lname)]
&& "$::XHC_HB04_BUTTONS($lname)" != "$::XHC_HB04_BUTTONS($uname)"} {
puts "duplicated item: \[XHC_HB04_BUTTONS\]$uname = $::XHC_HB04_BUTTONS($uname)"
puts "superseded with: \[XHC_HB04_BUTTONS\]$lname = $::XHC_HB04_BUTTONS($lname)"
unset ::XHC_HB04_BUTTONS($uname)
}
}
}
# require_pendant==yes: use -x, dont create pins unless connected
# require_pendant==no: create pins if not connected
if ![info exists ::XHC_HB04_CONFIG(require_pendant)] {
set ::XHC_HB04_CONFIG(require_pendant) yes ;# default
}
set dashx -x
switch $::XHC_HB04_CONFIG(require_pendant) {
no {set dashx ""}
}
if [info exists ::XHC_HB04_CONFIG(sequence)] {
set dashs "-s $::XHC_HB04_CONFIG(sequence)"
} else {
set dashs ""
}
set cmd "loadusr -W xhc-hb04 $dashx $dashs -I $cfg -H"
if [catch {eval $cmd} msg] {
set msg "\n$::progname: loadusr xhc-hb04:\n<$msg>\n\n"
set msg "$msg Is it plugged in?\n\n"
set msg "$msg Are permissions correct?\n\n"
set msg "$msg Continuing without xhc-hb04\n"
set msg "$msg \nFailing cmd:\n$cmd"
popup_msg "$msg"
return ;# not an exit
}
if ![info exists ::XHC_HB04_CONFIG(inch_or_mm)] {
set ::XHC_HB04_CONFIG(inch_or_mm) mm
}
switch -glob $::XHC_HB04_CONFIG(inch_or_mm) {
in* {setp xhc-hb04.inch-icon 1}
default {}
}
if ![info exists ::XHC_HB04_CONFIG(jogmode)] {
set ::XHC_HB04_CONFIG(jogmode) normal ;# default
}
set jogmode $::XHC_HB04_CONFIG(jogmode)
switch $jogmode {
normal {}
vnormal {}
default {
set ::XHC_HB04_CONFIG(jogmode) normal
set msg "Unkknown jogmode <$jogmode>"
set msg "$msg Using $::XHC_HB04_CONFIG(jogmode)"
popup_msg "$msg"
}
}
if [info exists ::XHC_HB04_CONFIG(coords)] {
if ![is_uniq $::XHC_HB04_CONFIG(coords)] {
err_exit "coords must be unique, not: <$::XHC_HB04_CONFIG(coords)>"
}
if {[llength $::XHC_HB04_CONFIG(coords)] > 4} {
err_exit "max no.of coords is 4 <$::XHC_HB04_CONFIG(coords)>"
}
} else {
set ::XHC_HB04_CONFIG(coords) {x y z a} ;# default
}
if ![info exists ::XHC_HB04_CONFIG(threadname)] {
set ::XHC_HB04_CONFIG(threadname) "servo-thread" ;# default
}
loadrt xhc_hb04_util names=pendant_util
addf pendant_util $::XHC_HB04_CONFIG(threadname)
# If twopass, do not call procs in pass0 that test pin
# connections since components not yet loaded
if { ![namespace exists ::tp] || ([::tp::passnumber] != 0) } {
connect_pins ;# per INI file items: [XHC_HB04_BUTTONS]buttonname=pin
wheel_setup $::XHC_HB04_CONFIG(jogmode)
# jog wheel per INI file items:
# [XHC_HB04_CONFIG]coords,coefs,scales
}
if [info exists ::makenet_msg] {
popup_msg $::makenet_msg
}
#parray ::XHC_HB04_CONFIG

7
lib/python/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
nf.py
lcnc_realtime.py
gscreen
gmoccapy/
stepconf/
pncconf/
pyui/

135
lib/python/bitfile.py Normal file
View File

@@ -0,0 +1,135 @@
# Copyright 2007 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#
# Info about Xilinx bitfiles:
#
# The format consists of several variable length chunks, preceded by what
# seems to be a constant header (a magic number).
#
# After the header, each "chunk" consists of a one byte "tag", a two or
# four byte length, and "length" bytes of data (the body).
#
# In some chunks, the body is a zero terminated printable string. In
# others it is a blob of binary data. The file format doesn't care.
#
# Standard Xilinx files use 5 chunks: 'a' through 'd' are zero-terminated
# strings with information about the file. 'e' is a large binary blob
# with the actual bitstream in it. Xilinx uses 2 byte lengths for chunks
# 'a' thru 'd', and 4 bytes for chunk 'e'. This library allows other
# chunks, and assume that all have 4 byte lengths, except 'a' thru 'd'.
#
import struct
class BitFile:
MAXCHUNKS = 50
SMALLCHUNKS = "abcd"
MAGIC = "\x00\x09" \
"\x0f\xf0\x0f\xf0" \
"\x0f\xf0\x0f\xf0" \
"\x00\x00\x01"
ORDER = "abcde"
def __init__(self, chunks={}):
self.chunks = dict(chunks)
def __getitem__(self, item):
return self.chunks[item]
def __setitem__(self, item, value):
self.chunks[item] = value
def __delitem__(self, item):
del self.chunks[item]
def chunkorder(self, xxx_todo_changeme):
(tag, value) = xxx_todo_changeme
if tag in self.ORDER:
return self.ORDER.index(tag)
return 256 + ord(tag)
@classmethod
def fromstring(cls, data):
if not data.startswith(cls.MAGIC):
raise ValueError("data does not start with the magic number")
i = len(cls.MAGIC)
chunks = {}
while i < len(data):
tag = data[i]
if tag in cls.SMALLCHUNKS:
chunksize = struct.unpack(">H", data[i+1:i+3])[0]
i = i + 3
else:
chunksize = struct.unpack(">I", data[i+1:i+5])[0]
i = i + 5
chunkdata = data[i:i+chunksize]
if tag in chunks:
raise ValueError("bitfile has chunk %r more than once" % tag)
chunks[tag] = chunkdata
i = i + chunksize
return cls(chunks)
@classmethod
def fromfile(cls, file):
return cls.fromstring(file.read())
@classmethod
def fromfilename(cls, filename):
return cls.fromstring(open(filename, "rb").read())
def tostring(self):
result = self.MAGIC
for tag, chunkdata in sorted(list(self.chunks.items()), key=self.chunkorder):
if len(tag) != 1:
raise ValueError("Tag %r must be a length-1 string" % tag)
result = result + tag
if tag in self.SMALLCHUNKS:
result += struct.pack(">H", len(chunkdata))
else:
result += struct.pack(">I", len(chunkdata))
result += chunkdata
return result
def tofile(self, file):
return file.write(self.tostring())
def tofilename(self, filename):
return open(filename, "wb").write(self.tostring())
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
c = BitFile()
c['a'] = "hello world"
c['e'] = "goodbye"
c['A'] = "shazam"
s = c.tostring()
print((repr(s)))
d = BitFile.fromstring(s)
print((d.chunks))
assert d.tostring() == s
for bitfile in sys.argv[1:]:
bits = open(bitfile, "rb").read()
c = BitFile.fromstring(bits)
for k, v in sorted(c.chunks.items()):
if k in 'abcd':
print((k, v))
else:
print((k, len(v)))
newbits = c.tostring()
assert bits == newbits # Assuming the original is in canonical order!
print()

486
lib/python/bwidget.py Normal file
View File

@@ -0,0 +1,486 @@
# Copyright 2004, 2005, 2006 Jeff Epler <jepler@unpythonic.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Wrapper for BWidget family of widgets"""
__author__ = "Jeff Epler <jepler AT unpy DOT net>"
__all__ = """
Entry Label Button ArrowButton ProgressBar ScrollView Separator
MainFrame LabelFrame TitleFrame PanelFrame ScrolledWindow ScrollableFrame
PanedWindow ButtonBox PagesManager NoteBook Dialog StatusBar
LabelEntry ComboBox SpinBox Tree ListBox MessageDialog ProgressDialog
PasswordDialog SelectFont SelectColor SelectColorMenu
CASCADE CHECKBUTTON COMMAND RADIOBUTTON SEPARATOR STATUS PROGRESSION
""".split()
import sys, types
import tkinter
def returnswidget(f):
def w(self, *args, **kw):
r = f(self, *args, **kw)
return self.nametowidget(str(r))
w.__doc__ = f.__doc__
return w
def makeswidget(f, t):
def w(self, *args, **kw):
r = str(f(self, *args, **kw))
try:
return self.nametowidget(r)
except KeyError:
return makewidget(self, t, str(r))
w.__doc__ = f.__doc__
return w
def nametowidget(self, name):
"""Return the Tkinter instance of a widget identified by
its Tcl name NAME."""
w = self
if name[0] == '.':
w = w._root()
name = name[1:]
while name:
i = name.find('.')
if i >= 0:
name, tail = name[:i], name[i+1:]
else:
tail = ''
while tail:
try:
w.children[name]
except KeyError:
j = tail.find('.')
if j >= 0:
name, tail = name + "." + tail[:j], tail[j+1:]
else:
name, tail = name + "." + tail, ''
else:
break
w = w.children[name]
name = tail
return w
tkinter.Misc.nametowidget = nametowidget
def makewidget(master, klass, path):
path = str(path)
self = klass()
self._name = path[len(master._w)+1:]
self._w = path
self.children = {}
master.children[self._name] = self
self.master = master
self.tk = master.tk
return self
class BWidget:
def _require(self, master):
master.tk.call("package", "require", "BWidget")
def __init__(self, master, cnf={}, **kw):
self._require(master)
tkinter.Widget.__init__(self, master, self.__class__.__name__, cnf, kw)
# Simple Widgets
class Entry(BWidget, tkinter.Entry):
def invoke(self):
return self.tk.call(self._w, "invoke")
class Label(BWidget, tkinter.Label):
def setfocus(self):
return self.tk.call(self._w, "setfocus")
class Button(BWidget, tkinter.Button): pass
class ArrowButton(BWidget, tkinter.Button): pass
class ProgressBar(BWidget, tkinter.Widget): pass
class ScrollView(BWidget, tkinter.Widget): pass
class Separator(BWidget, tkinter.Widget): pass
# Manager Widgets
class _Frame:
def getframe(self):
return self.tk.call(self._w, "getframe")
getframe = makeswidget(getframe, tkinter.Frame)
class _Items:
def itemcget(self, index, option):
return self.tk.call(self._w, "itemcget", index, '-' + option)
def itemconfigure(self, index, cnf=None, **kw):
return self._configure(('itemconfigure', index), cnf, kw)
CASCADE="cascade"
CHECKBUTTON="checkbutton"
COMMAND="command"
RADIOBUTTON="radiobutton"
SEPARATOR="separator"
STATUS = "status"
PROGRESSION = "progression"
class MainFrame(BWidget, _Frame, tkinter.Widget):
def addindicator(self, **kw):
return self.tk.call(self._w, "addindicator", *self._options(kw))
addindicator = makeswidget(addindicator, Label)
def getindicator(self, i):
return self.tk.call(self._w, "getindicator", i)
getindicator = returnswidget(getindicator)
def getmenu(self):
return self.tk.call(self._w, "getmenu")
getmenu = returnswidget(getmenu)
def setmenustate(self, tag, state):
return self.tk.call(self._w, "setmenustate", tag, state)
def showstatusbar(self, name):
return self.tk.call(self._w, "showstatusbar", name)
def showtoolbar(self, index, bool_):
return self.tk.call(self._w, "showtoolbar", index, bool_)
class LabelFrame(BWidget, _Frame, tkinter.Widget):
def align(self, others):
return self.tk.call("LabelFrame::align", self, *others)
class TitleFrame(BWidget, _Frame, tkinter.Frame): pass
class PanelFrame(BWidget, tkinter.Frame): pass
class ScrolledWindow(BWidget, _Frame, tkinter.Frame):
def setwidget(self, child):
return self.tk.call(self._w, "setwidget", child)
class ScrollableFrame(BWidget, _Frame, tkinter.Frame):
def see(self, w, vert=None, horiz=None):
if vert is None and horiz is None:
return self.tk.call(self._w, "see", w)
return self.tk.call(self._w, "see", w, vert, horiz)
def xview(self, *args):
return self.tk.call(self._w, "xview", *args)
def yview(self, *args):
return self.tk.call(self._w, "yview", *args)
class PanedWindow(BWidget, tkinter.Frame):
def add(self, **kw):
return self.tk.call(self._w, "add", *self._options(kw))
add = makeswidget(add, tkinter.Frame)
def getframe(self, index):
return self.tk.call(self._w, "getframe", index)
getframe = makeswidget(getframe, tkinter.Frame)
class ButtonBox(BWidget, _Items, tkinter.Frame):
def add(self, **kw):
return self.tk.call(self._w, "add", *self._options(kw))
add = makeswidget(add, Button)
def delete(self, index):
self.tk.call(self._w, "delete", index)
def index(self, item):
self.tk.call(self._w, "index", item)
def insert(self, index, *kw):
return self.tk.call(self._w, "insert", index, *self._options(kw))
insert = makeswidget(insert, Button)
def invoke(self, index):
return self.tk.call(self._w, "invoke", index)
def setfocus(self, index):
return self.tk.call(self._w, "setfocus", index)
class PagesManager(BWidget, tkinter.Frame):
def add(self, page):
return self.tk.call(self._w, "add", page)
add = makeswidget(add, tkinter.Frame)
def compute_size(self):
return self.tk.call(self._w, "compute_size")
def delete(self, page):
return self.tk.call(self._w, "delete", page)
def getframe(self, page):
return self.tk.call(self._w, "getframe", page)
getframe = makeswidget(getframe, tkinter.Frame)
def pages(self, *args):
return self.tk.call(self._w, "pages", *args)
def raise_page(self, page=None):
if page is None:
return self.tk.call(self._w, "raise")
return self.tk.call(self._w, "raise", page)
class NoteBook(PagesManager, _Items):
def bindtabs(self, event, script):
return self.tk.call(self._w, "bindtabs", event, script)
def delete(self, page, destroyframe=True):
return self.tk.call(self._w, "delete", page, destroyframe)
def insert(self, index, page, **kw):
return self.tk.call(self._w, "insert", index, page, *self._options(kw))
insert = makeswidget(insert, tkinter.Frame)
def move(self, page, index):
return self.tk.call(self._w, "move", page, index)
def see(self, page):
return self.tk.call(self._w, "see", page)
def raise_page(self, page=None):
if page is None:
return self.tk.call(self._w, "raise")
else:
return self.tk.call(self._w, "raise", page)
class Dialog(ButtonBox, tkinter.BaseWidget, _Frame):
def draw(self, focus=None):
if focus is None:
return self.tk.call(self, "draw")
return self.tk.call(self._w, "draw", focus)
def enddialog(self):
return self.tk.call(self._w, "enddialog")
def withdraw(self):
return self.tk.call(self._w, "withdraw")
class StatusBar(BWidget): pass
class LabelEntry(Entry): pass
class ComboBox(Entry):
def bind_entry(self, *args):
return self.tk.call(self._w, "bind", *args)
def getlistbox(self):
r = str(self.tk.call(self._w, "getlistbox"))
try:
return self.nametowidget(r)
except KeyError:
c = self.tk.call("winfo", "class", r)
if c == "ListBox":
return makewidget(self, ListBox, r)
else:
return makewidget(self, tkinter.Listbox, r)
def getvalue(self):
return self.tk.call(self._w, "getvalue")
def post(self):
return self.tk.call(self._w, "post")
def setvalue(self, index):
return self.tk.call(self._w, "setvalue", index)
def unpost(self):
return self.tk.call(self._w, "unpost")
class SpinBox(Entry):
def bind_entry(self, *args):
return self.tk.call(self._w, "bind", *args)
def setvalue(self, index):
return self.tk.call(self._w, "setvalue", index)
def getvalue(self):
return self.tk.call(self._w, "getvalue")
class Tree(BWidget, tkinter.Widget, _Items):
def bind_image(self, event, script):
return self.tk.call(self._w, "bindImage", event, script)
def bind_text(self, event, script):
return self.tk.call(self._w, "bindText", event, script)
def closetree(self, node):
return self.tk.call(self._w, "closetree", node)
def delete(self, arg, *args):
return self.tk.call(self._w, "delete", arg, *args)
def edit(self, node, text, *args):
"edit(self, node, text, verifycmd=None, clickres=None, select=None)"
return self.tk.call(self._w, "edit", node, text, *args)
def exists(self, node):
return self.tk.call(self._w, "exists", node)
def index(self, node):
return self.tk.call(self._w, "index", node)
def insert(self, index, parent, node="#auto", **kw):
return self.tk.call(self._w, "insert", index, parent, node,
*self._options(kw))
def move(self, parent, node, index):
return self.tk.call(self._w, "move", parent, node, index)
def nodes(self, node, *args):
return self.tk.call(self._w, "nodes", *args)
def opentree(self, node, recurse=True):
return self.tk.call(self._w, "opentree", node, recurse)
def parent(self, node):
return self.tk.call(self._w, "parent", node)
def reorder(self, node, neworder):
return self.tk.call(self._w, "reorder", node, neworder)
def see(self, node):
return self.tk.call(self._w, "see", node)
def selection_add(self, *args):
return self.tk.call(self._w, "selection", "add", *args)
def selection_clear(self):
return self.tl.call(self._w, "selection", "clear")
def selection_get(self):
return self.tl.call(self._w, "selection", "get")
def selection_includes(self, node):
return self.tl.call(self._w, "selection", "includes")
def selection_remove(self, *args):
return self.tk.call(self._w, "selection", "remove", *args)
def selection_set(self, *args):
return self.tk.call(self._w, "selection", "set", *args)
def selection_toggle(self, *args):
return self.tk.call(self._w, "selection", "toggle", *args)
def toggle(self, node):
return self.tk.call(self._w, "toggle", node)
def visible(self, node):
return self.tk.call(self._w, "visible", node)
def xview(self, *args):
return self.tk.call(self.xview, *args)
def yview(self, *args):
return self.tk.call(self.yview, *args)
class ListBox(BWidget, tkinter.Widget, _Items):
def bind_image(self, event, script):
return self.tk.call(self._w, "bindImage", event, script)
def bind_text(self, event, script):
return self.tk.call(self._w, "bindText", event, script)
def delete(self, arg, *args):
return self.tk.call(self._w, "delete", arg, *args)
def edit(self, item, text, *args):
"edit(self, item, text, verifycmd=None, clickres=None, select=None)"
return self.tk.call(self._w, "edit", item, text, *args)
def exists(self, item):
return self.tk.call(self._w, "exists", item)
def index(self, item):
return self.tk.call(self._w, "index", item)
def insert(self, index, item="#auto", **kw):
return self.tk.call(self._w, "insert", parent, item,
*self._options(kw))
def items(self, item, *args):
return self.tk.call(self._w, "items", *args)
def move(self, parent, item, index):
return self.tk.call(self._w, "move", parent, item, index)
def reorder(self, node, neworder):
return self.tk.call(self._w, "reorder", node, neworder)
def see(self, node):
return self.tk.call(self._w, "see", node)
def selection_add(self, *args):
return self.tk.call(self._w, "selection", "add", *args)
def selection_clear(self):
return self.tl.call(self._w, "selection", "clear")
def selection_get(self):
return self.tl.call(self._w, "selection", "get")
def selection_includes(self, node):
return self.tl.call(self._w, "selection", "includes")
def selection_remove(self, *args):
return self.tk.call(self._w, "selection", "remove", *args)
def selection_set(self, *args):
return self.tk.call(self._w, "selection", "set", *args)
def selection_toggle(self, *args):
return self.tk.call(self._w, "selection", "toggle", *args)
def xview(self, *args):
return self.tk.call(self.xview, *args)
def yview(self, *args):
return self.tk.call(self.yview, *args)
class MessageDialog(Dialog):
def __init__(self, master, cnf={}, **kw):
self._require(master)
tkinter.Widget.__init__(self, master, "MessageDlg", cnf, kw)
class ProgressDialog(Dialog):
def __init__(self, master, cnf={}, **kw):
self._require(master)
tkinter.Widget.__init__(self, master, "ProgressDlg", cnf, kw)
class PasswordDialog(Dialog):
def __init__(self, master, cnf={}, **kw):
self._require(master)
tkinter.Widget.__init__(self, master, "PasswdDlg", cnf, kw)
class SelectFont(Dialog): pass
class SelectColor(Dialog):
def setcolor(self, index, color):
self.tk.call("SelectColor::setcolor", index, color)
def SelectColorMenu(master, *args, **kw):
i = id([])
if master._w=='.':
w = '.' + name
else:
w = master._w + '.' + name
return master.tk.call("SelectColor::menu", args, *master._options(kw))
# vim:ts=8:sts=4:et:

View File

@@ -0,0 +1,213 @@
#!/usr/bin/env python3
# Copyright (c) 2017 Kurt Jacobson
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import re
from copy import copy
from logging import Formatter
PREFIX = '\033['
SUFFIX = '\033[0m'
COLORS = {
'black': 30,
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'magenta': 35,
'cyan': 36,
'white': 37,
'bgred': 41,
'bggreen': 42,
'bgyellow': 43,
'bggrey': 100
}
MAPPING = {
'VERBOSE': 'bggrey',
'DEBUG': 'blue',
'INFO': 'cyan',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bgred',
}
KEY_SET = set(list(COLORS))
# Returns `text` wrapped in ASCII escape codes to produce `color`
def COLORIZE(text, color=None):
seq = COLORS.get(color, 37) # default to white
return '{0}{1}m{2}{3}'.format(PREFIX, seq, text, SUFFIX)
# Returns `text` wrapped in Qt Richtext Html codes to produce `color`
def QTCOLORIZE(text, color=None):
if color in('bgred','bggreen','bgyellow','bggrey'):
color = color.replace('bg','')
return f'<span style="background-color: {color};">{text}</span>'
return f'<font color="{color}">{text}</font>'
# Matches only the first `color<text>` occurrence
# ^(.*?)<([^)]+)>
# Matches all `color<text>` occurrences, both take the same number of steps
# ([^<\s]+)<([^>]+)>
# (\w+)<([^>]+)>
RE = re.compile(r'(\w+)<([^>]+)>')
class ColoredFormatter(Formatter):
def __init__(self, pattern, pass_clean=True):
self.pass_clean = pass_clean
Formatter.__init__(self, pattern)
# Override the Formatter's format method to add ASCII colors
# to the levelname and any marked words in the log message.
def format(self, record):
colored_record = copy(record)
# Add colors to levelname
levelname = colored_record.levelname
color = MAPPING.get(levelname, 'white')
colored_record.levelname = COLORIZE(levelname, color)
# Add colors to tagged message text
msg = colored_record.getMessage()
plain_msg, color_msg = self.color_words(msg)
# if next logger will colorize too
# then don't clean color codes from msg
if self.pass_clean:
record.msg = plain_msg
colored_record.msg = color_msg
return Formatter.format(self, colored_record)
# Replace `color<message>` in the log message with ASCII codes to colorize
# the word or phrase in the terminal log handler. Also return a cleaned
# version of the message with the tags removed for use by the file handler.
def color_words(self, raw_msg):
# Function to handle color replacement
def color_handler(match):
group = match.group(0) # The whole thing: <red>hello
color = match.group(1) # Group 1: red
word = match.group(2) # Group 2: hello
if color in KEY_SET: # must be one of the color key words
return COLORIZE(word, color)
return group # If color is invalid, just return the word without tags
# Function to handle plain text (stripping tags)
def plain_handler(match):
return match.group(2) # Just return the word part
# Perform both replacements in one pass each
color_msg = RE.sub(color_handler, raw_msg)
plain_msg = RE.sub(plain_handler, raw_msg)
return plain_msg, color_msg
class QtColoredFormatter(Formatter):
def __init__(self, pattern, pass_clean=True):
self.pass_clean = pass_clean
Formatter.__init__(self, pattern)
# Override the Formatter's format method to add ASCII colors
# to the levelname and any marked words in the log message.
def format(self, record):
colored_record = copy(record)
# Add colors to levelname
levelname = colored_record.levelname
color = MAPPING.get(levelname, 'white')
colored_record.levelname = QTCOLORIZE(levelname, color)
# Add colors to tagged message text
msg = colored_record.getMessage()
plain_msg, color_msg = self.qtcolor_words(msg)
# if next logger will colorize too
# then don't clean color codes from msg
if self.pass_clean:
record.msg = plain_msg
colored_record.msg = color_msg
return Formatter.format(self, colored_record)
# Replace `color<message>` in the log message with Html codes to colorize
def qtcolor_words(self, raw_msg):
# Function to handle color replacement
def color_handler(match):
group = match.group(0) # The whole thing: <red>hello
color = match.group(1) # Group 1: red
word = match.group(2) # Group 2: hello
if color in KEY_SET: # must be one of the color key words
return QTCOLORIZE(word, color)
return group # If color is invalid, just return the word without tags
# Function to handle plain text (stripping tags)
def plain_handler(match):
return match.group(2) # Just return the word part
# Perform both replacements in one pass each
color_msg = RE.sub(color_handler, raw_msg)
plain_msg = RE.sub(plain_handler, raw_msg)
return plain_msg, color_msg
# ----------------------- E X A M P L E -----------------------
if __name__ == '__main__':
import logging
# Create logger
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
# Add console handler
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
cf = ColoredFormatter("[%(name)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)")
ch.setFormatter(cf)
log.addHandler(ch)
# Add file handler
fh = logging.FileHandler('demo.log')
fh.setLevel(logging.DEBUG)
ff = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(ff)
log.addHandler(fh)
# Log some stuff
log.debug("Logging demo has green<STARTED>")
log.info("Logging to yellow<demo.log> in the script dir")
log.warning("This is my last warning, red<take heed>")
log.error("This is an error")
log.critical("He's dead, Jim")
# Example exception logging
try:
print(False + "True")
except Exception as e:
log.debug('That did not work!', exc_info=e)

File diff suppressed because it is too large Load Diff

1035
lib/python/common/iniinfo.py Normal file

File diff suppressed because it is too large Load Diff

145
lib/python/common/logger.py Executable file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# Common GUI Logging Module
# Provides a consistent and easy to use logging facility. Log messages printed
# to the terminal will be colorized for easy identification of log level.
#
# Copyright (c) 2017 Kurt Jacobson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import os
import logging
from linuxcnc import ini
# For convenience import log levels so we don't need to import
# logging to set the log level within other modules.
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
# add a new verbose level
VERBOSE = 5
logging.VERBOSE = 5
logging.addLevelName(logging.VERBOSE, "VERBOSE")
# add a custom log level function
def verbose(self, message, *args, **kws):
if self.isEnabledFor(VERBOSE):
# Yes, logger takes its '*args' as 'args'.
self._log(logging.VERBOSE, message, args, **kws)
# add the custom log level to the library (class patch)
logging.Logger.verbose = verbose
logging.Logger.VERBOSE = VERBOSE
# Our custom colorizing formatters for the terminal/file handler
from .colored_formatter import ColoredFormatter, QtColoredFormatter
# Global name of the base logger
BASE_LOGGER_NAME = None
BASE_LOGGER_FILE = None
# Define the log message formats
TERM_FORMAT = '[%(name)s][%(levelname)s] %(message)s (%(filename)s:%(lineno)d)'
FILE_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
QTFILE_FORMAT = '%(levelname)s - %(message)s (%(filename)s:%(lineno)d)<br>'
# Get logger for module based on module.__name__
def getLogger(name):
if BASE_LOGGER_NAME is None:
initBaseLogger('DEFAULT')
name = '{0}.{1}'.format(BASE_LOGGER_NAME, name.upper())
return logging.getLogger(name)
# Set global logging level
def setGlobalLevel(level):
base_log = logging.getLogger(BASE_LOGGER_NAME)
base_log.setLevel(level)
# Initialize the base logger
def initBaseLogger(name, log_file=None, log_level=DEBUG, logToFile=False):
global BASE_LOGGER_NAME
BASE_LOGGER_NAME = name
if not log_file:
log_file = getLogFile(name)
global BASE_LOGGER_FILE
BASE_LOGGER_FILE = log_file
# Clear the previous sessions log file
with open(log_file, 'w') as fh:
pass
# Create base logger
base_log = logging.getLogger(BASE_LOGGER_NAME)
base_log.setLevel(log_level)
# Add console handler
ch = logging.StreamHandler()
ch.setLevel(logging.VERBOSE)
# is this a Qtvcp logger?
Qt = bool( 'qt' in name.lower())
cf = ColoredFormatter(TERM_FORMAT, pass_clean = not Qt)
ch.setFormatter(cf)
base_log.addHandler(ch)
# Add file handler
if logToFile:
fh = logging.FileHandler(log_file)
fh.setLevel(logging.VERBOSE)
# if Qtvcp, use Qt Rich text colors
# else use plain text
if Qt:
ff = QtColoredFormatter(QTFILE_FORMAT)
else:
ff = logging.Formatter(FILE_FORMAT)
fh.setFormatter(ff)
base_log.addHandler(fh)
# Get logger for logger
log = getLogger(__name__)
if logToFile:
base_log.info('Logging to: yellow<{}>'.format(log_file))
return base_log
# Attempt to find the log file specified INI [DISPLAY] LOG_FILE,
# failing that log to $HOME/<base_log_name>.log
def getLogFile(name):
# Default log file to use if not specified in INI
log_file = os.path.expanduser('~/{}.log').format(name.lower())
# LinuxCNC may not be running, so use get() to avoid a KeyError
ini_file = os.environ.get('INI_FILE_NAME')
config_dir = os.environ.get('CONFIG_DIR')
if ini_file:
lcnc_ini = ini(ini_file)
path = lcnc_ini.find('DISPLAY', 'LOG_FILE')
if path:
if path.startswith('~'):
# Path is relative to $HOME
log_file = os.path.expanduser(path)
elif not os.path.isabs(path):
# Assume intended path is relative to the INI file
log_file = os.path.join(config_dir, path)
else:
# It must be an absolute path then
log_file = os.path.realpath(path)
return log_file

View File

@@ -0,0 +1,8 @@
To use gladevcp in a run in place environment just source scripts/rip-environment
and it will set all paths.
For package installations the required files will be placed in the
/usr/share/glade system folder.
When using gladevcp widgets in python programs you need to import
gladevcp.makepins.

View File

@@ -0,0 +1,70 @@
from .hal_pythonplugin import *
try:
import importlib
support_aux_apps = True
except:
support_aux_apps = False
print("\n")
print("Gladevcp: Cannot import importlib")
print(" Auxiliary Gladevcp apps not available\n")
#----------------------------------------------------------------------
# Support auxiliary gladevcp apps
import os,sys,glob,subprocess
def excluded_filename(fname):
exclude_list = ["install","setup"]
for e in exclude_list:
if e in fname: return True
return False
if support_aux_apps:
#----------------------------------------------------------------------
modnames = []
#----------------------------------------------------------------------
# Auxiliary gladevcp apps specified by environmental variable
gladevcp_user_extras = os.getenv('GLADEVCP_EXTRAS')
if gladevcp_user_extras is not None:
print("gladevcp: GLADEVCP_EXTRAS:",gladevcp_user_extras)
for extradir in gladevcp_user_extras.split(":"):
for fname in glob.glob(extradir + "/*.py"):
if excluded_filename(fname):
print("gladevcp: excluded filename:",fname)
continue
modname = os.path.basename(fname).split(".")[0]
if modname in modnames:
print("gladevcp: rejecting duplicate:",fname)
continue
modnames.append(modname)
sys.path.insert(0,extradir) # prepend
importlib.import_module(modname)
print("gladevcp: importing:",fname)
#----------------------------------------------------------------------
# Auxiliary gladevcp apps may be installed in a known location
# location defined by the substitution item LINUXCNC_AUX_GLADEVCP.
# The location is available from the script linuxcnc_var
# (this script should always be in PATH for both RIP builds and
# deb installs of LinuxCNC)
s = subprocess.Popen(['linuxcnc_var','LINUXCNC_AUX_GLADEVCP']
,stdout=subprocess.PIPE
,stderr=subprocess.PIPE
)
p,e = s.communicate()
gladevcp_aux_apps_dir = p.strip().decode() # remove trailing \n
for auxdir in glob.glob(gladevcp_aux_apps_dir + "/*"):
print("gladevcp: auxiliary dir:",auxdir)
for fname in glob.glob(auxdir + "/*.py"):
if excluded_filename(fname):
print("gladevcp: excluded filename:",fname)
continue
modname = os.path.basename(fname).split(".")[0]
if modname in modnames:
print("gladevcp: rejecting duplicate:",fname)
continue
modnames.append(modname)
sys.path.insert(0,auxdir) # prepend
importlib.import_module(modname)
print("gladevcp: importing:",fname)
#----------------------------------------------------------------------

View File

@@ -0,0 +1,15 @@
# Modification of Probe Screen application by Serguei Glavatski (c) 2015
This modification moved the icons from one window to containers with switchable vertical tabs. The goal of this modification is to minimize the window.
The modification was made by zz912.
**Actual version and manual:**
- [https://vers.ge/en/blog/useful-articles/probe-screen-v28](https://vers.ge/en/blog/useful-articles/probe-screen-v28)
- [https://github.com/verser-git/probe_screen_v2.9](https://github.com/verser-git/probe_screen_v2.9)
---
### Load with gladevcp command:
```bash
gladevcp gtk_little_probe
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More