Skip to content

feat(machine): repeatable work zero via homing (save/restore offset)#242

Merged
fixcik merged 2 commits into
masterfrom
feat/repeatable-zero
Jun 5, 2026
Merged

feat(machine): repeatable work zero via homing (save/restore offset)#242
fixcik merged 2 commits into
masterfrom
feat/repeatable-zero

Conversation

@fixcik

@fixcik fixcik commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Повторяемый рабочий ноль через базирование — чтобы не выставлять 0 джогом каждый раз при той же оснастке.

Что внутри

  • Профиль CNC хранит workZeroMm: {x,y}|null — машинные XY рабочего нуля (только XY; Z всегда touch-off вручную).
  • «Сохранить ноль» (страница «Станок», блок DRO): пишет текущие машинные XY рабочего нуля (MPos−WPos) в профиль.
  • «Базирование + восстановить ноль»: $H → дождаться IdleG10 L2 P1 X Y (восстанавливает G54-офсет в сохранённую точку). Если homing не стартовал — офсет НЕ применяется (ошибка).
  • Детект homing по прошивке: на коннекте шлём $$, парсим $22; при $22=0 восстановление неактивно с подсказкой (показывается только если ноль сохранён).
  • Чистые хелперы lib/workZero.ts (workZeroFromStatus/parseHomingEnabled/restoreZeroGcode) + юнит-тесты.

Независимое ревью — APPROVE WITH NITS, обе правки внесены (guard на старт базирования; подсказка homing только при сохранённом нуле). pnpm test (324) + pnpm build зелёные. Проверка на железе — чек-лист в issue.

Refs #240

Summary by CodeRabbit

Новые возможности

  • Сохранение и восстановление нулевой позиции: добавлены кнопки для сохранения текущей рабочей нулевой точки и её восстановления с отображением сохранённых координат XY
  • Контроль доступности: функция восстановления активируется только при наличии сохранённой позиции и доступности процедуры хоминга
  • Локализация: добавлены переводы для операций с нулём на русском и английском языках

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Функциональность сохранения и восстановления рабочего нуля расширяет ЧПУ интерфейс: добавлены утилиты для расчета нулевой точки и обнаружения поддержки homing в прошивке, машинное состояние отслеживает параметр $22, профиль хранит координаты work-zero, асинхронная процедура восстановления выполняет полный цикл хоминга, UI компонент предоставляет кнопки и обратную связь, а многоязычная локализация поддерживает интерфейс.

Changes

Функциональность сохранения и восстановления work-zero

Layer / File(s) Summary
Базовые утилиты для работы с work-zero
cuprum-ui/src/lib/workZero.ts, cuprum-ui/src/lib/workZero.test.ts
Добавлены три экспортируемые функции: workZeroFromStatus() вычисляет XY нулевую точку из координат машины и рабочей точки, parseHomingEnabled() парсит строку параметра $22 из прошивки, restoreZeroGcode() генерирует G-code команду G10 L2 P1 с координатами. Полное покрытие unit-тестами с проверкой граничных случаев.
Расширение CNC профиля для хранения work-zero
cuprum-ui/src/lib/cncProfile.ts, cuprum-ui/src/lib/drillGcode.test.ts, cuprum-ui/src/lib/drillProgram.test.ts
Добавлено поле workZeroMm: { x: number; y: number } | null в интерфейс CncProfile для персистентного хранения сохраненных координат с инициализацией null, обновлены тестовые helpers для включения нового поля в объекты профилей.
Отслеживание поддержки homing в машинном состоянии
cuprum-ui/src/machineStore.ts
Добавлено состояние homingAvailable: boolean в useMachine store, реализована логика запроса параметров прошивки через команду $$ после подключения, парсинг входящих текстовых строк для выявления параметра $22=1, сброс флага при отключении.
Асинхронная процедура восстановления work-zero
cuprum-ui/src/lib/restoreWorkZero.ts
Реализована функция restoreWorkZero(z) выполняющая последовательность: отправка команды $H, ожидание смены состояния машины из idle/jog в home/run, ожидание возврата в idle/jog с таймаутом, обработка ошибок (alarm, timeout, отсутствие старта), отправка G-code восстановления координат.
UI элементы DRO для управления work-zero
cuprum-ui/src/components/machine/Dro.tsx
Добавлены импорты утилит и useSettings hook, реализованы обработчики handleSaveZero (сохранение текущих координат в профиль) и handleRestoreZero (асинхронное восстановление с публикацией ошибок), вычисляется флаг canRestore, добавлены кнопки сохранения/восстановления, условное отображение сохраненных координат и предупреждение о недоступности homing.
Многоязычная локализация UI
cuprum-ui/src/locales/en/machine.json, cuprum-ui/src/locales/ru/machine.json
Добавлены ключи локализации в разделе dro: названия кнопок saveZero и restoreZero, сообщение zeroSaved с подстановкой координат {{x}} и {{y}}, сообщение homingUnavailable о необходимости включения homing в прошивке через параметр $22=1.

Sequence Diagram

sequenceDiagram
    participant User as Пользователь
    participant DRO as Компонент Dro
    participant Profile as CncProfile
    participant MachineStore as machineStore
    participant API as api.machine
    participant Firmware as Прошивка ЧПУ
    
    rect rgb(200, 150, 255)
    Note over User,Firmware: Сценарий: сохранение work-zero
    User->>DRO: нажимает "Сохранить ноль"
    DRO->>MachineStore: получает workZeroFromStatus()
    MachineStore-->>DRO: координаты {x, y}
    DRO->>Profile: setCncProfile({workZeroMm: {x, y}})
    Profile-->>DRO: сохранено
    DRO-->>User: показывает "Ноль сохранен X/Y"
    end
    
    rect rgb(150, 200, 255)
    Note over User,Firmware: Сценарий: восстановление work-zero
    User->>DRO: нажимает "Восстановить ноль"
    DRO->>DRO: проверяет canRestore (connected, homingAvailable, workZeroMm, movable)
    DRO->>API: restoreWorkZero({x, y})
    API->>Firmware: отправляет $H (home)
    Firmware-->>MachineStore: status state = "home"
    MachineStore-->>API: ожидает состояние "idle/jog"
    Firmware-->>MachineStore: status state = "idle"
    MachineStore-->>API: готово
    API->>Firmware: отправляет G10 L2 P1 X/Y
    Firmware-->>DRO: выполнено
    DRO-->>User: "Ноль восстановлен" или Error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Machine: repeatable work zero via homing (save/restore offset) #240: PR реализует функциональность сохранения и восстановления work-zero, описанную в issue — добавляет поле CncProfile.workZeroMm, утилиты workZero.ts, функцию restoreWorkZero, поддержку homingAvailable в машинном состоянии, и UI элементы в компоненте Dro для выполнения полного цикла $H → ожидание Idle → G10 L2 P1 X/Y.

Poem

🐰 Вот кролик взмахнул лапкой волшебной—
Ноль сохранен в профиль прошел!
С хомингом дружит асинхронный синтаксис,
G-code танцует, координаты светлы...
Восстановленье work-zero поёт! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Заголовок точно описывает основное изменение: добавлена функциональность сохранения и восстановления нулевой точки работы через процедуру хоминга.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
cuprum-ui/src/lib/workZero.test.ts (1)

4-20: 💤 Low value

Опционально: добавить тесты для граничных случаев

Если в workZeroFromStatus будет добавлена проверка границ массивов (как предложено в обзоре workZero.ts), стоит добавить соответствующий тест, проверяющий выброс ошибки при передаче коротких массивов.

🧪 Пример дополнительного теста
it("throws on short arrays", () => {
  expect(() => workZeroFromStatus([10], [0, 0, 0])).toThrow();
  expect(() => workZeroFromStatus([10, 20, 0], [0])).toThrow();
  expect(() => workZeroFromStatus([], [])).toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cuprum-ui/src/lib/workZero.test.ts` around lines 4 - 20, Add tests to
cuprum-ui/src/lib/workZero.test.ts that verify workZeroFromStatus throws when
passed short arrays: call workZeroFromStatus with a single-element mpos or wpos
(e.g., [10], [0,0,0] and [10,20,0], [0]) and with empty arrays and assert they
throw; reference the existing describe block for workZeroFromStatus and add an
it("throws on short arrays", ...) that contains these expect(...).toThrow()
checks to cover the boundary validation proposed in workZero.ts.
cuprum-ui/src/lib/workZero.ts (1)

3-8: ⚡ Quick win

Рекомендация: добавить проверку границ массивов

Функция обращается к индексам [0] и [1] массивов mpos и wpos без проверки их длины. Хотя в нормальной работе MachineStatus всегда содержит 3-элементные массивы, при некорректном парсинге данных прошивки возможен доступ к несуществующим элементам, что приведёт к NaN в результате. Эти NaN будут сохранены в профиль и при восстановлении отправлены в станок как G10 L2 P1 XNaN YNaN, что может вызвать непредсказуемое поведение.

Рекомендуется добавить защитную проверку для повышения надёжности.

🛡️ Предлагаемое исправление
 export function workZeroFromStatus(
   mpos: readonly number[],
   wpos: readonly number[],
 ): { x: number; y: number } {
+  if (mpos.length < 2 || wpos.length < 2) {
+    throw new Error("mpos and wpos must have at least 2 elements (X, Y)");
+  }
   return { x: mpos[0] - wpos[0], y: mpos[1] - wpos[1] };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cuprum-ui/src/lib/workZero.ts` around lines 3 - 8, The function
workZeroFromStatus reads mpos[0], mpos[1], wpos[0], wpos[1] without bounds
checks which can produce NaN if arrays are short; update workZeroFromStatus to
validate the lengths (or individual elements) for mpos and wpos and fall back to
safe numeric defaults (e.g. 0) or coerce to numbers when an index is missing or
not a finite number so the returned {x,y} are always numeric; locate and update
the workZeroFromStatus function to perform these checks before computing x and
y.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cuprum-ui/src/lib/restoreWorkZero.ts`:
- Around line 21-29: When the GUARD_MS timeout elapses in restoreWorkZero (use
symbols GUARD_MS, guardStart, st), do not fall through to the completion wait
for non-homing states; instead treat any state other than "home" or "run" as a
failure and abort so restoreZeroGcode() is never executed on an un-homed
machine. Change the branch that currently does "break" for states like
"door"/"hold"/"check"/"sleep"/"unknown" to throw an error (or return a clear
failure) with a descriptive message (e.g. "homing did not start/completed") so
only a successful homing path reaches the completion wait and eventual call to
restoreZeroGcode().

---

Nitpick comments:
In `@cuprum-ui/src/lib/workZero.test.ts`:
- Around line 4-20: Add tests to cuprum-ui/src/lib/workZero.test.ts that verify
workZeroFromStatus throws when passed short arrays: call workZeroFromStatus with
a single-element mpos or wpos (e.g., [10], [0,0,0] and [10,20,0], [0]) and with
empty arrays and assert they throw; reference the existing describe block for
workZeroFromStatus and add an it("throws on short arrays", ...) that contains
these expect(...).toThrow() checks to cover the boundary validation proposed in
workZero.ts.

In `@cuprum-ui/src/lib/workZero.ts`:
- Around line 3-8: The function workZeroFromStatus reads mpos[0], mpos[1],
wpos[0], wpos[1] without bounds checks which can produce NaN if arrays are
short; update workZeroFromStatus to validate the lengths (or individual
elements) for mpos and wpos and fall back to safe numeric defaults (e.g. 0) or
coerce to numbers when an index is missing or not a finite number so the
returned {x,y} are always numeric; locate and update the workZeroFromStatus
function to perform these checks before computing x and y.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2095f168-7a25-4fe6-8d40-5846fc189db3

📥 Commits

Reviewing files that changed from the base of the PR and between d0d6dda and 705eef8.

📒 Files selected for processing (10)
  • cuprum-ui/src/components/machine/Dro.tsx
  • cuprum-ui/src/lib/cncProfile.ts
  • cuprum-ui/src/lib/drillGcode.test.ts
  • cuprum-ui/src/lib/drillProgram.test.ts
  • cuprum-ui/src/lib/restoreWorkZero.ts
  • cuprum-ui/src/lib/workZero.test.ts
  • cuprum-ui/src/lib/workZero.ts
  • cuprum-ui/src/locales/en/machine.json
  • cuprum-ui/src/locales/ru/machine.json
  • cuprum-ui/src/machineStore.ts

Comment on lines +21 to +29
if (Date.now() - guardStart > GUARD_MS) {
// Homing never started within the guard window. If the machine is still
// idle/jog, $H was effectively a no-op (e.g. rejected) — refuse to apply
// the offset on an un-homed machine (would set a wrong datum).
if (st === "idle" || st === "jog") {
throw new Error("homing did not start");
}
break; // left idle (some other state) — fall through to the completion wait
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

GRBL state machine behavior when homing command fails or is rejected in non-idle states

💡 Result:

In GRBL, the system state is managed by the sys.state variable, which enforces specific operational restrictions [1][2]. The $H (homing) command is designed to be executed only when the system is in an IDLE or ALARM state [3][4]. If a homing command is issued while the system is in a non-idle, restricted state (such as CYCLE or HOLD), the command will typically be rejected, and the system will return an error, commonly error: 8 (Not idle) [5][6]. A critical historical issue existed in the GRBL state machine regarding homing failures: if the system state was updated to STATE_HOMING before validating the entire command or its execution requirements, the controller could become "stuck" in the homing state [3]. In such cases, because the system incorrectly perceived itself as still performing a homing cycle, it would reject subsequent commands, including further $H attempts, as it was not in the required IDLE or ALARM state [3]. Modern implementations of GRBL have addressed this by ensuring that the sys.state is only transitioned to STATE_HOMING after all initial safety checks and command validations are successfully completed [3]. If a failure occurs during the homing process itself (e.g., limit switch failure, abort, or timeout), the system typically transitions into an ALARM state [4][2]. Once in an ALARM state, most operations are locked until the user acknowledges the alarm (e.g., via a system reset or an $X override command) and successfully completes a homing cycle [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant portion of restoreWorkZero.ts
FILE="cuprum-ui/src/lib/restoreWorkZero.ts"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

# Show lines around the guard phase and also the completion wait / where offset is applied
sed -n '1,200p' "$FILE" | nl -ba | sed -n '1,200p' || true

# Narrow search for the specific guard/completion logic
rg -n "guard|GUARD_MS|completion|work[- ]zero|offset|homing did not start|\$H" "$FILE" -S || true

# Find where MachineStateName is defined (or imported) and what values exist
rg -n "MachineStateName" "cuprum-ui/src/lib/restoreWorkZero.ts" -S || true

Repository: fixcik/cuprum

Length of output: 605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="cuprum-ui/src/lib/restoreWorkZero.ts"
echo "=== file stats ==="
wc -l "$FILE"

echo "=== file head (with line numbers via awk) ==="
awk '{printf "%5d: %s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo "=== exact guard/completion/offset parts ==="
rg -n "guardStart|GUARD_MS|completion|work[- ]zero|offset|homing|G54|\$H|MachineStateName" "$FILE" -S

echo "=== find MachineStateName definition/usages ==="
rg -n "type\s+MachineStateName|enum\s+MachineStateName|MachineStateName" "cuprum-ui/src" -S

echo "=== print any state mapping/table near MachineStateName (if found) ==="
# best-effort: show surrounding lines for each match
rg -n "MachineStateName" "cuprum-ui/src" -S | cut -d: -f1 | sort -u | head -n 20 | while read -r f; do
  echo "--- $f ---"
  awk '{printf "%5d: %s\n", NR, $0}' "$f" | sed -n '1,260p'
done

Repository: fixcik/cuprum

Length of output: 17253


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="cuprum-ui/src/lib/workZero.ts"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

wc -l "$FILE"
echo "=== file excerpt (with line numbers) ==="
awk '{printf "%5d: %s\n", NR, $0}' "$FILE" | sed -n '1,260p'

echo "=== search for restoreZeroGcode implementation ==="
rg -n "restoreZeroGcode|G54|offset|work.*zero|homing|\\$H|GRBL" "$FILE" -S

Repository: fixcik/cuprum

Length of output: 1189


Guard timeout не должен пропускать в completion wait при состояниях, отличных от home/run

В cuprum-ui/src/lib/restoreWorkZero.ts при истечении GUARD_MS (строки ~21-29) ошибка выбрасывается только если st === "idle" || st === "jog", а при door/hold/check/sleep/unknown выполняется break, затем в completion wait смещение всё равно применяется после возврата в idle/jog. restoreZeroGcode() без дополнительных проверок отправляет G10 L2 P1 ..., то есть это может привести к установке G54 на незахомированной машине.

Это особенно рискованно для GRBL: $H обычно разрешён только в IDLE/ALARM, а в прочих состояниях он отклоняется (например, error: 8 Not idle) и контроллер может позже вернуться в IDLE без успешного хоминга — текущая логика может ошибочно трактовать это как завершение.

Предлагаемая более строгая проверка
   if (Date.now() - guardStart > GUARD_MS) {
-    // Homing never started within the guard window. If the machine is still
-    // idle/jog, $H was effectively a no-op (e.g. rejected) — refuse to apply
-    // the offset on an un-homed machine (would set a wrong datum).
-    if (st === "idle" || st === "jog") {
+    // Homing must have entered home/run state within the guard window.
+    if (st !== "home" && st !== "run") {
-      throw new Error("homing did not start");
+      throw new Error(`homing did not start (state: ${st})`);
     }
     break; // left idle (some other state) — fall through to the completion wait
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Date.now() - guardStart > GUARD_MS) {
// Homing never started within the guard window. If the machine is still
// idle/jog, $H was effectively a no-op (e.g. rejected) — refuse to apply
// the offset on an un-homed machine (would set a wrong datum).
if (st === "idle" || st === "jog") {
throw new Error("homing did not start");
}
break; // left idle (some other state) — fall through to the completion wait
}
if (Date.now() - guardStart > GUARD_MS) {
// Homing must have entered home/run state within the guard window.
if (st !== "home" && st !== "run") {
throw new Error(`homing did not start (state: ${st})`);
}
break; // left idle (some other state) — fall through to the completion wait
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cuprum-ui/src/lib/restoreWorkZero.ts` around lines 21 - 29, When the GUARD_MS
timeout elapses in restoreWorkZero (use symbols GUARD_MS, guardStart, st), do
not fall through to the completion wait for non-homing states; instead treat any
state other than "home" or "run" as a failure and abort so restoreZeroGcode() is
never executed on an un-homed machine. Change the branch that currently does
"break" for states like "door"/"hold"/"check"/"sleep"/"unknown" to throw an
error (or return a clear failure) with a descriptive message (e.g. "homing did
not start/completed") so only a successful homing path reaches the completion
wait and eventual call to restoreZeroGcode().

@fixcik fixcik merged commit 2786b70 into master Jun 5, 2026
4 checks passed
@fixcik fixcik deleted the feat/repeatable-zero branch June 5, 2026 01:09
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.

1 participant