machinekit-hal icon indicating copy to clipboard operation
machinekit-hal copied to clipboard

HAL thread creation API: enable triggered and controlled-environment execution (eg simulated-time)

Open ArcEye opened this issue 7 years ago • 4 comments

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.

ArcEye avatar Aug 03 '18 15:08 ArcEye

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

ArcEye avatar Aug 03 '18 15:08 ArcEye

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

ArcEye avatar Aug 03 '18 15:08 ArcEye

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.

ArcEye avatar Aug 03 '18 15:08 ArcEye