Skip to content

modeless task, and jog-while-paused - #4700

Draft
rene-dev wants to merge 21 commits into
LinuxCNC:masterfrom
rene-dev:task-modeless
Draft

rene-dev wants to merge 21 commits into
LinuxCNC:masterfrom
rene-dev:task-modeless

Conversation

@rene-dev

Copy link
Copy Markdown
Member

Task without modes, and jogging while paused

Depends on #4644 (halui in task). Until that is merged this PR also shows its commits.
refactor of emctaskmain, to make the state machine easier to understand, removing about 200 lines.
fixes one very annoying thing in linuxcnc: not being able to jog while a program is paused.

No more Manual / MDI / Auto mode switching

User interfaces no longer switch task between Manual, MDI and Auto. Task tracks what it is running and reports the mode from that:

  • Auto while a program runs or is paused
  • MDI while MDI commands run
  • Manual otherwise

When it is idle, task accepts jogs, homing, MDI commands and program runs without any mode switch first. Starting a run:

  • checks that the machine is homed, a program is open and no jog (wheel jogs included) is active;
  • puts motion in coordinated mode.
    When the run ends, motion goes back to the joint or world mode it was in before, so wheel jogs work
    whenever nothing is running.

Task reports DONE only once the run has ended and motion is back in that mode. A client that waits on an MDI command can then jog straight away.

The planner picks its command table by what is running: idle, program, or MDI. The commands every state accepts are now listed once, in isImmediate(), instead of being repeated in each table.

API changes

  • NML: EMC_TASK_SET_MODE (type 504) is removed.
  • Python mode(), Tcl emc_mode, linuxcncrsh set mode:
    • they no longer send anything to task;
    • they record which page the user interface wants to show (per connection in linuxcncrsh);
    • while task is idle, task_mode / get mode report that page, so screens that switch pages on the mode keep working.
  • halui: halui.mode.manual, .mdi and .auto are removed. The halui.mode.is-* status pins stay and show the mode task reports.
  • shcom: sendManual(), sendAuto() and sendMdi() are removed. schedrmt and emclcd no longer switch modes before MDI or opening a program.
  • User interfaces: the wait_complete() calls after mode() are removed from:
    • AXIS, gmoccapy, gscreen (and its silverdragon skin), touchy and pyui;
    • GStat and the gladevcp and qtvcp actions;
    • the panels in the sample configs.

Behaviour changes

These are documented in updating-linuxcnc:

  • switching to Manual no longer aborts a running MDI command;
  • a run started from the Manual page runs the program;
  • a run with no program open is refused;
  • the parameter file is saved at the start of every MDI command started from idle.

Jog while paused

Axes can be jogged while a program or MDI command is paused, including at M0/M1, once the pause has stopped the motion. Joints cannot be jogged. This works from the keyboard, AXIS, halui, linuxcncrsh and the wheel.

  • How the jog is applied: motion keeps the paused program position and adds a pause offset to each axis, the same way external offsets are added. The offset stays within the soft limits. The queued moves and the interpreter are not touched.
  • On resume or step:
    • the spindles go back to their state, direction, speed and CSS from the moment of the pause, and motion waits for them to reach speed;
    • task restores mist and flood;
    • the axes go back to the paused position in a straight line: first the axes not listed in [TRAJ]PAUSE_RETURN_LAST (default Z), all together, then the listed ones;
    • the program then resumes or steps.
  • Pause during the return: the return stops where it is.
  • Abort: the machine stays where it was jogged to.
  • Reverse run: refused while an axis is jogged away from the paused position.
  • Status:
    • pause_offset, pause_jog_ok and pause_returning are new in EMC_MOTION_STAT and the Python module;
    • while paused, AXIS enables its jog and spindle controls, but not homing.

Problems

large change, needs testing. Gmoccapy and gstat UIs seems to be broken, as they depend on the mode change, this is still TODO.

Drop references to a websocket server that is not in the tree, list the
INI settings halui still reads, and state the cost of unused pins
consistently.
Configurations that still run 'loadusr halui' would fail to start now
that the program is gone. Install a halui stub that prints a warning and
exits successfully, warn at startup when [HAL]HALUI is set, and add a
migration note to the update docs.
They pinned down halui's behaviour so the move into task could be
checked for changes. That is done; the pre-existing halui/jogging and
halui/mdi tests stay.
The conversion to the new INI parser kept the condition but dropped the
read, so maxMaxVelocity stayed 1 and halui.max-velocity.* could never
set more than 1 unit/s. Read [TRAJ]MAX_LINEAR_VELOCITY, falling back to
[AXIS_X]MAX_VELOCITY and then 1.0, as before.
A halui command does not consume a serial number, so the echo still
points at the last NML command while it runs. Clearing the error flags
for it hid the error of a failed NML command, and a halui command that
the planner refused set ERROR on a client command that had succeeded.
Leave the flags to NML commands; the planner still reports a refused
halui command on the error channel.

tests/halui/client-status checks both, and the max-velocity limit.
Manual, MDI and Auto are no longer modes a user interface switches
between. Task tracks what it runs (idle, a program, MDI) and reports the
mode from that: Auto while a program runs or is paused, MDI while MDI
commands run, Manual otherwise.

- While idle task accepts jogs, homing, MDI commands and program runs.
  Starting a run checks homing, an open program and that no jog
  (including a wheel jog) is active, switches motion to coordinated mode
  and re-syncs the interpreter; nothing leaves interp_list until motion
  is in coordinated mode. When the run ends motion goes back to the joint
  or world mode it had before, so wheel jogs work whenever nothing runs.
- The planner chooses its command table by what runs: idle (the old
  Manual and Auto-idle tables merged), program (unchanged), MDI running
  (jogs and joint/world switching refused).
- EMC_TASK_SET_MODE is accepted in any state and does nothing.
- halui: halui.mode.manual, .mdi and .auto do nothing, the is-* pins show
  the derived mode, MDI commands go straight to task.

User interfaces that switch pages on the mode keep working through a
per-UI view mode: Python mode(), Tcl emc_mode and linuxcncrsh set mode
(per connection) record the page asked for, and task_mode / get mode
report it while task is idle. AXIS loses its "coordinated while Manual,
force teleop" kick and the dead tab-raise code.

Behaviour changes, documented in updating-linuxcnc: switching to Manual
no longer aborts a running MDI command; a run started from Manual runs
the program; a run with no program open is refused; the parameter file
is saved at the start of every MDI command from idle.

Tests: new tests/task-modeless; halui/mdi rewritten for the new
semantics; g28.2/invalid-pword waits for motion to leave coordinated
mode; motion-logger, M99, interp_initcode and linuxcncrsh expectations
updated for the mode switches that moved (COORD at run start, FREE or
TELEOP at run end, no abort from a mode change).
The planner's tables repeated the commands every state passes straight
to emcTaskIssueCommand(): 21 accepted in any state, 12 more once the
machine is on, the spindle group unless a program moves, the plan and
I/O group unless a program runs. isImmediate() now says this once, by
what task is doing, and each table keeps only what is special to it.
READING and WAITING share a table, the queued tool commands a helper.

No change in behaviour: every state accepts the same commands as before.
Axes can be jogged (keyboard, AXIS, halui, linuxcncrsh, wheel) while a
program or MDI command is paused, M0/M1 included, once the pause has
stopped the motion. Joints cannot.

Motion keeps the paused program position and adds a per-axis pause
offset on top of it, as the external offsets are added (axis_pause_*()
in axis.c), within the soft limits. The queued moves and the interpreter
are untouched. On resume or step, do_pause_resume() (control.c):

- puts the spindles back as they were at the pause (state, direction,
  speed, CSS) and waits for them to be at speed;
- returns the axes: the ones not in [TRAJ]PAUSE_RETURN_LAST (default Z)
  together in a straight line, then those, at the axis limits, and sets
  the offsets to exactly zero;
- then resumes or steps.

Task restores mist and flood. Pausing during the return stops it there.
An abort keeps the machine where it was jogged to: the planner takes the
offsets into its position. Reverse run is refused while jogged away.

Status: pause_offset, pause_jog_ok and pause_returning in EMC_MOTION_STAT
and Python. Task accepts axis jogs while paused, halui and linuxcncrsh
send them, AXIS enables its jog and spindle controls (not homing).

Tests: new tests/jog-while-paused; motion-logger expectations gain the
SET_PAUSE_RETURN_LAST startup command.
mode() sends nothing to task any more; it sets the page the user
interface shows. The wait_complete() after it waited on whatever command
came before, and while a program runs or is paused task does not report
DONE, so each one sat out its timeout: AXIS hung for 5 s on a spindle
button while paused (ensure_mode()).

Remove the wait after every mode() call in the user interfaces, their
libraries and the sample configs: AXIS, gmoccapy, gscreen and the
silverdragon skin, touchy, pyui, GStat, the gladevcp and qtvcp actions,
the gladevcp offset page and probe panels, the 3pcenter panel. The waits
after teleop_enable() that follow some of them stay.
The task mode follows what task runs, and nothing sets it any more:
task accepted EMC_TASK_SET_MODE and did nothing, and the Python, Tcl and
linuxcncrsh mode commands stopped sending it.

- NML: drop EMC_TASK_SET_MODE (type 504) and task's handling of it.
- shcom: drop sendManual(), sendAuto() and sendMdi(); schedrmt and
  emclcd no longer switch modes before MDI or opening a program, and
  emclcd's Test key no longer toggles the mode.
- halui: drop halui.mode.manual, .auto and .mdi. The .is-* status pins
  stay.
- Users of the pins: the xhc-hb04 pendant helper starts a program with
  halui.program.run only, the halui_pyvcp sim panel keeps the mode LEDs,
  the xhc-whb04b-6 example configuration and docs drop their mode nets,
  the remote start example needs halui.program.run only.
pause_return_plan() starts a group of axes back toward the paused
position, and pause_return_settled() can run in the same servo cycle,
before the planner has. The axes still read 'not active' from the end of
their jogs, so the group counted as arrived and its offsets were set to
zero at once: X and Y always jumped back, Z when X and Y were not jogged.
Mark a planned axis active until the planner reports.

The test now requires positions on the way back for X, Y and Z.
The return to the paused position ran at the axis velocity limits, too
fast to follow or stop. Each axis now remembers the fastest it moved
during jogs while paused (wheel jogs included), and each group of the
return goes no faster along its line than the fastest of its axes,
still within the axis limits.

The test checks that a 2 mm return after a 4 mm/s jog takes about 0.5 s.
When the last MDI command finished, task reported DONE in the cycle
before the run ended, and again before motion was back in joint or
world mode. A client waiting for DONE could then see coordinated mode,
or have an axis jog refused: linuxcncrsh's test read TELEOP_ENABLE OFF
after its MDI commands on CI.

Task now reports DONE only when no run is on and motion is in the mode
the run went back to (with a 1 s timeout should motion not get there).

tests/task-modeless checks that motion is in world mode as soon as an
MDI command is DONE, and that a jog right after is accepted.
@zz912

zz912 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

I really appreciate the work you are doing on simplifying Task and enabling jogging while the program is paused. I can see how the modeless approach could be particularly useful on small hobby machines.

However, I have a concern about removing mode switching unconditionally, especially regarding the default behavior and the implications for larger, production or industrial machines.

In the current behavior, being in Manual mode also provides a useful operational safeguard: a program cannot be started while the machine is in Manual mode. For example, if an operator accidentally presses a physical RUN button while working in Manual mode, the mode restriction prevents the program from starting. As I understand this PR, that same button could now start a loaded program even while the operator is on the Manual page, provided the other Task checks pass.

I realize that LinuxCNC's Manual mode is not necessarily equivalent to a safety-rated operating mode as defined by machinery safety standards. I also want to be transparent that the safety-related concerns and conclusions behind this comment were developed with AI assistance. I am not a machinery safety expert, and I am not claiming that this change necessarily violates any particular standard.

I have read machinery safety standards for CNC machines in the past, but that was a long time ago, and I no longer have access to those standards. As I remember them, they contained quite a few requirements concerning software and safety-related control functions, including requirements expressed in terms of Performance Levels (PL), sometimes as low as PL a. My recollection may be incomplete or outdated, and I cannot verify the exact requirements now. I understand that the required PL depends on the particular safety function and the machine's risk assessment, and I am not claiming that LinuxCNC itself is certified to any particular PL.

Nevertheless, I would hate to see LinuxCNC unnecessarily lose some of its suitability for larger machines because an existing operational safeguard is removed unconditionally. LinuxCNC is used in a wide range of applications, and I believe it would be a shame to make the default behavior less suitable for machines where predictable operating-mode selection and protection against unintended program starts are important.

For small hobby machines, the convenience of a modeless Task may be very welcome. For larger machines, though, I worry that making this behavior unconditional could make LinuxCNC significantly less suitable for applications where these considerations matter.

Would it be possible to make the modeless behavior an optional feature, while preserving the current mode-based behavior as the default?

This would allow hobby users and integrators who want the new behavior to enable it, while allowing existing installations to retain their current behavior and avoid unexpected changes to their control panels and operating procedures.

I think this could preserve the benefits of your work without forcing every LinuxCNC installation to adopt the same trade-off. It would also provide a more gradual migration path for existing configurations that rely on mode selection as part of their operating workflow.

Thanks again for working on this. I think the improvements have real value; my concern is mainly that the new behavior should be an explicit choice rather than an unconditional change to the default.

@rene-dev

Copy link
Copy Markdown
Member Author

I dont understand what you mean, the first thing that happens when you press cycle start is mode is switched to auto:
https://github.com/LinuxCNC/linuxcnc/blob/master/src/emc/usr_intf/halui.cc#L947
modes are many things, but not a safety feature.

@zz912

zz912 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

modes are many things, but not a safety feature.

I dont agree. Requirments for Performans Level = a are safety features too. I think we shouldnt ignore iso 16090-1:2022 .

@rene-dev

Copy link
Copy Markdown
Member Author

This has nothing to do with the task mode.

@zz912

zz912 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

I understand the motivation behind removing mode switching, and I agree that modes are not particularly useful in many GUIs. However, Gmoccapy is fundamentally built around the Manual, MDI, and Auto pages, so I think this change deserves a closer look from the perspective of a Gmoccapy integrator.

Could you please review this PR from that perspective as well?

If the operating mode is no longer something the operator selects to restrict which commands are available, how does the new architecture distinguish between “the operator intends to work manually” and “the operator has explicitly enabled automatic program execution”?

I am also concerned about the removal of the existing operational safeguard that prevents a program from being started while Manual mode is selected. I understand the benefits of the modeless approach, especially for hobby machines, but I believe preserving the current mode-based behavior as the default, with modeless operation available as an option, would be a reasonable compromise.

I think separating the actual Task activity from the operator's selected GUI page and the explicit intent to enable automatic operation would help address both concerns.

@c-morley

Copy link
Copy Markdown
Collaborator

I dont understand what you mean, the first thing that happens when you press cycle start is mode is switched to auto: https://github.com/LinuxCNC/linuxcnc/blob/master/src/emc/usr_intf/halui.cc#L947 modes are many things, but not a safety feature.

That may be true for HALUi but is not true for at least Gmoccapy and Qtdragon.
In Qtdragon's case, it can be either way.
And while it may not be a hard safety feature, it certainly could save you from ruining something.

@NoGare

NoGare commented Oct 18, 2026

Copy link
Copy Markdown

Nice features, jogging mid program is something I've wished severals times for(specifically in when within subroutine loops)

"a run started from the Manual page runs the program;"

I agree with what @zz912 & @c-morley are saying : programs should not run from manual unless explicitly set that way within ini. All industrial VMCs I've used have always had an adherence to the design pattern - if you're in manual, the cycle-start "chain" is broken. When in MDI, cycle start runs the typed command. Whether the physical or digital selector is switching a mode or breaking a run-mode/safety chain I don't believe LCNC should deviate from norms.

@zz912

zz912 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

René, I would like to share some observations I made while studying ISO 16090-1 and my thoughts on the future direction of LinuxCNC.

First, I would like to emphasize that responsibility for the safety of the finished machine lies with the integrator. Not with the LinuxCNC developer, and not with the operator. The integrator performs the risk assessment, designs the protective measures, and decides how the control system will be used on a particular machine. Nevertheless, I think it is a shame if the software makes this job unnecessarily difficult instead of helping the integrator build a safe machine.

When I studied ISO 16090-1 in the past, I noticed that LinuxCNC already met many requirements out of the box that an integrator would otherwise have to address through additional modifications. Unfortunately, I no longer have access to the standard, and my knowledge is not up to date, so please regard the following points as my technical observations rather than a formal assessment of compliance.

1. Operating modes are more than just programming variables

In my view, AUTO, MANUAL, and MDI are not merely internal Task states. They also have practical significance for organizing the operator's work and restricting which operations are available in different situations.

I understand your motivation for eliminating unnecessary mode switching and simplifying the Task architecture. However, I am concerned that removing this mechanism could also eliminate useful restrictions that integrators rely on when designing machine controls.

Before deciding that modes are no longer necessary, I think we should also consider their role in risk assessment and in ensuring that the machine behaves as operators expect.

2. Two different use cases for MDI commands

From a programmer's perspective, executing an MDI command may always be the same operation. From a safety and machine-control design perspective, however, I see a fundamental difference between two use cases.

The first case: MDI commands defined by the integrator in the machine configuration.

The integrator can create custom buttons and MDI commands, for example in Embedded Tabs. For each particular machine, the integrator decides which commands are available and under what conditions they can be executed. For example, they might create a button for a specific service operation that is only available when certain conditions are met.

In this case, the integrator can design the controls according to the risk assessment for the particular machine.
mdi_command_code

The second case: MDI commands entered directly by the operator in the MDI command line provided by the GUI.

This is a different situation. The operator may enter a command without fully understanding its purpose or consequences. The ability to enter commands manually is useful for experienced users and during setup, but the integrator should be able to decide whether this functionality is available and under what conditions it can be used.

From a programming perspective, both cases may result in the same MDI command being executed. From a risk-management perspective, however, they are not equivalent. In the first case, the integrator defines a specific operation and its execution conditions. In the second, the operator is allowed to enter general-purpose commands.
mdi_command_gui

3. The role of the physical CYCLE START button

I agree with NoGare's observation that, on conventional CNC machines, the physical CYCLE START button has a clear meaning depending on the selected operating mode:

  • In MANUAL, it does not start an automatic program.
  • In AUTO, it starts or resumes program execution.
  • In MDI, it executes the entered MDI command.

This behavior is predictable for the operator. The operator knows that entering a command does not necessarily mean it will be executed, and that CYCLE START represents an explicit request to begin the corresponding operation.

When I studied ISO 16090-1, my understanding was that the initiation of a sequence of movements must be controlled through the appropriate START CYCLE control. I do not want to claim that I can precisely establish all the conditions attached to this requirement without access to the current text of the standard. Nevertheless, I believe we should take this principle seriously when designing LinuxCNC.
Cycle_start

4. Current GUIs can make this principle difficult to implement

In my experience, it can sometimes be quite difficult for an integrator to prevent motion from starting in ways other than those intended. For example, the integrator may have to modify the GUI and remove certain virtual buttons for starting a program or executing MDI commands.

In Gmoccapy, I currently see several different ways for an operator to execute an MDI command, including through the keyboard. This may be convenient for an experienced user, but when designing the controls for a larger machine, I would like to be able to restrict these execution paths and clearly define what initiates motion.

Imagine a new operator learning to work on a large CNC machine controlled by LinuxCNC. If they are transitioning from Fanuc, Heidenhain, or Siemens, they will probably expect certain familiar behaviors from the machine controls. They could be unpleasantly surprised if simply confirming a command by pressing Enter starts a sequence of movements that they expected to begin only after pressing CYCLE START.

This is not just about operator experience. It is about making machine behavior predictable and allowing the integrator to configure the controls in a way that matches the intended operating procedures.

5. What I would like to see in the future

I do not expect you to solve this problem as part of this PR. Nor do I want to question the value of the improvements you are working on. Jogging while a program is paused is a useful feature, and eliminating unnecessary mode transitions may be the right direction for the architecture.

My main point is that, as LinuxCNC continues to evolve, we should not overlook the needs of integrators working with larger and industrial machines. Ideally, LinuxCNC should provide a flexible enough architecture to make small hobby machines convenient to operate while still giving integrators the tools they need to create predictable controls with appropriate restrictions for larger CNC machines.

I therefore suggest considering a documentation distinction between features intended primarily for hobby use and features whose use may require additional measures when integrating a machine in accordance with the applicable machinery safety standards.

My intention is not to label LinuxCNC as a whole unsuitable for industrial machines. Quite the opposite. I think it would be a shame if its suitability for such machines were unnecessarily reduced, or if integrators repeatedly had to solve problems that could have been avoided through appropriate software design.

Thank you for the work you are putting into LinuxCNC. I hope these observations will help inform future decisions about how to preserve the benefits of the new architecture without losing capabilities that are important to CNC machine integrators.

@grandixximo

Copy link
Copy Markdown
Contributor

Looked at this against the jog frames in the multiaxis stack (#4374): they combine well. Your pause offset is a per-axis planner on top of the paused position, so a frame jog while paused just folds its step into pause_tp instead of the teleop planners, same box limits.

One thing here, with your drill example in mind: PAUSE_RETURN_LAST returns in machine axes. With a tilted plane (G68.2) or TCP, returning "Z last" in machine Z approaches an angled hole sideways. When a plane is active the last leg should be along the tool axis (retract along the tool, return the rest, plunge back along the tool), which as far as I know is what Heidenhain and Siemens do on a tilted plane. Could the return-last set be expressed in the jog frame, or at least leave a hook for it? Happy to do the frame side as a follow-up once #4644 and this land.

@grandixximo

Copy link
Copy Markdown
Contributor

I'd like to propose a different approach and see if we can find a compromise.

Keep what task does in this PR: task derives what it is running (idle, program, interrupted, MDI), and UIs stop switching modes as a side effect of tabs and buttons. That removes the races and the Manual => MDI => Manual flicker from #4691.

But keep the mode as something the operator selects, not something derived:

  • One selected mode (Manual, MDI, Auto) lives in task, set from a HAL selector or from the UI in use. An inhibit pin lets an integrator lock it to a key switch on the panel.
  • Cycle start acts on the selected mode: Auto runs the open program, MDI runs the pending MDI line, Manual refuses. Entering an MDI line does not start motion by itself.
  • Jog while paused stays as you built it: from Auto interrupted the operator selects Manual, jogs, selects Auto again, and cycle start returns and resumes. From Auto interrupted only Manual and back to Auto are allowed.
  • HAL macros (Unify the way MDI commands are executed from HAL / GUI #4691) run whatever the selected mode is, show up as activity, and leave the selected mode alone.
  • The page a UI shows is the UI's own business, not the mode.

A UI that wants to feel modeless selects Auto itself when its run button is pressed, as halui already does on cycle start. Task has one behaviour, there is no moded and modeless LinuxCNC to maintain, and the integrator decides whether UIs may change the mode at all.

This is the split I sketched on #4571 (#4571 (comment)): what task is running is machine state, the selected mode is operating state shared by every panel and UI, the page is view state.

Open points I have not worked out: joint vs world mode at the transitions, and whether Manual should refuse an MDI sent directly from a UI.

Would that work with the refactor?

@zz912

zz912 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Open points:
whether Manual should refuse an MDI sent directly from a UI.

I am not a professional programmer, so I don't want to prescribe implementation rules. Instead, I would like to describe two use cases that, from my perspective, should behave differently.

An MDI command entered by the operator: If the operator manually types an MDI command into the GUI's MDI command line and submits it to Task while the selected mode is MANUAL, the command should be rejected.

An MDI command predefined by the integrator or developer: If the integrator or developer has deliberately provided a specific MDI command through a dedicated GUI button, such as G43, M6, or M61, it should be allowed to execute in MANUAL mode, provided the integrator has intended and configured that action to be available in this mode.

From the operator's and integrator's perspective, these are two different use cases, even if both ultimately execute an MDI command.

I don't know how this distinction should be implemented internally. My point is simply that selecting MANUAL should prevent arbitrary, operator-entered MDI commands from being executed, without necessarily preventing predefined actions that the integrator has deliberately made available in MANUAL mode.

@grandixximo

Copy link
Copy Markdown
Contributor

@zz912 agreed, that matches what I had in mind.

Task cannot tell a typed line from a button that sends the same text, so I would draw the line by name rather than by text:

That keeps the decision with the integrator, in one place, and every UI gets the same behaviour.

On the joint vs world open point: in the multiaxis stack (#4374) the jog frame (machine, plane, tool) already lives in motion. A joint frame next to them would carry the joint/world choice too, so nothing needs restoring after a run: the frame stays as the operator selected it, and a machine that is not homed jogs in joint until it is, then goes back to the selection. That would be a lane on top of the jog frames, after this PR settles.

@rene-dev

Copy link
Copy Markdown
Member Author

I think we all agree that neither task, nor the UI can provide any performance level, or SIL if you live in the EU.
Maybe you are confusing it with the operating modes mentioned along machine safety, but not a single thing in there is covered by what task does:

  • enforcing speed limits
  • enforcing the door lock
  • permissions and feeds and speeds for setup mode
  • many other things completley unrelated to this

QT action also switches modes on run action:

def RUN(self, line=0):
if not STATUS.is_auto_mode():
self.ensure_mode(linuxcnc.MODE_AUTO)

I think the sentence "a run started from the Manual page runs the program" is a bit misleading:
this is about the NML message, not UI. you still need to send the message.
It still requires the UI to show the button, the UI still can decide when to show the button.
If you look carefully at the changes, the UI behaviour is unchanged, and a fake mode is preserved within the UI.

  • gmoccapy only shows btn_run on the Auto page.
  • qtdragon's btn_start_clicked refuses with "Must be in AUTO mode" via STATUS.is_auto_mode(). On the branch that reads the per-process view_mode, so it still follows qtdragon's own tab.
  • In both, a run from the Manual page is still refused.
  • external run commands(qtaction, halui) behaviour is unchanged, on master it also runs the program, regardless of the mode.

@zz912

zz912 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

I agree that neither Task nor a GUI, by itself, guarantees a Performance Level or SIL. However, I think there is an important distinction here.

PL is assessed for the safety-related parts of a control system performing a specific safety function. The fact that LinuxCNC uses a general-purpose operating system and GUI does not automatically rule out achieving PL = a for a particular function. Of course, this would depend on the complete implementation, its architecture, and proper validation. I am not claiming that LinuxCNC or Gmoccapy is safety-certified.

My concern is also about operational interlocking and giving the integrator the ability to define predictable machine behavior, rather than claiming that the Task's mode selection is itself a safety function.

There is another practical issue with the Gmoccapy example. Hiding btn_run on the Auto page only restricts the button provided by that particular GUI. It does not prevent a physical Cycle Start button connected through HAL from starting a program.

The LinuxCNC documentation for remote program start explicitly describes connecting a physical button to halui.program.run, together with halui.mode.auto and a check of halui.mode.is-auto. In that arrangement, pressing the physical button can request Auto mode and start the program regardless of which page Gmoccapy is displaying.

I understand your point that external run commands can already start a program regardless of the mode in the current master branch, so I am not claiming that this behavior is necessarily a regression introduced by this PR. However, it illustrates why the GUI's selected page and the machine's operating mode should not be treated as interchangeable concepts.

My concern is that removing the existing mode-selection mechanism and its HAL pins may make it harder for integrators to implement consistent mode-dependent behavior across the GUI and physical controls.

I would therefore support separating Task activity from the selected operating mode, as discussed above, while retaining an explicit mechanism that allows the integrator to enforce mode-dependent command permissions for all command sources, including physical controls. This would preserve the flexibility of the new architecture without unnecessarily limiting the integrator's options.

@rene-dev

Copy link
Copy Markdown
Member Author

I went through every place in the tree that sends an MDI command or starts a
program, to see what the task mode actually blocks on master and what changes with this PR.

What master's task does

  • MDI is refused unless the mode is MDI ("Must be in MDI mode to issue MDI command").
  • A program run is ignored without any message in Manual, refused in MDI, and accepted in Auto.

Clients that switch the mode themselves, so the check never stopped them

halui (program.run switches to Auto first, mdi-command to MDI), Axis, all of qtvcp's ACTION (RUN, CALL_MDI*, CALL_OWORD, offsets), gladevcp's actions, MDI history and offset page, touchy, gscreen, pyui, plasmac2, the touch-off and tool buttons in gmoccapy, emcsched and axis/scripts/mdi.py.

This also means a physical RUN button wired to halui.program.run already starts the program from Manual on master.

Clients that block it in their own UI

  • gmoccapy: the run button only exists on the Auto page, R only works in Auto, and macros and the MDI entry only work in MDI.
  • qtdragon: "Must be in AUTO mode to run a program", unless auto mode switching is enabled.
  • tklinuxcnc: the run keys are only bound in Auto.

These checks still work with this PR. The mode a GUI selects is kept per process and reported back to it in stat.task_mode while task is idle. A run from gmoccapy's or qtdragon's Manual page is still refused.

What does change

  • Clients that never set a mode and relied on task to refuse or ignore them now run:
    • emclcd's start key;
    • tklinuxcnc's Run/Verify buttons and emc_mdi/emc_run in general;
    • linuxcncrsh SET RUN/SET MDI;
    • the raw gcode()/ocode() calls of the gladevcp probe panels;
    • a few silverdragon buttons;
    • out-of-tree scripts and pendants that call auto(AUTO_RUN) or mdi() without mode() first.
  • A HAL interlock that gates halui.program.run on halui.mode.is-auto, with the mode selected through halui.mode.manual/auto, can no longer be built. The mode request pins are gone, and is-auto is only true while a program runs.

So you have a point: for these clients and for HAL-based interlocks, Manual did act as a gate. It was an incomplete gate, though, because any client that switches the mode itself (halui included) went straight past it.

Proposal

Rather than keeping the modes as an option, I'd add an explicit run permit to task, e.g. a halui.program.run-permit input pin that defaults to true. While it is false, task refuses program runs (and optionally MDI) from every client with an error, instead of master's silent ignore.

A mode selector or keyswitch can be wired to it, which gives the "Manual breaks the cycle-start chain" behaviour. It is stronger than master, because it also covers halui, every GUI and every script. GUIs like gmoccapy could also drive it from their Manual page if that is wanted.

To be clear, neither the old mode nor this pin is a safety function. Those still need safety-rated hardware. But it restores, and extends, the protection against unintended program starts that you are asking for.

Would that address your concerns?

@rene-dev

Copy link
Copy Markdown
Member Author

@zz912 you were one of the people complaining about the problems with mode switching(#4691) I dont understand why you now suddenly think they are a good thing?

@grandixximo

grandixximo commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

@rene-dev thanks for going through every client, that inventory is useful. I agree with the conclusion that the master gate was incomplete: any client that switches the mode itself goes straight past it.

To compare against something concrete, I had Claude build the selected-mode version on top of this branch: grandixximo:task-selected-mode, two commits on task-modeless (diff against this PR). It keeps your run tracking, the planner tables, isImmediate(), jog while paused and the return on resume; what runs is reported apart from the mode, in stat.task_run (RUN_NONE, RUN_PROGRAM, RUN_MDI).

  • EMC_TASK_SET_MODE and halui.mode.manual, .mdi, .auto are back, and the mode is the one selected. Manual allows jogs and homing, MDI typed MDI commands, Auto program runs and steps. [HALUI]MDI_COMMAND runs in any mode and leaves the mode alone, so the Manual => MDI => Manual flicker from Unify the way MDI commands are executed from HAL / GUI #4691 is gone.
  • While paused, only Manual (to jog) or the mode the run started in (to resume or step) can be selected; jogs while paused need Manual.
  • halui.mode.lock closes the gap you describe: while set, only the halui mode pins change the mode, so no GUI, script or halui.program.run can switch it, and a run from any client is refused outside Auto, with an error.
  • [MDI_MACROS] (NAME, COMMAND, MODES): MDI commands run by name, only in the modes they list, from c.macro(name) or a halui.macro.<name> pin. This is zz912's split between typed MDI and integrator-defined commands.
  • AXIS selects Manual for a jog while paused and the mode of the run to resume. The full test suite passes (apart from tests/blendmath, the GCC 14 failure fixed on master), ui-smoke included.

On the run permit: with the selector wired to the mode pins and the lock set, Manual is your run-permit = false, for every client, as you propose. What the mode covers beyond that:

  • one selected mode shared by every UI, panel and pendant, where the per-process view lets two UIs and a halui panel each believe a different mode;
  • the line between typed MDI and the integrator's macros, which a permit pin cannot draw by itself;
  • what the jogs and the wheel may do while paused, and which mode resumes.

If the selected mode is still too much, I would at least want the permit to come with the macro list, so that the integrator decides per command what runs in Manual.

Choices I made that I would like input on, from you, @zz912, @c-morley, @NoGare and anyone else:

  1. Jogs outside Manual are refused. Master accepted them in MDI and Auto while idle.
  2. The names: halui.mode.lock, [MDI_MACROS], halui.macro.<name>, c.macro(), stat.task_run.
  3. The lock acts on edges: a selector turned while a program runs is refused and not applied once the run ends. Should task follow the held pin instead?
  4. The macro table: NAME/COMMAND/MODES triples, or closer to zz912's proposal on ZMQ messages from HALUI to GUIs #3580 with the GUI and HAL options per macro?

@zz912

zz912 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Hi René,

I managed to get access to ISO 16090-1 and review it again.
https://fliphtml5.com/agpqk/pmzh/ISO_16090-1_2022(en)
I realized that some of the things I wrote earlier were not accurate, so I would like to apologize for the confusion. I should have verified the relevant clauses more carefully before making those statements.

I am getting a bit lost in the details now, so I think it is best for me to step back and leave the discussion here. My thoughts are largely aligned with grandixximo's proposal, so I will leave it to him to continue exploring that approach with you.

Just to clarify one important point in response to the discussion: I do not have a problem with mode switching in general. My concern is with automatic mode switching as a side effect of UI actions or commands, because it can introduce race conditions and make the system's behavior harder to predict.

Thanks for your work on LinuxCNC, and sorry again for the confusion caused by my earlier comments.

@rene-dev

Copy link
Copy Markdown
Member Author

@grandixximo so the mode is still in task, but some commands can ignore it? that seems like a bad idea, and also thats a lot of added code, for really no purpose. remember that the flicker is gone, if the UI tab is decoupled.

@andypugh

Copy link
Copy Markdown
Collaborator

In the current behavior, being in Manual mode also provides a useful operational safeguard: a program cannot be started while the machine is in Manual mode.

This is not the case with Touchy. If Touchy has a program loaded it will run it regardless of which UI tab is showing.
I have had a frustrating (and destructive) evening trying to use my lathe macros (with re-worked "am I active" logic in the handler) with a G-coefficient program loaded. It was impossible to predict whether it would run the G-code or the macro. The results, however, of this situation were as one might predict.

Touchy is a special case, though, as that has a touchy.cycle-start button.

I have not looked at the behaviour of halui.program-run, but I think that René is suggesting that this has always automatically switched to auto mode?

@grandixximo

Copy link
Copy Markdown
Contributor

@andypugh yes. On master halui.program.run switches to Auto and runs (halui.cc), and so does Touchy's cycle start (emc_interface.py). So for those, Manual never blocked a run.

@rene-dev you are right about the flicker: decoupling the tab fixes it too. And you are right that commands skipping the mode is bad. That is my mistake: [HALUI]MDI_COMMAND should go through the same check, as a macro allowed in every mode, so it works as today but nothing skips the check.

What I think is still missing in this PR:

  1. The mode moves into each client. view_mode in emcmodule.cc, viewMode in emcsh.cc, viewmode per connection in emcrsh.cc: three copies today, and one more in every client of the NML replacement. A pendant on halui has none at all. Two UIs and a pendant can each show a different mode, which is the kind of state we agreed in NML replacement #4571 should not live in the UI.
  2. What cycle start does is decided by each UI. Andy's evening is what that looks like: the UI handler had to guess whether to run the program or the macro. With the mode in task and locked to a selector, cycle start in Manual is refused for every client, and the macros listed for Manual run.
  3. A run permit pin solves the run case. MDI the operator types versus the integrator's macros, jogs in Auto, and which mode resumes a paused run would each need another pin, and then we have the modes again, spread over HAL pins.

To make it smaller to review, I can split my branch: the mode in task alone, and the macros as a separate PR for #4691.

@gmoccapy

gmoccapy commented Oct 19, 2026 via email •

Copy link
Copy Markdown
Collaborator

@grandixximo

Copy link
Copy Markdown
Contributor

@gmoccapy I agree with the goals: one way to do each thing, the same pin names and behaviour in every GUI, and more security. I think those goals are the reason to keep the mode in task, not in each GUI:

  • A rule in a GUI only covers that GUI. halui, scripts, linuxcncrsh, a pendant, a remote or web UI all go around it, as you say yourself. Only task sees every command, so only task can enforce "no MDI outside MDI mode" for all of them.
  • "Same pins and behaviour in every GUI" is easiest if it is written once, in task, instead of once per GUI. Otherwise switching GUI means rewiring the panel, and with no GUI running the panel does nothing.
  • Other things read the mode too. The MTConnect adapter in the tree reports stat.task_mode as the controller mode (lcnc_source.py). With the mode inside a GUI it cannot know it.

I also looked at how other controllers handle this. They are not LinuxCNC, but they have solved the same problem, and they keep the mode in the controller, with the UIs asking for changes.

What should stay in the GUI: what it shows, which buttons exist, and its own checks and dialogs before a command. I ran gmoccapy on my branch (grandixximo:task-selected-mode), pressing its own buttons through its hardware button pins, with no change to gmoccapy: its Manual, MDI and Auto buttons select the mode in task, a gmoccapy macro runs in MDI and leaves it there, the run button on the Auto page runs the program, and halui.mode.manual from outside switches gmoccapy to its Manual page, where a run from any other client is then refused by task.

Concretely, this does not have to wait for the NML replacement:

  1. Now, with NML: the selected mode (my branch), the jog state shared by halui and the GUIs (Unify the way jogging works from HAL, and its interaction with the UI #4692), and, as an idea not built yet, a check in task before a run that is the same for every client, for example a subroutine named in the INI that can refuse the run.
  2. With the NML replacement: task asking the GUI in use a question and waiting for its answer, for checks that need the operator. NML has no way to send something to one client and get its answer back, so that part needs the new transport.

@gmoccapy

Copy link
Copy Markdown
Collaborator

Rather than keeping the modes as an option, I'd add an explicit run permit to task, e.g. a halui.program.run-permit input pin that defaults to true. While it is false, task refuses program runs (and optionally MDI) from every client with an error, instead of master's silent ignore.

A mode selector or keyswitch can be wired to it, which gives the "Manual breaks the cycle-start chain" behaviour. It is stronger than master, because it also covers halui, every GUI and every script. GUIs like gmoccapy could also drive it from their Manual page if that is wanted.

I like this Idea, but ....
IMHO we only need two modes: Manual and MDI should be one mode and the second one is auto.

@rene-dev : Please grant me a wish! Do not name hal pin from task halui.programm.run-permit or similar. I hate halui, the way it is working at the moment, because i causes problems if mixed with gmoccapy. If we, better you, make thinks new and hopefully better, I would recommend to change the hal pin names, knowing that this will brake existing configs, but will make clear stuff had a huge change. As the new concept (I hope I did understand correct) will be the way a gui interacts with task, so I do recommend a hal name like gui2task, at least different to halui.

Norbert

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.

7 participants