Multiaxis kinematics and limits - #4374
Conversation
kinematicsSwitch() stored the requested type in switchkins_type and only then ran the switch statement that validates it, so an out of range request left the module pointing at a kinematics that does not exist. The switch itself returned -1 and motion raised its error flag, but switchkins_type kept the bad value, so every kinematicsForward() and kinematicsInverse() call after that failed too and printed switchkins: Forward BAD switchkins_type </7> once per servo cycle for as long as the machine stayed up. Validate the request first and return without touching switchkins_type, which leaves the running kinematics in place. With the range checked up front the default arm of the switch is unreachable, so it goes away.
switchkins.c dispatched on switchkins_type with a three way switch and created a fixed kinstype.is-0/1/2, so a module could never provide more than three kinematics. Hold the setup, forward and inverse functions in arrays and dispatch by index. switchkinsSetup() still provides types 0,1,2 exactly as before, so no kinematics module changes and out of tree modules keep compiling. A module wanting more calls the new switchkinsRegister() from within switchkinsSetup(), once per additional type. The kinstype.is-N pins are created in a loop, which leaves the names of the first three unchanged.
Registration was restricted to types 3 and up, which left types 0,1,2 arriving one way and the rest another. Allow any type from 0, so registration is the general mechanism and the switchkinsSetup() arguments are a shorthand for the first three. A type has to come from one route or the other. Registering one that switchkinsSetup() already filled in is refused, and a rejected registration now fails the module load instead of only printing, since no caller checks the return value. The count of provided types is taken from the highest one filled by either route rather than assumed to be three, so a module can register all of them. A type left out below that is a gap, and the load time message now names the type and which of the three functions is missing instead of saying only "Missing setup function".
switchkins.c owned rtapi_app_main(), so a module could only use it by
having no main of its own. That ruled out halcompile components, which
is why the switchable kinematics in hal/components each carry a private
copy of the dispatch, the kinstype pins and the switch statement.
Move rtapi_app_main(), rtapi_app_exit() and the coordinates= and sparm=
module parameters to switchkins_main.c, and give switchkins.c a single
entry point:
int switchkinsInit(const int comp_id, kparms* kp, const char* coordinates);
It counts and validates the registered types, creates the pins and
starts on type 0. The caller owns the hal component, doing hal_init()
before and hal_ready() after, so anything that already has a component
can use switchkins by calling this.
The types switchkinsSetup() supplies now reach the arrays through
switchkinsRegister() like any others, rather than being written
directly through its out parameters. One registration path means the
checks apply to every type, so a module that both fills an argument and
registers the same type is refused rather than silently overwriting.
The eight existing modules gain switchkins_main.o in their -objs and
are otherwise untouched.
9e1c9ed to
773c3e9
Compare
millturn, xyzab_tdr_kins, xyzacb_trsrn and xyzbca_trsrn each carried their own copy of the switchkins dispatch: a private switchkins_type, a kinematicsSwitch() with a hand-written case per type, and a setup routine that had to hal_set_unready() the component again because it ran from kinematicsType(), long after halcompile had called hal_ready(). Four copies of the same thing, none of them sharing the fixes made to switchkins.c. They could not link switchkins.o before, because switchkins.c supplied rtapi_app_main() and so does halcompile. Now that the dispatch is separate from the 'main' program, a component can link it and call switchkinsInit() from EXTRA_SETUP(), which halcompile runs after hal_init() and before hal_ready(). Two build changes make that possible: - the generated per-comp .mak takes a <component>-extra-objs list, so a .comp can name objects besides its own. - switchkins.h is copied to ../include and installed, so <switchkins.h> resolves from a generated component source. Each of the four now registers its kinematics types and calls switchkinsInit(). Their identity type comes from kins_util.c, which gets them the coordinates= module parameter they never had, and a bad motion.switchkins-type is now rejected and leaves the running kinematics alone instead of stranding the module on a type that does not exist. Pin names are unchanged, except that millturn's in/out example pins are gone: they were template scaffolding copied from userkins.comp, unused by the sim config, and a kinematics-type setup routine is where kinematics pins belong now. millturn keeps its fpin pin and fdemo function. The xyzab-tdr, xyzacb-trsrn, xyzbca-trsrn and millturn sim configs give the same positions through the same MDI sequence as before, to four decimals, in every kinematics type.
Nothing stopped an out-of-tree kinematics module from using switchkins except that there was no way to get at the implementation, so anyone writing one reimplemented kinematicsSwitch() and the kinstype.is-N pins, or did without switching entirely. switchkinscomp.comp is the template for doing it properly. It sets TOPDIR to a source tree and includes switchkins.c and kins_util.c, which is how tpcomp.comp and homecomp.comp already reach the trajectory planning and homing sources. The module then registers its kinstypes and calls switchkinsInit() from EXTRA_SETUP(), the same fifteen lines the in-tree components use. That gets an out-of-tree module the kinematics switching, the kinstype.is-N pins, the coordinates= identity mapping and the HAL and G-code controls, all from the one implementation, and it costs no ABI: the sources are compiled into the module, so it is built against one tree and rebuilt when that tree changes. Like tpcomp, the template is not built in tree because it has no kinematics until TOPDIR is set, so it is filtered out of COMPS and its manpage is named explicitly. Renamed to user_switchkins, pointed at this tree and loaded as [KINS]KINEMATICS, it homes, switches to its example kinstype and back, and rejects a kinstype it does not have.
G12.1 P- selects one of the kinematics offered by a switchable
kinematics module and G13.1 cancels back to kinematics 0. Both are
queue synchronisation points, so no motion is ever planned in one
kinematics and executed in another.
Until now the only way to switch from a program was to write
motion.switchkins-type through an analog output and force a sync by
hand, typically M68 E3 Q1 followed by M66 E0 L0, wrapped in a
subroutine or a remapped M-code. That also costs the #5399 variable
on every switch, because M66 writes it.
G13.1 cancels to kinematics 0 rather than restoring whatever was
selected before, which is how every other cancel in the language
behaves and keeps a block's meaning independent of the path taken
through the program. To put back a caller's selection, read
#<_kins_type>:
#<saved> = #<_kins_type>
G12.1 P2
( ... )
G12.1 P#<saved>
Nothing cancels the selection implicitly. It survives program end and
abort so that the kinematics keeps matching the position readout, since
switching re-derives world position from the joints and would otherwise
move the readout while the machine stands still.
Motion takes the G-code request and the motion.switchkins-type pin on
their edges, so whichever asked most recently wins and a config can use
either or both. Writing the pin from motion instead does not work: the
configs source it from an analog output that would put its own value
back on the next servo cycle. motion.kins-type reports the selection
now in force.
Q was parsed and carried all the way to motion without anything ever
reading it, so it is gone.
EMC_ADJUST_KINS_OFFSET_DATA is registered in the NML format and name
tables and has the update() its declaration promised, without which the
message could not cross the channel.
The interpreter tracked the kinematics it had selected itself, which is not always the one motion is running. An abort clears the interpreter list, so a G12.1 that was queued but not yet sent is dropped while the interpreter keeps the type it converted. A config that drives motion.switchkins-type from HAL changes the kinematics without the interpreter hearing about it at all. Either way #<_kins_type> reports something that is not running, and the save and restore idiom #<saved> = #<_kins_type> G12.1 P3 ( ... ) G12.1 P#<saved> puts back the wrong kinematics. Carry the kinematics motion is running up into status and read it back in Interp::synch(), which already runs after an abort and after every completed switch. Task no longer writes the requested value into status, so the field has a single writer and always reports what motion is actually running.
handle_kinematicsSwitch() assigned the requested type, published it on motion.kins-type, stored it in the status, and only then asked the module to switch. A module that refuses a type it does not provide goes on running the one it has, so the readout named a kinematics that was not in force, and G12.1 P#<_kins_type> put that wrong number back. Ask first, record after. A refused switch leaves the type, the pin and #<_kins_type> on the kinematics still running, and still raises the motion error. The failure message names the type that was asked for rather than the HAL pin, which is not where the request came from when it came from G-code. G12.1 P7 on xyzab_tdr_kins, which provides two types, left motion.kins-type reading 7 while kinstype.is-0 stayed true. It reads 0.
motion.switchkins-type cannot be the general way to choose kinematics. The interpreter never sees it, so a program is read, its limits checked and its path looked ahead in whatever kinematics the interpreter last knew about, which need not be the one that ends up running it. Nothing in the pin can fix that; the interpreter has to be told, which is what G12.1 and G13.1 are for. Motion says so once per session, the first time the pin is used to change the type. A configuration that never switches never sees it, and the G-code route never triggers it. The pin is in a grace period: it keeps working for now, and is meant to go. Both the man page and the switchkins chapter claimed G12.1 and G13.1 write this pin. They do not, and cannot: the configs source it from an analog output that would put its own value back on the next servo cycle. They ask motion directly.
The G12.1 plumbing arrived from the out-of-tree patch with names that
describe nothing. `adjustKinsVar0` is the kinematics type, there is no
Var1, and nothing adjusts an offset. `kinsType` is not a type at all:
it was a char toggling between 'r' and 's' so the servo cycle could
notice that a new request had arrived. The field named like a type was
a flag and the field with the opaque name was the type.
So:
adjustKinsVar0 -> switchkins_type, an int
kinsType ('r'/'s' toggle) -> switchkins_seq, a counter
trajKinsType -> switchkins_seq in EMC_TRAJ_STAT
trajKinsTypeModified -> switchkins_changed in EMC_TRAJ_STAT
ADJUST_KINS_OFFSET(double) -> SELECT_KINS_TYPE(int)
EMC_ADJUST_KINS_OFFSET_DATA -> EMC_TRAJ_SELECT_KINS
EMCMOT_ADJUST_KINS_OFFSET_DATA -> EMCMOT_SELECT_KINS_TYPE
emcAdjustKinsOffset() -> emcSelectKinsType()
switchkins_type rather than kinsType because EMC_TRAJ_STAT already has
kinematics_type, which is the identity/serial/parallel/custom kind and
a different thing entirely. switchkins_type is what the HAL pin and
switchkins.c already call it.
The three status fields were prefixed traj but lived in EMC_MOTION_STAT.
They are trajectory status, so they move into EMC_TRAJ_STAT and lose the
prefix, which also means EMC_TRAJ_STAT::update() carries them.
A counter instead of a two-state toggle keeps the property the toggle
had, that asking for the type already in force is still seen as a
request, without pretending to be an enum.
No G-code, HAL pin or INI name changes.
The four sim configs whose kinematics components now use the switchkins core chose their kinematics by writing motion.switchkins-type through an analog output, the route motion has just deprecated. Each of them would have met the user with the deprecation warning the first time they pressed a kinematics button. The M428, M429 and M430 remaps, the TWP wrappers behind G53.1, G53.3, G53.6 and G69, the abort handler and remap.py now use G12.1 and G13.1. That drops the M66 sync either side of every switch, the test that the HAL pin exists at all, and the #5399 clobber each M66 costs, since G12.1 and G13.1 synchronise interpreter and motion themselves. The check that the switch took reads #<_kins_type> instead of the pin. The vismach guis for the two trsrn configs were reading the value requested through the analog output. They now take motion.kins-type, which is the kinematics actually in force. Eight other sim config directories still select kinematics from HAL: bridgemill, table-rotary-tilting, hexapod-sim, melfa-sim, puma, and the three copies of scara. They are untouched here, and still work.
The rest of the sim configs that shipped with switchkins chose their kinematics by writing motion.switchkins-type through an analog output, which motion now reports as deprecated: bridgemill, table-rotary-tilting, hexapod-sim, melfa-sim, puma and the three copies of scara. Same change as the comp sims got. The M428, M429 and M430 remaps use G12.1 and G13.1, which drops the M66 sync either side of every switch, the test for the hal pin, and the #5399 clobber each M66 costs. The check that the switch took reads #<_kins_type>. The [HAL] net from motion.analog-out-03 goes with them, and the two halshow watch lists follow motion.kins-type instead of the pin that used to drive it. No sim config selects kinematics from HAL now.
773c3e9 to
34ab07b
Compare
|
What is the migration path for people having their own out-of-tree kinematics switch component? |
The G-code chapter told the reader a config may select the kinematics "from G-code, from that pin, or from both", and the switchkins chapter said the same twice, in its introduction and again under G-code commands. All three predate motion reporting the pin as deprecated, and they contradict it. They now say the pin is deprecated and why, in the same words as the man page. The G-code chapter keeps the fact that the pin takes the same numbering, which is what somebody migrating away from it needs to know.
switchkins.h includes kinematics.h, so a module that includes switchkins.h does not need to include kinematics.h itself. switchkins.c had picked up the habit along with genhexkins, 5axiskins, pumakins, scarakins and three21kins, which had it before any of this. Modules that do not use switchkins.h still include kinematics.h directly, as they must.
Their module needs no source change. G12.1 and G13.1 reach a module through the same I checked rather than assumed. I built an out-of-tree component that deliberately does not use switchkins.h or switchkins.c: its own switchkins type, its own with no source change, and no deprecation warning, since the pin is never written. The config keeps working as well. The pin is deprecated, not gone, so an existing M68/M66 config runs as before with one warning per session. Moving off it is a config edit, not code: and the One group does have something to do, and it is not the one you asked about: anybody keeping a copy of switchkins.c in their own tree. Their current build is unaffected, but Announcing it is worth raising at a weekly meeting. Nothing is removed in this PR. |
The kinematics modules are users of switchkins, not part of it, so they take the header the way any other user would. switchkins.c and switchkins_main.c keep the quoted form, being the source itself.
The paragraph read as though the pin were an equal alternative that happened to carry a caveat. State the deprecation first, as a warning.
A realtime module cannot link a library, so an out-of-tree kinematics
module has to compile the switchkins implementation itself. Asking it
for the path to a source tree, as the template did, leaves anybody on a
deb install with nothing to point at.
Install switchkins.c and kins_util.c into share/linuxcnc, the way
mesa_modbus.c.tmpl already is, and put that directory on the realtime
include path. The template then reads
#include <switchkins.c>
#include <kins_util.c>
and builds as it stands.
Draft. Where the multiaxis kinematics work is going, open early so it can be argued with. The first three commits are #4372, review those there. As pieces are agreed I split them into small PRs, rebase this, and carry on, so nothing stalls behind it.
Plan, in order:
.comp,.c, in tree and out of tree.5axiskinsgets a.tool-offsetpin like every other module,maxkinsgets one or a doc note saying it has none, andG53stops ignoring the rotary tool offset on non-wrapped axes.haldata, so the only way to call it from anywhere else is to write a second copy of it. One implementation, parameters supplied at the call site.inRange(), but only at move endpoints and only once the block has reached motion, so a program that will overtravel says so partway into the cut rather than before it starts. With 4 in place the same check runs at load time, against the tool and the kinematics type each block will actually use. A verify button in a GUI is then a presentation of that, not a subsystem of its own.motion.switchkins-type, once the grace period has run. Which release that is has still to be decided.G12.1 and G13.1 sit on top of 1, and are in this branch already.
On tool length specifically: the offset is a nine-axis pose from the interpreter, canon adds it into the coordinate, and each rotary kinematics takes the Z component back through a HAL pin and re-applies it along the real tool direction. That round trip is exact when the config wires the pin, and silently wrong otherwise: a missing
netcosts the full tool length, and an X or Y offset is applied in the rotating work frame while the tool is bolted to the machine. The mechanism works; it has no owner.Why 4 is worth the trouble: every other way of answering "will this program overtravel" builds a second model of the machine, in the verification package or in the post, and the two descriptions drift apart. If the kinematics module can be called with parameters the caller chooses, LinuxCNC can check a program against the same code that will run it, with no second model to keep in sync. That is not available to anyone whose kinematics is locked inside the control.
In this branch now
Switchkins becomes one implementation that everything talks to:
rtapi_app_main(), so a module can take its main from anywhere and callswitchkinsInit()itselfEXTRA_SETUP(). The generated per-comp .mak takes a<component>-extra-objslist, and switchkins.h is installedTOPDIRplus#include, the pattern tpcomp and homecomp already use, so no ABIThen G12.1 and G13.1 on top:
#<_kins_type>stays right across an abort or a switch made behind its backmotion.switchkins-type, so a program is read, its limits checked and its path looked ahead in whatever kinematics the interpreter last knew about. Warned once per session; the pin is in a grace period and still worksadjustKinsVar0was the kinematics type,kinsTypewas a char toggling between 'r' and 's'. Renamed, and the traj-prefixed status fields moved into EMC_TRAJ_STAT where they belongWhy G12.1 and G13.1
Out-of-tree patches have used these numbers for kinematics switching for years, so configurations in the wild already speak them.
They also keep LinuxCNC parallel with other controllers, where G12.1 and G13.1 turn polar coordinate interpolation on and off. That is itself a change of kinematics, and if LinuxCNC ever gets it, switchkins is where it belongs rather than as a mechanism of its own.
G12 and G13 are circular pocket milling on those controllers. Leaving the plain codes free keeps room to port that later.
User visible
coordinates=module parameter, their identity type now coming from kins_util.c.motion.switchkins-typeis rejected and leaves the running kinematics alone, instead of switching to a type that does not exist and clearing everykinstype.is-Npin.inandoutexample pins, template scaffolding referenced by no config.fpinandfdemostay.motion.analog-out-03intomotion.switchkins-type; the trsrn vismach guis and the halshow watch lists followmotion.kins-typeinstead.Testing
The four comp sim configs give identical positions to master through the same MDI sequence, in every kinematics type. Every migrated config was then driven through its own M428, M429 and M430 buttons, right type each time, no deprecation warning, including the TWP wrappers and the abort handler in the trsrn configs.
G12.1 and G13.1 tested on scarakins, on a converted component, and on an out-of-tree component that uses none of switchkins.c: selection, cancel,
#<_kins_type>round trip, HAL switching behind the interpreter's back, abort of a program that had switched mid-run, and a type the module does not provide, which leaves the kinematics, the pin and#<_kins_type>where they were.