Skip to content

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.

Each request has two important values:

  • request-id identifies this particular attempt; and
  • opcode says 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:

  1. The executor publishes a new request-id and the opcode, then raises request.
  2. The action controller accepts that ID and starts the operation.
  3. While working, the controller may set state to Accepted or Running. It leaves response-id unchanged.
  4. The controller writes the final result and a terminal state.
  5. The controller copies request-id to response-id last.
  6. 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.

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.

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_ERROR
from 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_ERROR

executor_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.

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 = TRUE
remap.executor.<name>.request = FALSE

cancel 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.

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.

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.

Test the protocol with physical outputs inhibited first:

  1. Run each remapped code and confirm the expected opcode and a new nonzero request-id.
  2. Leave the response pending and confirm that the part program remains at the remapped code.
  3. Return Succeeded and confirm that request falls and the next block runs.
  4. Return Failed with a diagnostic result and confirm that executor_execute stops the program.
  5. Withhold the response until timeout and confirm that cancel rises while request falls.
  6. Abort an active request and confirm the same cancellation indication.
  7. Present an old response-id during a later request and confirm that it does not complete the new request.
  8. 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.