From c6f73a3b8d3513df5297f4e7bc9ca49442e1a350 Mon Sep 17 00:00:00 2001 From: osiu97 Date: Wed, 8 Jul 2026 19:31:51 +0200 Subject: [PATCH] Implement proper stepper idling. Should close #3 --- gemini.md | 2 ++ index.html | 36 ++++++++++++++++++++++++++++++++++++ src/main.cpp | 35 ++++++++++++++++++++++++++++++----- web/js/ble.js | 1 + web/js/sections/home.js | 2 +- 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/gemini.md b/gemini.md index 4de7a10..ecf08c4 100644 --- a/gemini.md +++ b/gemini.md @@ -92,11 +92,13 @@ Name: WijiBoard | `HOMEALL` | Home both motors simultaneously | | `HOME1` | Home motor 1 | | `HOME2` | Home motor 2 | +| `DISABLE` | Disable steppers (un-hold) and require rehoming. Auto-triggers after 30 mins (`30 * 60 * 1000` ms) of inactivity | | `POS` | Request current positions (triggers NOTIFY) | ### Status notifications (firmware → browser) `P:,` — current step positions for motor 1 and 2, sent every 200 ms while moving. +`SYS:IDLE_TIMEOUT` — Sent when motors are automatically disabled due to 30 mins of inactivity. --- diff --git a/index.html b/index.html index a60e88f..ce214b1 100644 --- a/index.html +++ b/index.html @@ -86,6 +86,14 @@
+ + +
@@ -202,6 +210,34 @@ window.dispatchEvent(new Event('pwa-installable')); }); + // ── Disable Steppers & Idle Timeout Logic ────────────────────── + const btnDisable = document.getElementById('btn-disable-steppers'); + btnDisable.addEventListener('click', async () => { + if (BLE.isConnected()) { + await BLE.write('DISABLE').catch(e => UI.log(e.message, 'error')); + localStorage.setItem('wiji_homed', 'false'); + UI.log('Steppers manually disabled. Rehoming required.', 'warn'); + const warningEl = document.getElementById('home-warning'); + if (warningEl) warningEl.style.display = 'block'; + } + }); + + document.addEventListener('ble:status', (e) => { + if (e.detail === 'SYS:IDLE_TIMEOUT') { + localStorage.setItem('wiji_homed', 'false'); + UI.log('Steppers disabled due to 30 mins inactivity. Rehoming required.', 'warn'); + const warningEl = document.getElementById('home-warning'); + if (warningEl) warningEl.style.display = 'block'; + } + }); + + BLE.on('connected', () => { + btnDisable.style.display = 'inline-flex'; + }); + BLE.on('disconnected', () => { + btnDisable.style.display = 'none'; + }); + // ── Check Web Bluetooth availability ────────────────────────── if (!navigator.bluetooth) { setTimeout(() => { diff --git a/src/main.cpp b/src/main.cpp index 34fc7cb..184677b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,7 @@ * HOME1 → Home motor 1 (Right) * HOME2 → Home motor 2 (Left) * HOMEALL → Home both motors simultaneously + * DISABLE → Disable steppers (un-hold) and require rehoming * POS → Request current positions (triggers NOTIFY) * * BLE Status Characteristic (NOTIFY): @@ -72,6 +73,11 @@ bool bleConnected = false; unsigned long lastNotify = 0; const unsigned long NOTIFY_INTERVAL_MS = 200; +// ─── Idle timeout tracker ───────────────────────────────────────── +unsigned long lastMotorActive = 0; +const unsigned long IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +bool motorsDisabled = true; // Assume disabled at startup until moved + // ─── Forward declarations ───────────────────────────────────────── void parseCommand(const String &cmd); void sendPosition(); @@ -193,6 +199,14 @@ void parseCommand(const String &cmd) { sendPosition(); return; } + // DISABLE + if (cmd == "DISABLE") { + stepper1.disableOutputs(); + stepper2.disableOutputs(); + motorsDisabled = true; + Serial.println("[SYS] Steppers manually disabled."); + return; + } // POS – explicit position request if (cmd == "POS") { sendPosition(); @@ -280,6 +294,22 @@ void loop() { stepper1.run(); stepper2.run(); + bool moving = stepper1.isRunning() || stepper2.isRunning() || homingState != HOME_IDLE; + + if (moving) { + lastMotorActive = millis(); + motorsDisabled = false; + } else if (!motorsDisabled && (millis() - lastMotorActive > IDLE_TIMEOUT_MS)) { + stepper1.disableOutputs(); + stepper2.disableOutputs(); + motorsDisabled = true; + Serial.println("[SYS] Idle timeout reached. Steppers disabled."); + if (bleConnected && pStatusChar) { + pStatusChar->setValue("SYS:IDLE_TIMEOUT"); + pStatusChar->notify(); + } + } + if (homingState == HOME_ALL_PHASE1) { if (stepper1.distanceToGo() == 0 && stepper2.distanceToGo() == 0) { stepper1.setCurrentPosition(1024); @@ -291,8 +321,6 @@ void loop() { } } else if (homingState == HOME_ALL_PHASE2) { if (stepper1.distanceToGo() == 0 && stepper2.distanceToGo() == 0) { - stepper1.disableOutputs(); - stepper2.disableOutputs(); stepper1.setMaxSpeed(currentMaxSpeed); stepper2.setMaxSpeed(currentMaxSpeed); stepper1.setAcceleration(currentAccel); @@ -309,7 +337,6 @@ void loop() { } } else if (homingState == HOME_1_PHASE2) { if (stepper1.distanceToGo() == 0) { - stepper1.disableOutputs(); stepper1.setMaxSpeed(currentMaxSpeed); stepper1.setAcceleration(currentAccel); homingState = HOME_IDLE; @@ -322,7 +349,6 @@ void loop() { } } else if (homingState == HOME_2_PHASE2) { if (stepper2.distanceToGo() == 0) { - stepper2.disableOutputs(); stepper2.setMaxSpeed(currentMaxSpeed); stepper2.setAcceleration(currentAccel); homingState = HOME_IDLE; @@ -332,7 +358,6 @@ void loop() { // Periodic position notify while motors are moving if (bleConnected) { unsigned long now = millis(); - bool moving = stepper1.isRunning() || stepper2.isRunning() || homingState != HOME_IDLE; if (moving && (now - lastNotify >= NOTIFY_INTERVAL_MS)) { lastNotify = now; sendPosition(); diff --git a/web/js/ble.js b/web/js/ble.js index c71a36b..61d6aa5 100644 --- a/web/js/ble.js +++ b/web/js/ble.js @@ -18,6 +18,7 @@ * SPD: → set max speed (steps/sec) * ACC: → set acceleration (steps/sec²) * HOME → zero both steppers + * DISABLE → disable steppers (un-hold) * POS → request position report * * Status characteristic (NOTIFY): diff --git a/web/js/sections/home.js b/web/js/sections/home.js index 55d3c62..39ef83d 100644 --- a/web/js/sections/home.js +++ b/web/js/sections/home.js @@ -15,7 +15,7 @@ const HomeSection = {

WijiBoard

-

WiFi Spirit Board — SCARA arm controller. Select a section from the sidebar to get started.

+

Bluetooth Spirit Board — SCARA arm controller. Select a section from the sidebar to get started.