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..9516110 100644 --- a/src/driverstation/esp32s3/driver_station_esp32.hpp +++ b/src/driverstation/esp32s3/driver_station_esp32.hpp @@ -12,6 +12,19 @@ #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 +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 { @@ -21,16 +34,28 @@ 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); + 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 ] ========================================"); @@ -38,6 +63,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(); @@ -126,6 +152,26 @@ 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.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); + } + 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; @@ -137,6 +183,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 62d4779..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/core/runtime.hpp b/src/probot/core/runtime.hpp index 12f8694..503e130 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 { @@ -203,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(); 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/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() { 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);