Executor protocol
This page continues the setup from Remap a machine action. The remap gives an M-code its program-level meaning. The executor protocol carries one request to machine action logic and waits for that request to finish.
The supplied HalExecutor uses seven HAL pins. Four go from the remap component to the machine action controller; three bring the response back.
Understand one request
Section titled “Understand one request”Each request has two important values:
request-ididentifies this particular attempt; andopcodesays which operation to perform, such as release (0) or actuate (1).
The action controller reports progress in state and may place a machine-specific diagnostic in result. Once the response is complete, it copies the request ID into response-id. That matching ID tells the executor that the state and result belong to the current request rather than an earlier one.
For a successful request:
- The executor publishes a new
request-idand theopcode, then raisesrequest. - The action controller accepts that ID and starts the operation.
- While working, the controller may set
stateto Accepted or Running. It leavesresponse-idunchanged. - The controller writes the final
resultand a terminalstate. - The controller copies
request-idtoresponse-idlast. - The executor sees the matching ID, reads the completed response, and lowers
request.
Writing response-id last is important. Treat it as “this response is now complete.” If it is copied while the operation is only Accepted or Running, the executor treats that nonterminal response as a failure.
Wire the HAL endpoint
Section titled “Wire the HAL endpoint”For a component named remap and an endpoint named action, HalExecutor creates these pins:
| Pin | Type | Direction | Meaning |
|---|---|---|---|
remap.executor.action.request-id |
u32 |
Output | Nonzero ID assigned to the current attempt. |
remap.executor.action.opcode |
s32 |
Output | Operation requested by the remap. |
remap.executor.action.request |
bit |
Output | True while the request belongs to the executor. |
remap.executor.action.cancel |
bit |
Output | True when an unfinished request has been abandoned. |
remap.executor.action.response-id |
u32 |
Input | ID of the completed response. |
remap.executor.action.state |
s32 |
Input | Current or final controller state. |
remap.executor.action.result |
s32 |
Input | Machine-defined result or diagnostic code. |
Directions are from the remap component’s point of view. Connect the four outputs to the action controller and drive the three inputs from it. The machine integrator chooses the HAL signal names.
The state values are part of the executor protocol:
| Value | State | Completes the request? |
|---|---|---|
0 |
Unavailable | No |
1 |
Idle | No |
2 |
Accepted | No |
3 |
Running | No |
4 |
Succeeded | Yes |
5 |
Failed | Yes |
6 |
Cancelled | Yes |
The action controller defines the meaning of result. Document those values beside that controller. For example, a failed result might distinguish lost pressure from contradictory position switches.
One HalExecutor handles one active request at a time. If a panel button, another service, and a remap can all operate the same mechanism, the action controller must define how those requests are serialized or rejected.
Use the common success policy
Section titled “Use the common success policy”Most remaps should call executor_execute:
def m400_machine_action(self, **words): yield from executor_execute( self, self.machine_action, request=1, timeout=20.0, )It lets the part program continue only after a Succeeded response. Failed, Cancelled, timed-out, and transport-error outcomes become interpreter errors. The timeout uses a monotonic clock and defaults to 10 seconds; override it only when the operation needs a different limit.
Use executor_transact when the remap must decide how to interpret a final response itself:
from interpreter import INTERP_ERRORfrom stdglue import EXECUTOR_FAILED, executor_transact
def custom_machine_action(self, **words): response = yield from executor_transact(self, self.machine_action, request=7)
if response.outcome == EXECUTOR_FAILED and response.result == 12: # This machine accepts result 12 as "already in the requested state". return
if not response.succeeded: self.set_errormsg(response.message or "machine action failed") yield INTERP_ERRORexecutor_transact returns the raw ExecutorResponse. It still manages the timeout and cleanup, but it does not decide which outcome is acceptable. Exceptions from the endpoint propagate to the remap.
Handle timeout and program abort
Section titled “Handle timeout and program abort”When the timeout expires, the executor cancels the operation. With executor_execute, it also stops the part program with an interpreter error.
When LinuxCNC aborts while the Python remap is suspended, it closes the remap generator. The executor’s cleanup then cancels the unfinished operation. HalExecutor publishes cancellation as:
remap.executor.<name>.cancel = TRUEremap.executor.<name>.request = FALSEcancel remains true until the next request. This gives realtime logic time to observe it without relying on a short userspace pulse.
The action controller must give cancellation priority, abandon the matching operation, and perform the machine-defined abort response before accepting another request. Cancellation is a command-lifecycle event, not a safety function. The safety-rated system remains responsible for hazardous conditions and emergency stopping.
Keep owning the physical state
Section titled “Keep owning the physical state”The executor owns the request, not the lasting state of the mechanism. After a successful request is released, the action controller must continue to own output holding, feedback monitoring, loss-of-pressure behavior, and the machine’s physical state.
The controller also decides when an operation has truly succeeded. One mechanism may require pressure plus two agreeing position switches; another may use a qualified analog position. Report Succeeded only after every condition promised by that action is true.
Use another transport when needed
Section titled “Use another transport when needed”The transaction helpers do not require HAL. A custom endpoint provides:
operation = endpoint.begin(request)The returned operation has three methods:
| Method | Behavior |
|---|---|
poll() |
Return None while pending or an ExecutorResponse when finished. Do not block. |
release() |
Release an operation after a terminal response. Repeated cleanup must be harmless. |
cancel() |
Cancel an unfinished operation and release its transport resources. Repeated cleanup must be harmless. |
This interface can be implemented by a service, shared memory, a simulated device, or another machine-specific transport. Attach the endpoint to the interpreter in toplevel.py; the remap function can keep using the same helper.
Commission the complete path
Section titled “Commission the complete path”Test the protocol with physical outputs inhibited first:
- Run each remapped code and confirm the expected
opcodeand a new nonzerorequest-id. - Leave the response pending and confirm that the part program remains at the remapped code.
- Return Succeeded and confirm that
requestfalls and the next block runs. - Return Failed with a diagnostic
resultand confirm thatexecutor_executestops the program. - Withhold the response until timeout and confirm that
cancelrises whilerequestfalls. - Abort an active request and confirm the same cancellation indication.
- Present an old
response-idduring a later request and confirm that it does not complete the new request. - Exercise other request sources and confirm the controller’s documented arbitration and interlocks.
Repeat the applicable tests during controlled machine commissioning before relying on the action in production part programs.