From 8ee6e89ccfe652d353bcd54c1b28650f2267a08a Mon Sep 17 00:00:00 2001 From: tunapro Date: Tue, 3 Feb 2026 19:35:01 +0300 Subject: [PATCH 1/6] feat: add WiFi channel configuration - Add PROBOT_WIFI_AP_CHANNEL macro (default 1, range 1-11) - Display channel in serial output at startup - Update examples to show channel usage Co-Authored-By: Claude Opus 4.5 --- examples/IBT2MotorDemo/IBT2MotorDemo.ino | 1 + examples/JoystickTest/JoystickTest.ino | 1 + examples/MotorOpenLoopDemo/MotorOpenLoopDemo.ino | 1 + examples/command_based/AutonomousDemo/AutonomousDemo.ino | 1 + .../command_based/MecanumDriveDemo/MecanumDriveDemo.ino | 1 + examples/command_based/ShooterDemo/ShooterDemo.ino | 1 + examples/command_based/SliderDemo/SliderDemo.ino | 1 + examples/command_based/TankDriveDemo/TankDriveDemo.ino | 1 + examples/command_based/TurretDemo/TurretDemo.ino | 1 + src/driverstation/esp32s3/driver_station_esp32.hpp | 9 ++++++++- 10 files changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/IBT2MotorDemo/IBT2MotorDemo.ino b/examples/IBT2MotorDemo/IBT2MotorDemo.ino index 508378c..ec18f12 100644 --- a/examples/IBT2MotorDemo/IBT2MotorDemo.ino +++ b/examples/IBT2MotorDemo/IBT2MotorDemo.ino @@ -52,6 +52,7 @@ */ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/JoystickTest/JoystickTest.ino b/examples/JoystickTest/JoystickTest.ino index 3b07e14..5ec50d5 100644 --- a/examples/JoystickTest/JoystickTest.ino +++ b/examples/JoystickTest/JoystickTest.ino @@ -57,6 +57,7 @@ */ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/MotorOpenLoopDemo/MotorOpenLoopDemo.ino b/examples/MotorOpenLoopDemo/MotorOpenLoopDemo.ino index 7af948a..7b692fa 100644 --- a/examples/MotorOpenLoopDemo/MotorOpenLoopDemo.ino +++ b/examples/MotorOpenLoopDemo/MotorOpenLoopDemo.ino @@ -48,6 +48,7 @@ */ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/AutonomousDemo/AutonomousDemo.ino b/examples/command_based/AutonomousDemo/AutonomousDemo.ino index 9cfb1e4..79a2525 100644 --- a/examples/command_based/AutonomousDemo/AutonomousDemo.ino +++ b/examples/command_based/AutonomousDemo/AutonomousDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/MecanumDriveDemo/MecanumDriveDemo.ino b/examples/command_based/MecanumDriveDemo/MecanumDriveDemo.ino index da68b98..e8d27d9 100644 --- a/examples/command_based/MecanumDriveDemo/MecanumDriveDemo.ino +++ b/examples/command_based/MecanumDriveDemo/MecanumDriveDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/ShooterDemo/ShooterDemo.ino b/examples/command_based/ShooterDemo/ShooterDemo.ino index 5b5305a..493c50b 100644 --- a/examples/command_based/ShooterDemo/ShooterDemo.ino +++ b/examples/command_based/ShooterDemo/ShooterDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/SliderDemo/SliderDemo.ino b/examples/command_based/SliderDemo/SliderDemo.ino index 1c187b9..963f4b1 100644 --- a/examples/command_based/SliderDemo/SliderDemo.ino +++ b/examples/command_based/SliderDemo/SliderDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/TankDriveDemo/TankDriveDemo.ino b/examples/command_based/TankDriveDemo/TankDriveDemo.ino index 85135ac..008e83c 100644 --- a/examples/command_based/TankDriveDemo/TankDriveDemo.ino +++ b/examples/command_based/TankDriveDemo/TankDriveDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/examples/command_based/TurretDemo/TurretDemo.ino b/examples/command_based/TurretDemo/TurretDemo.ino index 02fa177..184ce9b 100644 --- a/examples/command_based/TurretDemo/TurretDemo.ino +++ b/examples/command_based/TurretDemo/TurretDemo.ino @@ -1,4 +1,5 @@ #define PROBOT_WIFI_AP_PASSWORD "ProBot1234" +#define PROBOT_WIFI_AP_CHANNEL 3 // Opsiyonel (varsayilan 1) #include #include diff --git a/src/driverstation/esp32s3/driver_station_esp32.hpp b/src/driverstation/esp32s3/driver_station_esp32.hpp index c9441a7..9fca7ba 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -12,6 +12,11 @@ #error "DriverStation AP password not provided. Define PROBOT_WIFI_AP_PASSWORD (>=8 chars) before including probot.h." #endif static_assert(sizeof(PROBOT_WIFI_AP_PASSWORD) - 1 >= 8, "PROBOT_WIFI_AP_PASSWORD must be at least 8 characters."); +#ifndef PROBOT_WIFI_AP_CHANNEL +#define PROBOT_WIFI_AP_CHANNEL 1 +#endif +static_assert(PROBOT_WIFI_AP_CHANNEL >= 1 && PROBOT_WIFI_AP_CHANNEL <= 11, + "PROBOT_WIFI_AP_CHANNEL must be between 1 and 11."); namespace probot::driverstation::esp32 { class DriverStation { @@ -24,13 +29,15 @@ namespace probot::driverstation::esp32 { String ssid = generateSSID(); ap_ssid_ = ssid; WiFi.mode(WIFI_AP); - WiFi.softAP(ssid.c_str(), pw); + WiFi.softAP(ssid.c_str(), pw, PROBOT_WIFI_AP_CHANNEL); Serial.println("[DS ] ========================================"); Serial.print("[DS ] WiFi SSID: "); Serial.println(ssid); Serial.print("[DS ] Password: "); Serial.println("********"); + Serial.print("[DS ] Channel: "); + Serial.println(PROBOT_WIFI_AP_CHANNEL); Serial.print("[DS ] IP Address: "); Serial.println(WiFi.softAPIP()); Serial.println("[DS ] ========================================"); From d36668c684ea88c3ded914845b7a623496e0a1ce Mon Sep 17 00:00:00 2001 From: tunapro Date: Tue, 3 Feb 2026 21:49:39 +0300 Subject: [PATCH 2/6] fix: sync UI state on reconnection - Add /getState endpoint returning robot phase and auto settings - Add syncState() JS function to restore UI on page load - Track autoStartMs in StateService for remaining time calculation - UI now correctly shows robot state after reconnection Co-Authored-By: Claude Opus 4.5 --- .../esp32s3/driver_station_esp32.hpp | 20 ++++++ src/driverstation/esp32s3/index_html.h | 65 +++++++++++++++++++ src/probot/core/runtime.hpp | 6 +- src/probot/robot/state.hpp | 5 +- tests/test_robot_state.cpp | 2 + 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/driverstation/esp32s3/driver_station_esp32.hpp b/src/driverstation/esp32s3/driver_station_esp32.hpp index 9fca7ba..21a2805 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -45,6 +45,7 @@ namespace probot::driverstation::esp32 { _server.on("/", HTTP_GET, [this](){ if (!enforceOwner()) return; handleRoot(); }); _server.on("/updateController", HTTP_POST, [this](){ if (!enforceOwner()) return; handleUpdateController(); }); _server.on("/robotControl", HTTP_GET, [this](){ if (!enforceOwner()) return; handleRobotControl(); }); + _server.on("/getState", HTTP_GET, [this](){ if (!enforceOwner()) return; handleGetState(); }); _server.on("/getBattery", HTTP_GET, [this](){ handleGetBattery(); }); _server.on("/telemetry", HTTP_GET, [this](){ if (!enforceOwner()) return; handleTelemetry(); }); _server.begin(); @@ -133,6 +134,25 @@ namespace probot::driverstation::esp32 { _server.send(200, "text/plain", buf); } + void handleGetState(){ + auto s = _rs.read(); + uint32_t now_ms = millis(); + uint32_t remaining_ms = 0; + if (s.phase == probot::robot::Phase::AUTONOMOUS && s.autoStartMs != 0 && s.autoPeriodSeconds > 0) { + uint32_t total_ms = static_cast(s.autoPeriodSeconds) * 1000u; + uint32_t elapsed = now_ms - s.autoStartMs; + remaining_ms = (elapsed >= total_ms) ? 0u : (total_ms - elapsed); + } + char buf[128]; + snprintf(buf, sizeof(buf), + "{\"phase\":%u,\"autonomousEnabled\":%s,\"autoPeriodSeconds\":%d,\"autoRemainingMs\":%u}", + static_cast(s.phase), + s.autonomousEnabled ? "true" : "false", + (int)s.autoPeriodSeconds, + (unsigned)remaining_ms); + _server.send(200, "application/json", buf); + } + void handleRobotControl(){ String cmd = _server.arg("cmd"); bool enAuto = _server.arg("auto").toInt() != 0; diff --git a/src/driverstation/esp32s3/index_html.h b/src/driverstation/esp32s3/index_html.h index 62d4779..8ba4e8c 100644 --- a/src/driverstation/esp32s3/index_html.h +++ b/src/driverstation/esp32s3/index_html.h @@ -826,6 +826,70 @@ function stopAutoTimer(){ updateAutoDisplay(); } + async function syncState(){ + try{ + const r = await fetch('/getState'); + if(!r.ok) return; + const data = await r.json(); + const btn = document.getElementById('robotButton'); + if(!btn) return; + + const autoPeriodEl = document.getElementById('autoPeriod'); + const autoEnableEl = document.getElementById('enableAutonomous'); + + if(autoPeriodEl && typeof data.autoPeriodSeconds === 'number'){ + autoPeriodEl.value = data.autoPeriodSeconds; + } + if(autoEnableEl && typeof data.autonomousEnabled === 'boolean'){ + autoEnableEl.checked = data.autonomousEnabled; + } + + const remainingMs = (typeof data.autoRemainingMs === 'number') ? data.autoRemainingMs : null; + const remainingSec = remainingMs !== null ? Math.max(0, remainingMs) / 1000 : (parseFloat(autoPeriodEl ? autoPeriodEl.value : 0) || 0); + + if(data.phase === 1){ + controlState = "armed"; + btn.textContent = "Start"; + btn.style.background = "var(--start)"; + btn.style.color = "var(--ice)"; + stopAutoTimer(); + autoRemaining = parseFloat(autoPeriodEl ? autoPeriodEl.value : 0) || 0; + updateAutoDisplay(); + setPhaseDisplay('init'); + }else if(data.phase === 2){ + controlState = "running"; + btn.textContent = "Stop"; + btn.style.background = "var(--stop)"; + btn.style.color = "var(--ice)"; + stopAutoTimer(); + if(remainingSec > 0){ + startAutoTimer(remainingSec); + }else{ + autoModeEnabled = true; + autoRemaining = 0; + updateAutoDisplay(); + setPhaseDisplay('auto'); + } + }else if(data.phase === 3){ + controlState = "running"; + btn.textContent = "Stop"; + btn.style.background = "var(--stop)"; + btn.style.color = "var(--ice)"; + stopAutoTimer(); + setPhaseDisplay('teleop'); + }else{ + controlState = "idle"; + btn.textContent = "Init"; + btn.style.background = "var(--navy)"; + btn.style.color = "var(--ice)"; + stopAutoTimer(); + setPhaseDisplay('stopped'); + } + }catch(e){ + console.error('syncState failed:', e); + } + } + async function handleRobotButton(){ let cmd=""; const enableAuto=document.getElementById('enableAutonomous').checked; @@ -977,6 +1041,7 @@ function stopAutoTimer(){ updateAutoDisplay(); setPhaseDisplay('standby'); requestAnimationFrame(gamepadLoop); + syncState(); }); document.getElementById('autoPeriod').addEventListener('input',e=>{ diff --git a/src/probot/core/runtime.hpp b/src/probot/core/runtime.hpp index 12f8694..cc10418 100644 --- a/src/probot/core/runtime.hpp +++ b/src/probot/core/runtime.hpp @@ -36,7 +36,9 @@ namespace probot { inline RuntimeState g_state{}; inline void autonomousWorker(void*){ - __atomic_store_n(&g_state.auto_start_ms, millis(), __ATOMIC_SEQ_CST); + uint32_t now = millis(); + __atomic_store_n(&g_state.auto_start_ms, now, __ATOMIC_SEQ_CST); + probot::robot::state().setAutoStartMs(now, now); ::autonomousInit(); for(;;){ ::autonomousLoop(); vTaskDelay(pdMS_TO_TICKS(20)); } } @@ -64,6 +66,7 @@ namespace probot { auto& s = g_state; if (s.hAuto){ vTaskDelete(s.hAuto); s.hAuto = nullptr; } __atomic_store_n(&s.auto_start_ms, 0u, __ATOMIC_SEQ_CST); + probot::robot::state().setAutoStartMs(millis(), 0u); } inline void stopTeleop(){ @@ -177,6 +180,7 @@ namespace probot { if (s.autonomousEnabled){ autoLen = s.autoPeriodSeconds; __atomic_store_n(&detail::g_state.auto_start_ms, 0u, __ATOMIC_SEQ_CST); + probot::robot::state().setAutoStartMs(now, 0u); probot::robot::state().setPhase(now, Phase::AUTONOMOUS); startAutonomous(); } else { diff --git a/src/probot/robot/state.hpp b/src/probot/robot/state.hpp index d06d91a..a446711 100644 --- a/src/probot/robot/state.hpp +++ b/src/probot/robot/state.hpp @@ -13,6 +13,7 @@ namespace probot::robot { float batteryVoltage; bool autonomousEnabled; int32_t autoPeriodSeconds; + uint32_t autoStartMs; int32_t clientCount; bool deadlineMiss; }; @@ -23,7 +24,7 @@ namespace probot::robot { _cur = 0; StateSnapshot s{}; s.ms=0; s.seq=0; s.status=Status::STOP; s.phase=Phase::NOT_INIT; s.batteryVoltage=0.0f; - s.autonomousEnabled=false; s.autoPeriodSeconds=30; s.clientCount=0; s.deadlineMiss=false; + s.autonomousEnabled=false; s.autoPeriodSeconds=30; s.autoStartMs=0; s.clientCount=0; s.deadlineMiss=false; _buf[0] = s; _buf[1] = s; } @@ -32,6 +33,7 @@ namespace probot::robot { void setBatteryVoltage(uint32_t now_ms, float v){ writeField(now_ms, [&](StateSnapshot& w){ w.batteryVoltage = v; }); } void setAutonomous(uint32_t now_ms, bool en){ writeField(now_ms, [&](StateSnapshot& w){ w.autonomousEnabled = en; }); } void setAutoPeriodSeconds(uint32_t now_ms, int32_t s){ writeField(now_ms, [&](StateSnapshot& w){ w.autoPeriodSeconds = s; }); } + void setAutoStartMs(uint32_t now_ms, uint32_t ms){ writeField(now_ms, [&](StateSnapshot& w){ w.autoStartMs = ms; }); } void setClientCount(uint32_t now_ms, int32_t c){ writeField(now_ms, [&](StateSnapshot& w){ w.clientCount = c; }); } void setDeadlineMiss(uint32_t now_ms, bool v){ writeField(now_ms, [&](StateSnapshot& w){ w.deadlineMiss = v; }); } @@ -43,6 +45,7 @@ namespace probot::robot { // Convenience getters bool autonomousEnabled() const { return _buf[__atomic_load_n(&_cur, __ATOMIC_SEQ_CST)].autonomousEnabled; } int32_t autoPeriodSeconds() const { return _buf[__atomic_load_n(&_cur, __ATOMIC_SEQ_CST)].autoPeriodSeconds; } + uint32_t autoStartMs() const { return _buf[__atomic_load_n(&_cur, __ATOMIC_SEQ_CST)].autoStartMs; } Status status() const { return _buf[__atomic_load_n(&_cur, __ATOMIC_SEQ_CST)].status; } Phase phase() const { return _buf[__atomic_load_n(&_cur, __ATOMIC_SEQ_CST)].phase; } diff --git a/tests/test_robot_state.cpp b/tests/test_robot_state.cpp index 0a4ef5f..6c4c17d 100644 --- a/tests/test_robot_state.cpp +++ b/tests/test_robot_state.cpp @@ -13,6 +13,7 @@ TEST_CASE(state_service_updates_fields){ state.setBatteryVoltage(10u, 11.5f); state.setAutonomous(10u, true); state.setAutoPeriodSeconds(10u, 15); + state.setAutoStartMs(10u, 123u); state.setClientCount(10u, 1); state.setDeadlineMiss(10u, true); @@ -22,6 +23,7 @@ TEST_CASE(state_service_updates_fields){ EXPECT_NEAR(snap.batteryVoltage, 11.5f, 1e-5f); EXPECT_TRUE(snap.autonomousEnabled); EXPECT_TRUE(snap.autoPeriodSeconds == 15); + EXPECT_TRUE(snap.autoStartMs == 123u); EXPECT_TRUE(snap.clientCount == 1); EXPECT_TRUE(snap.deadlineMiss); EXPECT_TRUE(snap.seq > snap0.seq); From 884e35ecc8ce662c33130338731330c5d4d3e2b8 Mon Sep 17 00:00:00 2001 From: tunapro Date: Wed, 4 Feb 2026 00:00:43 +0300 Subject: [PATCH 3/6] feat: add autonomous cancellation and UI state sync - Add runtime check: cancel autonomous when switch disabled mid-run - Add cancelAuto command to driver station - Lock autonomous switch during teleop/autonomous phases - Add periodic state sync (1s interval) for header status updates - Switch change now properly communicates with robot Co-Authored-By: Claude Opus 4.5 --- .../esp32s3/driver_station_esp32.hpp | 2 ++ src/driverstation/esp32s3/index_html.h | 24 +++++++++++++++---- src/probot/core/runtime.hpp | 6 +++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/driverstation/esp32s3/driver_station_esp32.hpp b/src/driverstation/esp32s3/driver_station_esp32.hpp index 21a2805..05c8a46 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -164,6 +164,8 @@ namespace probot::driverstation::esp32 { _rs.setAutonomous(millis(), enAuto); if (autoLen > 0) _rs.setAutoPeriodSeconds(millis(), autoLen); _rs.setStatus(millis(), robot::Status::START); + } else if (cmd == "cancelAuto"){ + _rs.setAutonomous(millis(), false); } else if (cmd == "stop"){ _rs.setStatus(millis(), robot::Status::STOP); } diff --git a/src/driverstation/esp32s3/index_html.h b/src/driverstation/esp32s3/index_html.h index 8ba4e8c..45b119c 100644 --- a/src/driverstation/esp32s3/index_html.h +++ b/src/driverstation/esp32s3/index_html.h @@ -840,8 +840,11 @@ function stopAutoTimer(){ if(autoPeriodEl && typeof data.autoPeriodSeconds === 'number'){ autoPeriodEl.value = data.autoPeriodSeconds; } - if(autoEnableEl && typeof data.autonomousEnabled === 'boolean'){ - autoEnableEl.checked = data.autonomousEnabled; + if(autoEnableEl){ + if(typeof data.autonomousEnabled === 'boolean'){ + autoEnableEl.checked = data.autonomousEnabled; + } + autoEnableEl.disabled = (data.phase === 2 || data.phase === 3); } const remainingMs = (typeof data.autoRemainingMs === 'number') ? data.autoRemainingMs : null; @@ -1054,9 +1057,21 @@ function stopAutoTimer(){ updateAutoDisplay(); }); - document.getElementById('enableAutonomous').addEventListener('change',e=>{ + document.getElementById('enableAutonomous').addEventListener('change', async e=>{ if(!e.target.checked){ - stopAutoTimer(); + if(controlState === "running"){ + try{ + const r = await fetch('/robotControl?cmd=cancelAuto'); + if(!r.ok) throw new Error("Cancel auto failed"); + }catch(err){ + console.error(err); + return; + } + stopAutoTimer(); + setPhaseDisplay('teleop'); + }else{ + stopAutoTimer(); + } } }); @@ -1076,6 +1091,7 @@ function stopAutoTimer(){ if(el) el.textContent=''; } setInterval(pollTelemetry,50); + setInterval(syncState, 1000); diff --git a/src/probot/core/runtime.hpp b/src/probot/core/runtime.hpp index cc10418..503e130 100644 --- a/src/probot/core/runtime.hpp +++ b/src/probot/core/runtime.hpp @@ -207,6 +207,12 @@ namespace probot { } } + if (s.status == Status::START && s.phase == Phase::AUTONOMOUS && !s.autonomousEnabled){ + stopAutonomous(); + probot::robot::state().setPhase(now, Phase::TELEOP); + startTeleop(); + } + if (now - lastLed >= 500){ lastLed = now; updateLed(); From b798fbf74e387d808c8dacd9f63721d45c978594 Mon Sep 17 00:00:00 2001 From: tunapro Date: Wed, 4 Feb 2026 00:28:04 +0300 Subject: [PATCH 4/6] fix: improve autonomous switch behavior and UI sync - Only calculate remaining_ms when autonomous is still enabled - Keep switch enabled during autonomous (for cancellation) - Lock switch only during teleop - Fix autoRemaining initialization based on switch state - Handle autonomousEnabled=false in phase 2 display Co-Authored-By: Claude Opus 4.5 --- .../esp32s3/driver_station_esp32.hpp | 3 +- src/driverstation/esp32s3/index_html.h | 34 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/driverstation/esp32s3/driver_station_esp32.hpp b/src/driverstation/esp32s3/driver_station_esp32.hpp index 05c8a46..74f7bb1 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -138,7 +138,8 @@ namespace probot::driverstation::esp32 { auto s = _rs.read(); uint32_t now_ms = millis(); uint32_t remaining_ms = 0; - if (s.phase == probot::robot::Phase::AUTONOMOUS && s.autoStartMs != 0 && s.autoPeriodSeconds > 0) { + if (s.phase == probot::robot::Phase::AUTONOMOUS && s.autonomousEnabled && + s.autoStartMs != 0 && s.autoPeriodSeconds > 0) { uint32_t total_ms = static_cast(s.autoPeriodSeconds) * 1000u; uint32_t elapsed = now_ms - s.autoStartMs; remaining_ms = (elapsed >= total_ms) ? 0u : (total_ms - elapsed); diff --git a/src/driverstation/esp32s3/index_html.h b/src/driverstation/esp32s3/index_html.h index 45b119c..c23b322 100644 --- a/src/driverstation/esp32s3/index_html.h +++ b/src/driverstation/esp32s3/index_html.h @@ -837,14 +837,17 @@ function stopAutoTimer(){ const autoPeriodEl = document.getElementById('autoPeriod'); const autoEnableEl = document.getElementById('enableAutonomous'); - if(autoPeriodEl && typeof data.autoPeriodSeconds === 'number'){ + const isAutonomous = (data.phase === 2); + const isTeleop = (data.phase === 3); + const isRunning = (isAutonomous || isTeleop); + if(autoPeriodEl && typeof data.autoPeriodSeconds === 'number' && isRunning){ autoPeriodEl.value = data.autoPeriodSeconds; } if(autoEnableEl){ - if(typeof data.autonomousEnabled === 'boolean'){ + if(typeof data.autonomousEnabled === 'boolean' && (isAutonomous || isTeleop)){ autoEnableEl.checked = data.autonomousEnabled; } - autoEnableEl.disabled = (data.phase === 2 || data.phase === 3); + autoEnableEl.disabled = isTeleop; } const remainingMs = (typeof data.autoRemainingMs === 'number') ? data.autoRemainingMs : null; @@ -856,7 +859,11 @@ function stopAutoTimer(){ btn.style.background = "var(--start)"; btn.style.color = "var(--ice)"; stopAutoTimer(); - autoRemaining = parseFloat(autoPeriodEl ? autoPeriodEl.value : 0) || 0; + if(autoEnableEl && autoEnableEl.checked){ + autoRemaining = parseFloat(autoPeriodEl ? autoPeriodEl.value : 0) || 0; + }else{ + autoRemaining = 0; + } updateAutoDisplay(); setPhaseDisplay('init'); }else if(data.phase === 2){ @@ -864,14 +871,19 @@ function stopAutoTimer(){ btn.textContent = "Stop"; btn.style.background = "var(--stop)"; btn.style.color = "var(--ice)"; - stopAutoTimer(); - if(remainingSec > 0){ - startAutoTimer(remainingSec); + if(data.autonomousEnabled === false){ + stopAutoTimer(); + setPhaseDisplay('teleop'); }else{ - autoModeEnabled = true; - autoRemaining = 0; - updateAutoDisplay(); - setPhaseDisplay('auto'); + stopAutoTimer(); + if(remainingSec > 0){ + startAutoTimer(remainingSec); + }else{ + autoModeEnabled = true; + autoRemaining = 0; + updateAutoDisplay(); + setPhaseDisplay('auto'); + } } }else if(data.phase === 3){ controlState = "running"; From ecefaef83f18ea7c81fd4ccd2baf32ad1f68ba83 Mon Sep 17 00:00:00 2001 From: tunapro Date: Wed, 4 Feb 2026 01:25:20 +0300 Subject: [PATCH 5/6] feat: add custom SSID configuration - Add PROBOT_WIFI_AP_SSID macro for custom network name - Add PROBOT_WIFI_AP_SSID_NO_MAC_SUFFIX to disable MAC suffix - Static assertions for SSID length validation Co-Authored-By: Claude Opus 4.5 --- .../esp32s3/driver_station_esp32.hpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/driverstation/esp32s3/driver_station_esp32.hpp b/src/driverstation/esp32s3/driver_station_esp32.hpp index 74f7bb1..9516110 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -12,6 +12,14 @@ #error "DriverStation AP password not provided. Define PROBOT_WIFI_AP_PASSWORD (>=8 chars) before including probot.h." #endif static_assert(sizeof(PROBOT_WIFI_AP_PASSWORD) - 1 >= 8, "PROBOT_WIFI_AP_PASSWORD must be at least 8 characters."); +#ifdef PROBOT_WIFI_AP_SSID +static_assert(sizeof(PROBOT_WIFI_AP_SSID) - 1 >= 1, "PROBOT_WIFI_AP_SSID must be at least 1 character."); +#ifdef PROBOT_WIFI_AP_SSID_NO_MAC_SUFFIX +static_assert(sizeof(PROBOT_WIFI_AP_SSID) - 1 <= 32, "PROBOT_WIFI_AP_SSID must be 32 characters or fewer."); +#else +static_assert(sizeof(PROBOT_WIFI_AP_SSID) - 1 <= 25, "PROBOT_WIFI_AP_SSID must be 25 characters or fewer when MAC suffix is enabled."); +#endif +#endif #ifndef PROBOT_WIFI_AP_CHANNEL #define PROBOT_WIFI_AP_CHANNEL 1 #endif @@ -26,7 +34,17 @@ namespace probot::driverstation::esp32 { void begin(){ const char* pw = PROBOT_WIFI_AP_PASSWORD; - String ssid = generateSSID(); + String ssid; +#ifdef PROBOT_WIFI_AP_SSID + ssid = String(PROBOT_WIFI_AP_SSID); +#ifndef PROBOT_WIFI_AP_SSID_NO_MAC_SUFFIX + char suffix[8]; + snprintf(suffix, sizeof(suffix), "-%06X", (unsigned int)(ESP.getEfuseMac() & 0xFFFFFF)); + ssid += suffix; +#endif +#else + ssid = generateSSID(); +#endif ap_ssid_ = ssid; WiFi.mode(WIFI_AP); WiFi.softAP(ssid.c_str(), pw, PROBOT_WIFI_AP_CHANNEL); From 5a20dddfef521ca454ccb17bb54fa5b4d64ed45b Mon Sep 17 00:00:00 2001 From: tunapro Date: Wed, 4 Feb 2026 02:14:24 +0300 Subject: [PATCH 6/6] feat: implement circular buffer for telemetry and add auto-scroll toggle Co-Authored-By: Claude Opus 4.5 --- src/driverstation/esp32s3/index_html.h | 23 +++++++++- src/probot/telemetry/telemetry.hpp | 60 ++++++++++++++++++++------ 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/driverstation/esp32s3/index_html.h b/src/driverstation/esp32s3/index_html.h index c23b322..d07c660 100644 --- a/src/driverstation/esp32s3/index_html.h +++ b/src/driverstation/esp32s3/index_html.h @@ -705,6 +705,7 @@ const char MAIN_page[] PROGMEM = R"=====(

Telemetry


       
+      
     
     

System Logs

@@ -720,6 +721,7 @@ const char MAIN_page[] PROGMEM = R"=====( diff --git a/src/probot/telemetry/telemetry.hpp b/src/probot/telemetry/telemetry.hpp index 2edbdfb..fb75b52 100644 --- a/src/probot/telemetry/telemetry.hpp +++ b/src/probot/telemetry/telemetry.hpp @@ -11,6 +11,7 @@ namespace detail { struct TelemetryBuffer { char data[BUFFER_SIZE]; + volatile uint16_t head = 0; volatile uint16_t len = 0; volatile uint32_t seq = 0; }; @@ -18,24 +19,42 @@ namespace detail { inline TelemetryBuffer g_buffer{}; } -inline void print(const char* msg) { +inline void writeBytes(const char* msg, size_t msgLen) { auto& buf = detail::g_buffer; - size_t msgLen = strlen(msg); - size_t space = detail::BUFFER_SIZE - 1 - buf.len; + if (msgLen == 0) return; - if (msgLen > space) { - // Buffer dolu, baştan yaz + if (msgLen >= detail::BUFFER_SIZE) { + msg += (msgLen - detail::BUFFER_SIZE); + msgLen = detail::BUFFER_SIZE; + buf.head = 0; buf.len = 0; - space = detail::BUFFER_SIZE - 1; } - size_t toWrite = (msgLen < space) ? msgLen : space; - memcpy(buf.data + buf.len, msg, toWrite); - uint16_t oldLen = buf.len; buf.len = static_cast(oldLen + toWrite); - buf.data[buf.len] = '\0'; + uint16_t head = buf.head; + size_t first = detail::BUFFER_SIZE - head; + if (first > msgLen) first = msgLen; + memcpy(buf.data + head, msg, first); + size_t remaining = msgLen - first; + if (remaining) { + memcpy(buf.data, msg + first, remaining); + } + head = static_cast((head + msgLen) % detail::BUFFER_SIZE); + buf.head = head; + + uint16_t newLen = buf.len; + if (newLen + msgLen >= detail::BUFFER_SIZE) { + newLen = detail::BUFFER_SIZE; + } else { + newLen = static_cast(newLen + msgLen); + } + buf.len = newLen; uint32_t s = buf.seq; buf.seq = s + 1; } +inline void print(const char* msg) { + writeBytes(msg, strlen(msg)); +} + inline void println(const char* msg = "") { print(msg); print("\n"); @@ -52,14 +71,31 @@ inline void printf(const char* fmt, ...) { inline void clear() { auto& buf = detail::g_buffer; + buf.head = 0; buf.len = 0; - buf.data[0] = '\0'; uint32_t s = buf.seq; buf.seq = s + 1; } // Internal: DS tarafından çağrılır inline const char* getBuffer() { - return detail::g_buffer.data; + auto& buf = detail::g_buffer; + static char out[detail::BUFFER_SIZE + 1]; + uint16_t len = buf.len; + if (len == 0) { + out[0] = '\0'; + return out; + } + uint16_t head = buf.head; + uint16_t tail = static_cast((head + detail::BUFFER_SIZE - len) % detail::BUFFER_SIZE); + size_t first = detail::BUFFER_SIZE - tail; + if (first > len) first = len; + memcpy(out, buf.data + tail, first); + size_t remaining = len - first; + if (remaining) { + memcpy(out + first, buf.data, remaining); + } + out[len] = '\0'; + return out; } inline uint16_t getLength() {