machinekit-hal
machinekit-hal copied to clipboard
HAL thread creation API: enable triggered and controlled-environment execution (eg simulated-time)
Issue by mhaberler
Thu Aug 28 07:04:45 2014
Originally opened as https://github.com/machinekit/machinekit/issues/285
HAL threads support only periodic execution of a limited thread function API (see #284), with the global flag "all threads executing" or "all threads stopped". There is no way to pass arguments, or commands to threads. Making this more flexible would enable (see also #219):
- triggered execution of function chains (use cases: event-based execution of function chains, like setup/shutdown - see micges' hm2_eth driver)
- a controlled-execution environment, for instance a simulated-time RTAPI - would enable regression-testing of time-dependent behavior without a RT kernel (eg in automated build environments - a big gaping hole)
This can be enabled by passing to a new thread:
- a custom loop function (currently compiled-in: hal_lib.c:thread_task)
- an optional opaque userdata reference, interpreted by the custom loop function (in an RT context) if needed, and acted upon - example: a structure containing ringbuffers for command/response interaction, which would in effect provide a remote procedure call into the RT environment
The changes to enable this are in fact minor; the legacy API is:
int hal_create_thread(const char *name, unsigned long period, int uses_fp)
int hal_thread_delete(const char *name)
The new API would look like so:
typedef void (*thread_execute)(void *arg);
typedef struct {
unsigned long period_nsec;
int uses_fp;
int cpu_id;
thread_execute execute; // if NULL, default to thread_task
void *arg; // user-defined arguments to custom loop function
} hal_threadargs_t ;
extern int hal_create_xthread(const char *name, const hal_threadargs_t *args);
The legacy API can easily formulated as a call to hal_create_xthread(). So no per-component API changes.
A time-lapse RTAPI could work like so given the above:
- create a ringbuffer
- create a custom variant of thread_task which gets a handle on the ringbuffer
- the thread executes the chain for every message read from the ring
- the message contains arbitrary parameters to be interpreted before chain execution, for instance period, and values to be returned by rtapi_get_time() and rtapi_get_clocks()
Now simulation of RT execution may happen in a fully controlled environment, both with respect to the number of chain invocations and the time values the components see.
Needed: a custom component implementing the new task; possibly a custom RTAPI flavor where rtapi_get_time() and rtapi_get_clocks() report values which include an offset which can be set via the thread control message.
Possible application: study effects of jitter compensation for the trajectory planner - with simulated jitter, and step-by-step.
Comment by mhaberler
Thu Aug 28 08:40:46 2014
a fairly minimal change, no impact: http://git.mah.priv.at/gitweb?p=emc2-dev.git;a=commit;h=2428135992c0a1cd6ff2dd59b2443362a5e1294d
Comment by mhaberler
Mon Apr 6 09:09:37 2015
most of this is now contained in the HAL instantiation branch, about to be merged
Comment by mhaberler
Tue Apr 14 22:34:38 2015
I have a first cut for HAL triggers in https://github.com/mhaberler/machinekit/commits/hal-triggers .
in a nutshell, triggers are userfuncts which thread functs can be chained upon - you call the userfunct, the chain gets executed. It is pretty much like a thread, except not cyclical but controlled from userland.
Here's an example showing the usage:
loadrt or2
loadrt and2
setp or2.0.in0 1
setp and2.0.in0 1
setp and2.0.in1 1
# or2.0.out and and2.0.out still false since thread functions not executed yet
show pin or2.0.out and2.0.out
# create a trigger funct called 'servo'
newinst trigger servo
# chain the or2 and and2 thread functions onto servo
addf or2.0 servo
addf and2.0 servo
# number of times the servo chain was run, still 0:
show pin servo.count
# execute the servo chain
call servo
# or2.0.out now true since thread function executed once
show pin or2.0.out and2.0.out
# number of times the servo chain was run (1):
show pin servo.count
# show the chains (for now under 'show thread')
show thread
# show the functs, 'servo' is listed as type 'trigger'
show funct
# we can create triggers as needed:
newinst trigger base
show funct
and the execution:
mah@nwheezy:~/machinekit-prepare/src$ halcmd -f
halcmd: source hal/icomp-example/trigger-example.hal
Component Pins:
Comp Inst Type Dir Value Name Epsilon Flags
32771 bit OUT FALSE and2.0.out 0
32770 bit OUT FALSE or2.0.out 0
Component Pins:
Comp Inst Type Dir Value Name Epsilon Flags
32772 86 u32 OUT 0x00000000 servo.count 0
hal/icomp-example/trigger-example.hal:23: function call servo returned 0
Component Pins:
Comp Inst Type Dir Value Name Epsilon Flags
32771 bit OUT TRUE and2.0.out 0
32770 bit OUT TRUE or2.0.out 0
Component Pins:
Comp Inst Type Dir Value Name Epsilon Flags
32772 86 u32 OUT 0x00000001 servo.count 0
Realtime Threads (flavor: posix) :
Period FP Name ( Time, Max-Time )
Trigger chains:
servo
1 or2.0
2 and2.0
Exported Functions:
Comp Inst CodeAddr Arg FP Users Type Name
32771 b6f1dcef b6dca0e8 NO 1 thread and2.0
32769 b6f2ee36 00000000 NO 0 user delinst
32769 b6f2ec98 00000000 NO 0 user newinst
32770 b6f1fcef b6dca0d0 NO 1 thread or2.0
32772 86 b6f1b900 b6dca0fc NO 0 trigger servo
Exported Functions:
Comp Inst CodeAddr Arg FP Users Type Name
32771 b6f1dcef b6dca0e8 NO 1 thread and2.0
32772 89 b6f1b900 b6dca100 NO 0 trigger base
32769 b6f2ee36 00000000 NO 0 user delinst
32769 b6f2ec98 00000000 NO 0 user newinst
32770 b6f1fcef b6dca0d0 NO 1 thread or2.0
32772 86 b6f1b900 b6dca0fc NO 0 trigger servo
halcmd:
see also the nosetests unit test, same thing in Python: https://github.com/mhaberler/machinekit/blob/7b5e11e1c3ab78626813fdeb31bb4a4402fb8007/nosetests/unittest_usrfunct.py
this works fine, but to fully support fake-time execution for the timelapse RTAPI a bit more work is needed, also timing pins are missing. That would boil down to passing parameters with the funct call which defines the environment (time etc) the functs are run in.
But that is worth it only if you consider this useful - what do you think, @cdsteinkuehler @zultron @strahlex @robEllenberg ?
I think this will enable regression-testing comps which contain state in a reproducible way; it might have uses beyond.
very useful for the following problems too: external synchronisation of HAL threads not possible #70 triggered RT execution of HAL function chains missing #63 HAL lacks filedescriptor-based eventing mechanism #82