Skip to content

RFC alternative: evaluate a kinematics module outside RT by binding to its live HAL pins - #4

Open
grandixximo wants to merge 1 commit into
masterfrom
kins-bind-variant
Open

RFC alternative: evaluate a kinematics module outside RT by binding to its live HAL pins#4
grandixximo wants to merge 1 commit into
masterfrom
kins-bind-variant

Conversation

@grandixximo

Copy link
Copy Markdown
Owner

RFC, not a merge request. This is an alternative to #3, which sets out the problem, the mathematics and the diagnostic. Read that one first. Both branches are standalone and complete: same loader, same Jacobian, same limit calculation, same kinslimits tool. They differ only in what a kinematics module has to do to be usable outside RT. Merge one or the other, not both.

The idea

A kinematics module's haldata is a struct of pin handles, and a handle is an opaque pointer to the value cell hal_get_real() reads. So a non-RT copy of the module can point its haldata at the cells the running RT instance already owns, and its own forward and inverse then work unmodified, on live values, at any pose asked of them.

int nonrt_attach(const char* coordinates, nonrt_ops_t* ops)
{
    static struct haldata nonrt_haldata; // private to this copy
    kparms kp = {0};

    fiveaxis_kparms(&kp);
    haldata = &nonrt_haldata;

    if (nonrt_bind_real(&haldata->pivot_length,
                        "%s.pivot-length", kp.halprefix)) return -1;
    if (fiveaxis_map_joints(coordinates, &kp)) return -1;

    ops->forward     = fiveaxis_KinematicsForward;
    ops->inverse     = fiveaxis_KinematicsInverse;
    ops->is_identity = 0;
    return 0;
}

That is the whole obligation, plus splitting the coordinate parse out of setup so the hook can run it without creating pins. Rules 1 to 5 in the other PR are gone: no parameter union in a shared header, no struct to register, no snapshot to refresh from inside the servo loop, no sequence counter, and the kinematics math is untouched. Out-of-tree modules can opt in without anyone editing a header they do not own.

trivkins needs no binding at all, only a statement that joints are axes, so a caller can skip the module entirely.

Cost, side by side

Counting only the parts that differ, with the loader, the Jacobian, the limit calculation and the diagnostic excluded because they are identical in both branches:

snapshot (#3) binding (this)
5axiskins +192 -65 +53 -9
trivkins +25 +14
kinematics.h, switchkins.c +14 -1 none
kinematics_params.h +232 none
new shared header none +88
HAL +252 -1 +1
HAL_VER bump yes no
total +715 -67 +158 -10

The HAL side is the sharpest difference. hal_get_pin_value_by_name() already exists, already returns exactly the right pointer, and was simply never EXPORT_SYMBOLed, so RT modules cannot link it. That one line is the entire HAL change here, against 212 lines of new allocator and a shmem layout revision there.

They agree

With the pivot at its default of 250, kinslimits reports the same caps from both branches, to the digit:

s=0.000  vel<=    22.918 (j0)  acc<=     114.6 (j0)  jerk<=      1145.9 (j0)  cond=4.48
s=0.250  vel<=    24.807 (j0)  acc<=     124.0 (j0)  jerk<=      1240.3 (j0)  cond=4.17
s=0.500  vel<=    30.000 (j3)  acc<=     162.1 (j0)  jerk<=      1620.6 (j0)  cond=3.32
s=0.750  vel<=    24.807 (j2)  acc<=     124.0 (j2)  jerk<=      1240.3 (j2)  cond=4.17
s=1.000  vel<=    22.918 (j2)  acc<=     114.6 (j2)  jerk<=      1145.9 (j2)  cond=4.48

The identity path matches too, at 62.5 for the 3-4-5 move in the other PR. 5axiskins still loads under motmod after the setup refactor.

They disagree in one instructive place

Set 5axiskins.pivot-length to 100 with no motion thread running, and this branch reports caps for a 100 mm pivot while the snapshot branch reports caps for 250:

s=0.000  vel<=    30.000 (j3)  acc<=     200.0 (j3)  jerk<=      2000.0 (j3)  cond=2.01
        j0:   1.0000   0.0000   0.0000   0.0000  -1.7453   0.0000   0.0000   0.0000  -0.0000

dX/dB is 1.7453, which is 100 * pi/180, so it is reading the live pin. A snapshot only refreshes when RT calls forward or inverse, and nothing was calling them. On a running machine do_forward_kins() covers that every servo cycle, so this is not a bug in the snapshot approach; it is a dependency the binding approach does not have.

What binding still requires, and what I checked

No module in tree caches derived state. I went through all of them: every one reads its parameters inside forward and inverse, or in a helper those functions call, such as genhex_read_hal_pins and pentakins_read_hal_pins. So the recompute hook I expected to need is needed nowhere in tree. An out-of-tree module that does cache would need one, and would know it.

Output pins and scratch must stay private. The two copies live in different processes and must not write to each other's state. genserfuncs is the case in tree: it writes last_iterations and keeps a hal_malloc'd go_pose scratch, so a bound copy has to keep those private rather than point them at the RT cells. 5axiskins has neither, which is why its non-RT haldata is a single static struct. This is the one thing binding asks of a module author that the snapshot does not, and it is a real judgement call per module.

The cell moves if the pin is later linked to a signal. These parameters are set with setp in every config I have seen, so binding once is probably fine, but it deserves a decision rather than my assumption.

Consistency across parameters is given up. Three rot-point values read one at a time can straddle a change that a snapshot would deliver atomically. Since machine geometry changing mid-move is not physical, I do not think this is a real loss, but it is the honest argument for the snapshot.

My view

I think this is the better answer and I would rather ship it. I am putting both up because the judgement about what a kinematics module should be obliged to do is not mine alone to make.

Alternative to publishing a parameter snapshot in shared memory.

A kinematics module's haldata is a struct of pin handles, and a handle
is an opaque pointer to the value cell that hal_get_real() reads.  So a
second, non-RT copy of the module can point its haldata at the cells the
running RT instance already owns, and then its forward and inverse work
unmodified, on live values, at any pose the caller asks for.

Per module that comes to:

  - export nonrt_attach(), which binds the input pins by name, runs the
    same coordinate parse setup runs, and returns the module's existing
    forward and inverse pointers;
  - split the coordinate parse out of setup so nonrt_attach() can call
    it without creating pins.

Nothing else changes.  The kinematics math is untouched, there is no
parameter struct to declare, no shared header that grows once per
module, no snapshot to refresh from inside the servo loop and no
sequence counter to get right.  Out-of-tree modules can opt in without
anyone editing a header they do not own.

Bind input pins only.  Output pins and scratch storage stay private to
the non-RT copy: the two copies run in different processes and must not
write to each other's state.  5axiskins has neither, so its non-RT
haldata is one static struct.  trivkins needs no binding at all, only a
statement that joints are axes.

hal_get_pin_value_by_name() already existed and already did the right
thing; it was simply never EXPORT_SYMBOL'd, so RT modules could not link
it.  That one line is the entire HAL change.

Verified against the struct-snapshot version of this work: for the same
pivot length, kinslimits reports identical caps to the digit.  With
5axiskins.pivot-length set by setp and no motion thread running, this
version reads the value that was set and the snapshot version reads the
setup default, because a snapshot only refreshes when RT calls forward
or inverse.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant