From cc1909faaf9c1cbed46bf59a05a2af8b5e493b17 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 4 Jun 2026 03:33:36 -0600 Subject: [PATCH] Make ESP32 firmware API fully generic --- docs/api-and-serial-commands.md | 236 ++---------- docs/api.md | 239 +----------- .../esp32/overland-controller/app_config.cpp | 10 +- firmware/esp32/overland-controller/bms.cpp | 2 +- firmware/esp32/overland-controller/config.h | 4 +- .../overland-controller.ino | 355 +++++------------- firmware/esp32/overland-controller/relays.cpp | 8 +- tools/esp32_bringup_check.sh | 16 +- 8 files changed, 159 insertions(+), 711 deletions(-) diff --git a/docs/api-and-serial-commands.md b/docs/api-and-serial-commands.md index 09dcfc0..8f484cc 100644 --- a/docs/api-and-serial-commands.md +++ b/docs/api-and-serial-commands.md @@ -6,79 +6,29 @@ Base URL while connected to the ESP32 access point: http://192.168.4.1 ---- - ## Status ### GET /status -Returns full controller status. +Returns full controller status, including battery/BMS telemetry, relay states, temperature sensor states, alarms, system info, and current configuration. -Includes: - -- Battery/BMS telemetry -- Cell voltages -- Temperature readings -- Sensor health -- Relay states -- Vehicle state -- Network state -- Alarm state -- System info -- Current configuration - -Relay states use generic relay IDs: +Relay states use generic IDs: relay_1 relay_2 -Temperature sensors use generic sensor IDs: +Temperature sensors use generic IDs: - temp_1 - temp_2 - temp_3 - temp_4 - temp_5 - temp_6 - temp_7 - temp_8 - ---- + temp_1 through temp_8 ## Relay Control API -### GET /relay/relay_1/on + GET /relay/relay_1/on + GET /relay/relay_1/off + GET /relay/relay_2/on + GET /relay/relay_2/off -Turns relay 1 on. - -### GET /relay/relay_1/off - -Turns relay 1 off. - -### GET /relay/relay_2/on - -Turns relay 2 on. - -### GET /relay/relay_2/off - -Turns relay 2 off. - -Relay display names are configurable. The firmware should not assume what a relay controls. - ---- - -## Deprecated Relay Aliases - -These legacy routes may still exist for compatibility during development, but should not be used by new clients: - - /relay/starlink/on - /relay/starlink/off - /relay/fridge/on - /relay/fridge/off - -Future work should remove these aliases after the Pico/dashboard clients use generic relay IDs. - ---- +Relay display names are configurable. Firmware and API clients should not assume what a relay controls. ## Configuration API @@ -86,13 +36,9 @@ Future work should remove these aliases after the Pico/dashboard clients use gen Returns saved configuration. ---- - ### POST /config/relay -Updates relay config. - -Example body: +Example: { "id": "relay_1", @@ -100,18 +46,9 @@ Example body: "enabled": true } -Valid IDs: - -- relay_1 -- relay_2 - ---- - ### POST /config/bms -Updates BMS config. - -Example body: +Example: { "enabled": true, @@ -120,13 +57,9 @@ Example body: "address_type": "public" } ---- - ### POST /config/temp -Updates temperature sensor config. - -Example body: +Example: { "id": "temp_1", @@ -135,24 +68,9 @@ Example body: "enabled": true } -Valid IDs: - -- temp_1 -- temp_2 -- temp_3 -- temp_4 -- temp_5 -- temp_6 -- temp_7 -- temp_8 - ---- - ### POST /config/factory-reset -Clears saved config and restores generic defaults. - ---- +Clears saved configuration and restores generic defaults. ## Serial Monitor @@ -164,135 +82,59 @@ Line ending: Newline ---- - ## Status and Config Commands -### status - -Prints full JSON status to Serial. - -### config - -Prints current configuration to Serial. - -### save - -Saves current in-memory configuration to NVS. - -### factory reset - -Clears saved config and restores defaults. - ---- + status + config + save + factory reset ## Log Level Commands -### log quiet - -Minimizes serial output. - -### log info - -Normal serial output. - -### log debug - -Enables recurring debug output. - ---- + log quiet + log info + log debug ## Relay Control Commands -Legacy relay control commands may still exist during development: - - relay starlink on - relay starlink off - relay fridge on - relay fridge off - -Future work should replace these with generic serial relay commands. - ---- + relay 1 on + relay 1 off + relay 2 on + relay 2 off ## Relay Configuration Commands -### relayname 1 Aux Power - -Sets relay 1 display name. - -### relayname 2 Accessory Power - -Sets relay 2 display name. + relayname 1 Aux Power + relayname 2 Accessory Power Run `save` after changing names. ---- - ## Temperature Sensor Configuration Commands -### tempname 1 Cabin - -Sets temp sensor 1 display name. - -### tempname 2 Outside Air - -Sets temp sensor 2 display name. + tempname 1 Cabin + tempname 2 Outside Air Run `save` after changing names. ---- - ## BMS Configuration Commands -### bmsname House Battery - -Sets BMS display name. - -### bmsaddr aa:bb:cc:dd:ee:ff - -Sets BMS BLE address. + bmsname House Battery + bmsaddr aa:bb:cc:dd:ee:ff Run `save` after changing BMS config manually. ---- - ## BMS Setup Mode Commands -### enter setup - -Enters BMS setup mode. - -Effects: - -- Disconnects BMS client -- Pauses BMS reconnect -- Allows repeated BLE scans - -### scan ble - -Scans for BLE devices. + enter setup + scan ble + select bms 1 + exit setup Notes: - Some BMS modules advertise intermittently. -- Multiple scans may be required. - -### select bms 1 - -Selects BLE scan result 1 as the configured BMS. - -This saves: - -- BMS name -- BMS address -- BMS enabled state - -### exit setup - -Leaves BMS setup mode and allows BMS reconnect. - ---- +- Multiple BLE scans may be required. +- Once a BMS address is configured, BMS telemetry reconnects directly using the saved address. ## Typical First-Time Setup Flow @@ -308,11 +150,7 @@ Leaves BMS setup mode and allows BMS reconnect. save config ---- - ## Known Notes -- BMS telemetry is reliable once a BMS address is configured. -- BLE discovery depends on how often the BMS advertises. - DS18B20 sensors require a 4.7kΩ pull-up resistor on the 1-Wire data bus. -- Some legacy relay names still exist internally and should be removed in a future cleanup. +- The firmware uses generic relay and temperature IDs. Installation-specific names belong in saved configuration only. diff --git a/docs/api.md b/docs/api.md index 2069a28..4c80088 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,238 +1,3 @@ -# Xterra Overland Dashboard API Specification +# Overland Controller API -## Overview - -The ESP32 acts as the system controller and provides status and control endpoints. - -Primary communication is UART over CAT5. WiFi HTTP API is the backup communication path. - -WiFi HTTP API is provided for: - -* Web dashboard -* Mobile access -* Configuration -* Future Home Assistant integration - ---- - -# GET /status - -Returns complete current system status. - -Example: - -```json -{ - "timestamp": 1748910000, - - "battery": { - "soc": 82, - "voltage": 13.2, - "current": -6.4, - "remaining_ah": 82.5, - "runtime_hours": 11.2, - "temperature_f": 77.4 - }, - - "temps": { - "fridge_zone_1": 35.8, - "fridge_zone_2": 12.1, - "rear_seat": 78.0, - "outside": 88.4 - }, - - "relays": { - "starlink": false, - "fridge": true - }, - - "network": { - "wifi_enabled": false, - "uart_connected": true - } -} -``` - ---- - -# GET /battery - -Returns detailed battery information. - -Example: - -```json -{ - "soc": 82, - "voltage": 13.2, - "current": -6.4, - "power_w": -84.5, - "remaining_ah": 82.5, - "runtime_hours": 11.2, - "temperature_f": 77.4, - - "cells": [ - 3.299, - 3.301, - 3.300, - 3.298 - ], - - "cell_delta_mv": 3 -} -``` - ---- - -# GET /temps - -Returns all temperature sensors. - -Example: - -```json -{ - "fridge_zone_1": 35.8, - "fridge_zone_2": 12.1, - "rear_seat": 78.0, - "outside": 88.4 -} -``` - ---- - -# GET /relays - -Returns relay states. - -Example: - -```json -{ - "starlink": false, - "fridge": true -} -``` - ---- - -# POST /relay/starlink - -Request: - -```json -{ - "state": true -} -``` - -Response: - -```json -{ - "success": true, - "starlink": true -} -``` - ---- - -# POST /relay/fridge - -Request: - -```json -{ - "state": true -} -``` - -Response: - -```json -{ - "success": true, - "fridge": true -} -``` - ---- - -# GET /network - -Returns network status. - -Example: - -```json -{ - "wifi_enabled": false, - "uart_connected": true, - "starlink_enabled": false -} -``` - ---- - -# POST /network/wifi - -Enable WiFi temporarily. - -Request: - -```json -{ - "minutes": 10 -} -``` - -Response: - -```json -{ - "success": true, - "expires_minutes": 10 -} -``` - ---- - -# Future Endpoints - -## Vehicle Data - -GET /vehicle - -```json -{ - "speed_mph": 62, - "rpm": 2200, - "coolant_temp_f": 192, - "fuel_percent": 48 -} -``` - -## GPS - -GET /gps - -```json -{ - "lat": 43.6629, - "lon": -116.6874, - "speed_mph": 0, - "heading": 0 -} -``` - -## Historical Data - -GET /history - -GET /history/temps - -GET /history/battery - -## Home Assistant - -MQTT integration planned. +See `docs/api-and-serial-commands.md` for the current HTTP API and Serial command reference. diff --git a/firmware/esp32/overland-controller/app_config.cpp b/firmware/esp32/overland-controller/app_config.cpp index ae113b3..48df0a8 100644 --- a/firmware/esp32/overland-controller/app_config.cpp +++ b/firmware/esp32/overland-controller/app_config.cpp @@ -13,14 +13,14 @@ void loadDefaultConfig() { appConfig.relays[0] = { "relay_1", "Relay 1", - RELAY_STARLINK_PIN, + RELAY_1_PIN, true }; appConfig.relays[1] = { "relay_2", "Relay 2", - RELAY_FRIDGE_PIN, + RELAY_2_PIN, true }; @@ -44,7 +44,7 @@ void loadDefaultConfig() { void loadConfig() { loadDefaultConfig(); - preferences.begin("xterra", true); + preferences.begin("overland", true); bool configured = preferences.getBool("configured", false); @@ -106,7 +106,7 @@ void loadConfig() { } void saveConfig() { - preferences.begin("xterra", false); + preferences.begin("overland", false); preferences.putBool("configured", true); preferences.putString("device", appConfig.deviceName); @@ -135,7 +135,7 @@ void saveConfig() { } void factoryResetConfig() { - preferences.begin("xterra", false); + preferences.begin("overland", false); preferences.clear(); preferences.end(); diff --git a/firmware/esp32/overland-controller/bms.cpp b/firmware/esp32/overland-controller/bms.cpp index 9817a0a..cb21d14 100644 --- a/firmware/esp32/overland-controller/bms.cpp +++ b/firmware/esp32/overland-controller/bms.cpp @@ -497,7 +497,7 @@ static bool parseCellVoltages() { } void initBms() { - NimBLEDevice::init("XterraESP32"); + NimBLEDevice::init("OverlandController"); NimBLEDevice::setPower(ESP_PWR_LVL_P9); resetResponseBuffer(); } diff --git a/firmware/esp32/overland-controller/config.h b/firmware/esp32/overland-controller/config.h index 5d2ea7b..f306efc 100644 --- a/firmware/esp32/overland-controller/config.h +++ b/firmware/esp32/overland-controller/config.h @@ -3,8 +3,8 @@ #define DEVICE_NAME "Overland Controller" // Relay Outputs -#define RELAY_STARLINK_PIN 16 -#define RELAY_FRIDGE_PIN 17 +#define RELAY_1_PIN 16 +#define RELAY_2_PIN 17 // DS18B20 Bus #define ONEWIRE_PIN 4 diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index a098237..30e3938 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -18,15 +18,9 @@ HardwareSerial DashboardSerial(2); String uartLineBuffer; float calculateRuntimeHours() { - if (!bmsData.valid || bmsData.current >= 0) { - return 0; - } - + if (!bmsData.valid || bmsData.current >= 0) return 0; float dischargeAmps = -bmsData.current; - if (dischargeAmps <= 0.1) { - return 0; - } - + if (dischargeAmps <= 0.1) return 0; return bmsData.remainingAh / dischargeAmps; } @@ -35,7 +29,6 @@ void buildStatusDocument(JsonDocument& doc) { doc["timestamp"] = millis(); JsonObject battery = doc.createNestedObject("battery"); - if (bmsData.valid) { battery["source"] = "jbd_bms"; battery["connected"] = bmsData.connected; @@ -52,9 +45,7 @@ void buildStatusDocument(JsonDocument& doc) { JsonArray cells = battery.createNestedArray("cell_voltages"); if (bmsData.cellsValid) { - for (int i = 0; i < bmsData.cellCount; i++) { - cells.add(bmsData.cellVoltages[i]); - } + for (int i = 0; i < bmsData.cellCount; i++) cells.add(bmsData.cellVoltages[i]); } battery["cell_min_voltage"] = bmsData.cellMinVoltage; @@ -62,55 +53,52 @@ void buildStatusDocument(JsonDocument& doc) { battery["cell_delta_mv"] = bmsData.cellDeltaMv; battery["cells_valid"] = bmsData.cellsValid; } else { - battery["source"] = "placeholder"; + battery["source"] = "unconfigured"; battery["connected"] = false; battery["soc"] = 0; battery["voltage"] = 0; battery["current"] = 0; battery["remaining_ah"] = 0; - battery["capacity_ah"] = 150; + battery["capacity_ah"] = 0; battery["runtime_hours"] = 0; battery["temperature_f"] = 0; battery["cycle_count"] = 0; battery["cell_count"] = 0; battery["ntc_count"] = 0; + battery["cell_delta_mv"] = 0; + battery["cells_valid"] = false; } - JsonObject temps = doc.createNestedObject("temps"); + const float tempValues[4] = {sensors.temp1, sensors.temp2, sensors.temp3, sensors.temp4}; + const bool tempOnline[4] = {sensors.temp1Online, sensors.temp2Online, sensors.temp3Online, sensors.temp4Online}; - if (sensors.temp1Online) { - temps["fridge_zone_1"] = sensors.temp1; - } else { - temps["fridge_zone_1"] = nullptr; + JsonArray temps = doc.createNestedArray("temps"); + int tempCount = appConfig.tempSensorCount > 4 ? 4 : appConfig.tempSensorCount; + + for (int i = 0; i < tempCount; i++) { + JsonObject temp = temps.createNestedObject(); + temp["id"] = appConfig.tempSensors[i].id; + temp["name"] = appConfig.tempSensors[i].name; + temp["enabled"] = appConfig.tempSensors[i].enabled; + temp["online"] = tempOnline[i]; + temp["temperature_f"] = tempOnline[i] ? tempValues[i] : nullptr; } - if (sensors.temp2Online) { - temps["fridge_zone_2"] = sensors.temp2; - } else { - temps["fridge_zone_2"] = nullptr; - } + JsonArray relayStates = doc.createNestedArray("relays"); - if (sensors.temp3Online) { - temps["rear_seat"] = sensors.temp3; - } else { - temps["rear_seat"] = nullptr; - } + JsonObject relay1 = relayStates.createNestedObject(); + relay1["id"] = appConfig.relays[0].id; + relay1["name"] = appConfig.relays[0].name; + relay1["pin"] = appConfig.relays[0].pin; + relay1["enabled"] = appConfig.relays[0].enabled; + relay1["state"] = relays.relay1; - if (sensors.temp4Online) { - temps["outside"] = sensors.temp4; - } else { - temps["outside"] = nullptr; - } - - JsonObject sensorHealth = doc.createNestedObject("sensor_health"); - sensorHealth["fridge_zone_1"] = sensors.temp1Online; - sensorHealth["fridge_zone_2"] = sensors.temp2Online; - sensorHealth["rear_seat"] = sensors.temp3Online; - sensorHealth["outside"] = sensors.temp4Online; - - JsonObject relayObj = doc.createNestedObject("relays"); - relayObj["starlink"] = relays.relay1; - relayObj["fridge"] = relays.relay2; + JsonObject relay2 = relayStates.createNestedObject(); + relay2["id"] = appConfig.relays[1].id; + relay2["name"] = appConfig.relays[1].name; + relay2["pin"] = appConfig.relays[1].pin; + relay2["enabled"] = appConfig.relays[1].enabled; + relay2["state"] = relays.relay2; JsonObject vehicle = doc.createNestedObject("vehicle"); vehicle["ignition_on"] = digitalRead(IGNITION_PIN); @@ -163,15 +151,10 @@ void buildStatusDocument(JsonDocument& doc) { } void sendStatus(Stream& output, bool pretty = false) { - DynamicJsonDocument doc(2048); + DynamicJsonDocument doc(4096); buildStatusDocument(doc); - - if (pretty) { - serializeJsonPretty(doc, output); - } else { - serializeJson(doc, output); - } - + if (pretty) serializeJsonPretty(doc, output); + else serializeJson(doc, output); output.println(); } @@ -183,23 +166,19 @@ void sendError(Stream& output, const char* message) { output.println(); } -bool setRelayByName(const char* relayName, bool enabled) { - if (strcmp(relayName, "starlink") == 0) { - relays.relay1 = enabled; - } else if (strcmp(relayName, "fridge") == 0) { - relays.relay2 = enabled; - } else { - return false; - } +bool setRelayById(const String& relayId, bool enabled) { + if (relayId == "relay_1") relays.relay1 = enabled; + else if (relayId == "relay_2") relays.relay2 = enabled; + else return false; updateRelayOutputs(); return true; } -void sendRelayResponse(Stream& output, const char* relayName, bool enabled) { +void sendRelayResponse(Stream& output, const String& relayId, bool enabled) { DynamicJsonDocument doc(256); doc["type"] = MSG_RELAY_RESPONSE; - doc["relay"] = relayName; + doc["relay"] = relayId; doc["enabled"] = enabled; doc["ok"] = true; serializeJson(doc, output); @@ -223,15 +202,15 @@ void handleUartMessage(const String& line) { } if (strcmp(type, MSG_SET_RELAY) == 0) { - const char* relayName = doc["relay"] | ""; + String relayId = doc["relay"] | ""; bool enabled = doc["enabled"] | false; - if (!setRelayByName(relayName, enabled)) { + if (!setRelayById(relayId, enabled)) { sendError(DashboardSerial, "unknown_relay"); return; } - sendRelayResponse(DashboardSerial, relayName, enabled); + sendRelayResponse(DashboardSerial, relayId, enabled); return; } @@ -244,15 +223,10 @@ void pollDashboardUart() { if (c == '\n') { uartLineBuffer.trim(); - - if (uartLineBuffer.length() > 0) { - handleUartMessage(uartLineBuffer); - } - + if (uartLineBuffer.length() > 0) handleUartMessage(uartLineBuffer); uartLineBuffer = ""; } else if (c != '\r') { uartLineBuffer += c; - if (uartLineBuffer.length() > 512) { uartLineBuffer = ""; sendError(DashboardSerial, "message_too_long"); @@ -261,11 +235,8 @@ void pollDashboardUart() { } } - void handleDebugSerial() { - if (!Serial.available()) { - return; - } + if (!Serial.available()) return; String command = Serial.readStringUntil('\n'); command.trim(); @@ -275,30 +246,6 @@ void handleDebugSerial() { return; } - if (command == "relay starlink on") { - setRelayByName("starlink", true); - Serial.println("OK starlink on"); - return; - } - - if (command == "relay starlink off") { - setRelayByName("starlink", false); - Serial.println("OK starlink off"); - return; - } - - if (command == "relay fridge on") { - setRelayByName("fridge", true); - Serial.println("OK fridge on"); - return; - } - - if (command == "relay fridge off") { - setRelayByName("fridge", false); - Serial.println("OK fridge off"); - return; - } - if (command == "config") { printConfig(); return; @@ -328,17 +275,44 @@ void handleDebugSerial() { return; } - if (command.startsWith("relayname ")) { + if (command == "save") { + saveConfig(); + Serial.println("OK config saved"); + return; + } + + if (command.startsWith("relay ")) { int firstSpace = command.indexOf(' '); int secondSpace = command.indexOf(' ', firstSpace + 1); if (secondSpace > 0) { int relayNumber = command.substring(firstSpace + 1, secondSpace).toInt(); + String action = command.substring(secondSpace + 1); + String relayId = "relay_" + String(relayNumber); + if (relayNumber >= 1 && relayNumber <= MAX_RELAYS && (action == "on" || action == "off")) { + bool enabled = action == "on"; + if (setRelayById(relayId, enabled)) { + Serial.print("OK "); + Serial.print(relayId); + Serial.print(" "); + Serial.println(action); + return; + } + } + } + + Serial.println("Invalid relay command. Use: relay 1 on, relay 1 off, relay 2 on, relay 2 off"); + return; + } + + if (command.startsWith("relayname ")) { + int firstSpace = command.indexOf(' '); + int secondSpace = command.indexOf(' ', firstSpace + 1); + if (secondSpace > 0) { + int relayNumber = command.substring(firstSpace + 1, secondSpace).toInt(); if (relayNumber >= 1 && relayNumber <= MAX_RELAYS) { - appConfig.relays[relayNumber - 1].name = - command.substring(secondSpace + 1); - + appConfig.relays[relayNumber - 1].name = command.substring(secondSpace + 1); Serial.println("OK relay name updated"); return; } @@ -348,14 +322,10 @@ void handleDebugSerial() { if (command.startsWith("tempname ")) { int firstSpace = command.indexOf(' '); int secondSpace = command.indexOf(' ', firstSpace + 1); - if (secondSpace > 0) { int sensorNumber = command.substring(firstSpace + 1, secondSpace).toInt(); - if (sensorNumber >= 1 && sensorNumber <= MAX_TEMP_SENSORS) { - appConfig.tempSensors[sensorNumber - 1].name = - command.substring(secondSpace + 1); - + appConfig.tempSensors[sensorNumber - 1].name = command.substring(secondSpace + 1); Serial.println("OK temp sensor name updated"); return; } @@ -370,6 +340,7 @@ void handleDebugSerial() { if (command.startsWith("bmsaddr ")) { appConfig.bms.address = command.substring(8); + appConfig.bms.enabled = appConfig.bms.address.length() > 0; Serial.println("OK bms address updated"); return; } @@ -398,7 +369,6 @@ void handleDebugSerial() { } const BleScanResult* result = getBleScanResult(selected - 1); - if (!result) { Serial.println("Invalid BMS selection. Run: scan ble"); return; @@ -407,12 +377,7 @@ void handleDebugSerial() { appConfig.bms.enabled = true; appConfig.bms.address = result->address; appConfig.bms.addressType = "public"; - - if (result->name.length() > 0) { - appConfig.bms.name = result->name; - } else { - appConfig.bms.name = "BMS"; - } + appConfig.bms.name = result->name.length() > 0 ? result->name : "BMS"; saveConfig(); @@ -428,42 +393,16 @@ void handleDebugSerial() { if (command.length() > 0) { Serial.print("Unknown command: "); Serial.println(command); - Serial.println("Commands: status, relay starlink on/off, relay fridge on/off"); + Serial.println("Commands: status, config, save, relay 1 on/off, relay 2 on/off, relayname N , tempname N , bmsname , bmsaddr , enter setup, scan ble, select bms N, exit setup"); } } - void sendConfigJson() { DynamicJsonDocument doc(4096); - - doc["device_name"] = appConfig.deviceName; - - JsonArray relaysArray = doc.createNestedArray("relays"); - for (int i = 0; i < MAX_RELAYS; i++) { - JsonObject relay = relaysArray.createNestedObject(); - relay["id"] = appConfig.relays[i].id; - relay["name"] = appConfig.relays[i].name; - relay["pin"] = appConfig.relays[i].pin; - relay["enabled"] = appConfig.relays[i].enabled; - } - - JsonObject bms = doc.createNestedObject("bms"); - bms["enabled"] = appConfig.bms.enabled; - bms["name"] = appConfig.bms.name; - bms["address"] = appConfig.bms.address; - bms["address_type"] = appConfig.bms.addressType; - - JsonArray temps = doc.createNestedArray("temperature_sensors"); - for (int i = 0; i < MAX_TEMP_SENSORS; i++) { - JsonObject temp = temps.createNestedObject(); - temp["id"] = appConfig.tempSensors[i].id; - temp["name"] = appConfig.tempSensors[i].name; - temp["address"] = appConfig.tempSensors[i].address; - temp["enabled"] = appConfig.tempSensors[i].enabled; - } + addConfigStatus(doc); String output; - serializeJson(doc, output); + serializeJson(doc["config"], output); server.send(200, "application/json", output); } @@ -474,18 +413,14 @@ void sendOkConfig() { int findRelayConfigIndex(const String& id) { for (int i = 0; i < MAX_RELAYS; i++) { - if (appConfig.relays[i].id == id) { - return i; - } + if (appConfig.relays[i].id == id) return i; } return -1; } int findTempSensorConfigIndex(const String& id) { for (int i = 0; i < MAX_TEMP_SENSORS; i++) { - if (appConfig.tempSensors[i].id == id) { - return i; - } + if (appConfig.tempSensors[i].id == id) return i; } return -1; } @@ -497,7 +432,6 @@ void handleGetConfig() { void handleUpdateRelayConfig() { DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, server.arg("plain")); - if (error) { server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}"); return; @@ -505,19 +439,13 @@ void handleUpdateRelayConfig() { String id = doc["id"] | ""; int index = findRelayConfigIndex(id); - if (index < 0) { server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}"); return; } - if (doc["name"].is()) { - appConfig.relays[index].name = doc["name"].as(); - } - - if (doc["enabled"].is()) { - appConfig.relays[index].enabled = doc["enabled"].as(); - } + if (doc["name"].is()) appConfig.relays[index].name = doc["name"].as(); + if (doc["enabled"].is()) appConfig.relays[index].enabled = doc["enabled"].as(); sendOkConfig(); } @@ -525,27 +453,15 @@ void handleUpdateRelayConfig() { void handleUpdateBmsConfig() { DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, server.arg("plain")); - if (error) { server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}"); return; } - if (doc["enabled"].is()) { - appConfig.bms.enabled = doc["enabled"].as(); - } - - if (doc["name"].is()) { - appConfig.bms.name = doc["name"].as(); - } - - if (doc["address"].is()) { - appConfig.bms.address = doc["address"].as(); - } - - if (doc["address_type"].is()) { - appConfig.bms.addressType = doc["address_type"].as(); - } + if (doc["enabled"].is()) appConfig.bms.enabled = doc["enabled"].as(); + if (doc["name"].is()) appConfig.bms.name = doc["name"].as(); + if (doc["address"].is()) appConfig.bms.address = doc["address"].as(); + if (doc["address_type"].is()) appConfig.bms.addressType = doc["address_type"].as(); sendOkConfig(); } @@ -553,7 +469,6 @@ void handleUpdateBmsConfig() { void handleUpdateTempSensorConfig() { DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, server.arg("plain")); - if (error) { server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}"); return; @@ -561,23 +476,14 @@ void handleUpdateTempSensorConfig() { String id = doc["id"] | ""; int index = findTempSensorConfigIndex(id); - if (index < 0) { server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}"); return; } - if (doc["name"].is()) { - appConfig.tempSensors[index].name = doc["name"].as(); - } - - if (doc["address"].is()) { - appConfig.tempSensors[index].address = doc["address"].as(); - } - - if (doc["enabled"].is()) { - appConfig.tempSensors[index].enabled = doc["enabled"].as(); - } + if (doc["name"].is()) appConfig.tempSensors[index].name = doc["name"].as(); + if (doc["address"].is()) appConfig.tempSensors[index].address = doc["address"].as(); + if (doc["enabled"].is()) appConfig.tempSensors[index].enabled = doc["enabled"].as(); sendOkConfig(); } @@ -587,17 +493,10 @@ void handleFactoryResetConfig() { sendConfigJson(); } - void handleGenericRelayRoute() { String uri = server.uri(); - - // Expected format: - // /relay/relay_1/on - // /relay/relay_1/off - // /relay/relay_2/on - // /relay/relay_2/off - String prefix = "/relay/"; + if (!uri.startsWith(prefix)) { server.send(404, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_route\"}"); return; @@ -616,26 +515,18 @@ void handleGenericRelayRoute() { bool enabled; - if (action == "on") { - enabled = true; - } else if (action == "off") { - enabled = false; - } else { + if (action == "on") enabled = true; + else if (action == "off") enabled = false; + else { server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_action\"}"); return; } - if (relayId == "relay_1") { - relays.relay1 = enabled; - } else if (relayId == "relay_2") { - relays.relay2 = enabled; - } else { + if (!setRelayById(relayId, enabled)) { server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}"); return; } - updateRelayOutputs(); - DynamicJsonDocument doc(512); doc["ok"] = true; doc["id"] = relayId; @@ -647,7 +538,7 @@ void handleGenericRelayRoute() { } void handleStatus() { - DynamicJsonDocument doc(2048); + DynamicJsonDocument doc(4096); buildStatusDocument(doc); String output; @@ -656,40 +547,6 @@ void handleStatus() { server.send(200, "application/json", output); } -void handleRelayHttp(const char* relayName, bool enabled) { - if (!setRelayByName(relayName, enabled)) { - server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}"); - return; - } - - DynamicJsonDocument doc(256); - doc["type"] = MSG_RELAY_RESPONSE; - doc["relay"] = relayName; - doc["enabled"] = enabled; - doc["ok"] = true; - - String output; - serializeJson(doc, output); - - server.send(200, "application/json", output); -} - -void handleStarlinkOn() { - handleRelayHttp("starlink", true); -} - -void handleStarlinkOff() { - handleRelayHttp("starlink", false); -} - -void handleFridgeOn() { - handleRelayHttp("fridge", true); -} - -void handleFridgeOff() { - handleRelayHttp("fridge", false); -} - void setup() { Serial.begin(115200); @@ -702,9 +559,6 @@ void setup() { Serial.println(); Serial.println("=================================="); - - loadConfig(); - printConfig(); Serial.println("Overland Controller Booting"); Serial.println("=================================="); @@ -718,7 +572,6 @@ void setup() { initBms(); WiFi.mode(WIFI_AP); - bool apResult = WiFi.softAP("OverlandController"); if (apResult) { @@ -733,20 +586,12 @@ void setup() { server.on("/relay/relay_2/on", HTTP_GET, handleGenericRelayRoute); server.on("/relay/relay_2/off", HTTP_GET, handleGenericRelayRoute); - server.on("/config", HTTP_GET, handleGetConfig); server.on("/config/relay", HTTP_POST, handleUpdateRelayConfig); server.on("/config/bms", HTTP_POST, handleUpdateBmsConfig); server.on("/config/temp", HTTP_POST, handleUpdateTempSensorConfig); server.on("/config/factory-reset", HTTP_POST, handleFactoryResetConfig); - - server.on("/relay/starlink/on", handleStarlinkOn); - server.on("/relay/starlink/off", handleStarlinkOff); - - server.on("/relay/fridge/on", handleFridgeOn); - server.on("/relay/fridge/off", handleFridgeOff); - server.begin(); Serial.println("Web Server Started"); diff --git a/firmware/esp32/overland-controller/relays.cpp b/firmware/esp32/overland-controller/relays.cpp index ada295e..7fa1ec5 100644 --- a/firmware/esp32/overland-controller/relays.cpp +++ b/firmware/esp32/overland-controller/relays.cpp @@ -5,13 +5,13 @@ RelayState relays; void initRelays() { - pinMode(RELAY_STARLINK_PIN, OUTPUT); - pinMode(RELAY_FRIDGE_PIN, OUTPUT); + pinMode(RELAY_1_PIN, OUTPUT); + pinMode(RELAY_2_PIN, OUTPUT); updateRelayOutputs(); } void updateRelayOutputs() { - digitalWrite(RELAY_STARLINK_PIN, relays.relay1); - digitalWrite(RELAY_FRIDGE_PIN, relays.relay2); + digitalWrite(RELAY_1_PIN, relays.relay1); + digitalWrite(RELAY_2_PIN, relays.relay2); } diff --git a/tools/esp32_bringup_check.sh b/tools/esp32_bringup_check.sh index e5625dc..da7a8c2 100755 --- a/tools/esp32_bringup_check.sh +++ b/tools/esp32_bringup_check.sh @@ -10,20 +10,20 @@ echo "1. Status:" curl -s "$BASE_URL/status" | python3 -m json.tool echo -echo "2. Starlink ON:" -curl -s "$BASE_URL/relay/starlink/on" | python3 -m json.tool +echo "2. Relay 1 ON:" +curl -s "$BASE_URL/relay/relay_1/on" | python3 -m json.tool echo -echo "3. Starlink OFF:" -curl -s "$BASE_URL/relay/starlink/off" | python3 -m json.tool +echo "3. Relay 1 OFF:" +curl -s "$BASE_URL/relay/relay_1/off" | python3 -m json.tool echo -echo "4. Fridge ON:" -curl -s "$BASE_URL/relay/fridge/on" | python3 -m json.tool +echo "4. Relay 2 ON:" +curl -s "$BASE_URL/relay/relay_2/on" | python3 -m json.tool echo -echo "5. Fridge OFF:" -curl -s "$BASE_URL/relay/fridge/off" | python3 -m json.tool +echo "5. Relay 2 OFF:" +curl -s "$BASE_URL/relay/relay_2/off" | python3 -m json.tool echo echo "ESP32 HTTP bring-up test complete."