Skip to content

Power the motors down after 1 h idle, with a re-zero wizard to bring them back #81

Description

@BernardJen

Summary

The steppers are energized 24/7, holding current on a machine that is idle most of the
time. Turn them off automatically after a configurable idle period (default 1 h). Because
powering them down frees the gantry — and this machine has no way to find home again — the
next use must go through a re-zero wizard: move the head to the paper's top-left corner,
set the position, motors back on.

Motivation

Nothing ever de-energizes the motors. motorsOff()
(src/grbl/GrblController.ts:640, FluidNC $MD) exists only
behind a manual button (src/ui/App.tsx:1928), and there is no idle
timer, no keep-alive setting, and no controller-side idle disable configured anywhere in the
daemon. An unattended plotter therefore sits at full holding current indefinitely — wasted
power, needless heat in the drivers and motors, coil whine in the room, and wear for no work
done. The machine is idle far more than it plots.

Why this needs a wizard

Turning the motors off is the easy half. The consequence is the hard half: this machine has
no limit switches and no homing ($22=0, no $H — AGENTS.md), so the work origin exists
only because the operator put it there by hand, and the energized steppers are the only thing
holding the gantry in place. Once they drop out the gantry can be nudged or sag on the long
axis, and FluidNC has no encoder feedback — it keeps reporting the pre-shutdown coordinates
whatever the gantry actually did. The reported position becomes a claim, not a measurement.

The daemon currently has no way to express that:

  • posReady (gateway/server.ts:98) is the flag whose entire job is
    "the position is trustworthy". It is cleared on disconnect
    (gateway/server.ts:387) and set again by a restore
    (gateway/server.ts:244) or by Calibrate
    (gateway/server.ts:534) — but nothing clears it while the daemon
    stays connected
    , which is exactly the case a motor power-down creates. Note the
    motorsOff handler (gateway/server.ts:540) sits two cases below
    setWorkZero, which does posReady = true; persistState(); the new path is its mirror
    image and belongs in the same switch.
  • persistState (gateway/server.ts:102) keeps writing the position
    to disk, and on the next connect restoreSavedPosition
    (gateway/server.ts:233) feeds it back via G10 L20 and reinstates
    it as the work origin.

So adding auto-off without the trust handling would be actively worse than leaving the motors
on: the daemon would confidently restore an origin recorded before the gantry was free to move,
and with soft limits disabled per-axis (gateway/server.ts:452) nothing
stops a plot from driving into the frame. The two halves ship together.

The same applies to the existing Motors off button, which frees the gantry deliberately
(its doc comment: "so the gantry can be moved by hand") and today marks nothing stale. It
should feed the same wizard.

Proposal

1. Idle auto-off

  • Track time since the last commanded motion in the daemon; after the configured idle period,
    issue $MD. Default 1 h, configurable (belongs with shared settings, Persist app settings on the gateway (shared across clients) #14 / Settings page Move machine, pen and drawing settings off the main page into a Settings page #57).
  • Never fire mid-job. Beyond the obvious Run/queued cases, a pen-change hold is
    deliberately Idle with an empty queue and no motion
    (isPlotting, gateway/server.ts:308, and
    src/ui/App.tsx:146)
    and is explicitly exempt from the 20 s stall watchdog
    (src/ui/App.tsx:87). A job waiting for a hand must not be counted as
    idle: dropping the motors there would shift the gantry mid-plot and the rest of the drawing
    would land offset on a half-finished sheet. There is no resume and no second sheet. isPlotting — today used only by the self-update guard
    (gateway/server.ts:614) — already encodes every one of these cases
    and is the right basis for the idle timer.
  • Consider an explicit Keep motors on override for a session where the operator is stepping
    away mid-setup and wants the origin preserved.

2. Position becomes untrusted when the motors drop

  • On auto-off (and on manual Motors off): clear posReady, stop persisting, and make sure
    a position recorded on the wrong side of a shutdown is never restored as the origin — the
    saved state needs a flag, or the file must be invalidated at shutdown.
  • Surface it in the snapshot and as a gateway event (src/gateway/protocol.ts) so every
    connected client agrees. One machine, one truth.

3. Gate motion while untrusted

  • Refuse Plot, Go to home, and the registration wizard's jog-and-Set steps, enforced in
    the gateway rather than the UI — the browser is a thin client. Jogging stays allowed (the
    operator may need it to reach the corner) but its coordinates mean nothing until re-zeroed.
  • Refusals name the reason ("motors powered down after 1 h idle — home is lost"), never fail
    silently.

4. Re-zero wizard

Opens when the operator next tries to use the machine, and from the Home/calibration panel:

  1. Explain that the motors were powered down and home is gone.
  2. State that the motors are off — the operator is about to push the gantry and needs to know
    it will not fight back.
  3. Move the head to the paper's top-left corner (by hand, or jog once re-enabled).
  4. SetsetWorkZero() (src/grbl/GrblController.ts:614),
    the same call the Calibrate button already makes.
  5. Confirm: motors energized, position trusted, persistence resumed.

The Home/calibration panel already tells the operator exactly this sequence in prose
(src/ui/App.tsx:1907) — the wizard makes it mandatory instead of
advisory. Model the interaction on the registration wizard
(src/ui/RegistrationWizard.tsx), which already does
jog-to-a-point-and-press-Set well.

Acceptance criteria

  • Motors power down automatically after a configurable idle period; default 1 h.
  • Auto-off never fires while a plot is running, paused, or held at a pen change.
  • Auto-off and manual Motors off both mark the position untrusted.
  • While untrusted the gateway refuses Plot / Go to home with a reason, for every connected
    client — not just the one that saw the event.
  • A position recorded after a motor shutdown is never silently restored as the work origin.
  • The re-zero wizard walks the corner-and-Set sequence and restores trusted state on
    completion.
  • Unit tests: idle timer vs. the busy predicate (including the pen-change hold), and the
    trust state machine (idle → untrusted → re-zeroed → trusted).
  • Hardware-verified: motors audibly drop out after the timeout, the gantry is free, the
    wizard blocks plotting until home is reset, and a plot after re-zeroing lands correctly
    (leave unchecked until the operator confirms on the machine — AGENTS.md).

Open question

FluidNC can disable idle steppers itself (a controller-side idle timeout) rather than the
daemon sending $MD on a timer. Controller-side is simpler and survives a daemon restart, but
the daemon then has to detect the dropout to invalidate the origin, and it is not clear
FluidNC reports motor-enable state in its status. Daemon-driven $MD means the daemon always
knows exactly when it happened. Leaning daemon-driven for that reason — worth confirming
against the machine's config.

Relationship

Touches work-coordinates and machine-status (openspec/specs/). Timeout setting relates to #14
(shared settings) and #57 (Settings page). Reuses the wizard pattern from the registration
wizard (v1.2.0). Part of epic #10.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions