|
|
|
@@ -1,4 +1,5 @@
|
|
|
|
|
#include <WiFi.h>
|
|
|
|
|
#include <esp_heap_caps.h>
|
|
|
|
|
#include <WebServer.h>
|
|
|
|
|
#include <Preferences.h>
|
|
|
|
|
#include <esp_wifi.h>
|
|
|
|
@@ -638,12 +639,15 @@ button[onclick^="scan"]{
|
|
|
|
|
<input id="bmsAddressTypeInput" placeholder="Address type: public or random">
|
|
|
|
|
<div class="btnrow">
|
|
|
|
|
<button class="save" onclick="saveBmsFullConfig()">Save BMS</button>
|
|
|
|
|
<button onclick="scanBmsDevices()">Scan BMS</button>
|
|
|
|
|
<button class="off" onclick="setBmsEnabled(false)">Disable BMS</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="btnrow">
|
|
|
|
|
<button onclick="setBmsEnabled(true)">Enable BMS</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row"><span class="muted">BMS Polling</span><span class="v" id="bmsEnabledText">--</span></div>
|
|
|
|
|
<div id="bmsScanStatus" class="muted"></div>
|
|
|
|
|
<div id="bmsScanResults"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
@@ -1045,12 +1049,12 @@ function render(data){
|
|
|
|
|
|
|
|
|
|
function populateSetupFields(data){
|
|
|
|
|
const cfg=data.config||{};
|
|
|
|
|
if($("deviceNameInput")) $("deviceNameInput").value=cfg.device_name||"";
|
|
|
|
|
setConfigValue("deviceNameInput", cfg.device_name||"");
|
|
|
|
|
|
|
|
|
|
const bms=cfg.bms||{};
|
|
|
|
|
if($("bmsNameInput")) $("bmsNameInput").value=bms.name||"";
|
|
|
|
|
if($("bmsAddressInput")) $("bmsAddressInput").value=bms.address||"";
|
|
|
|
|
if($("bmsAddressTypeInput")) $("bmsAddressTypeInput").value=bms.address_type||"public";
|
|
|
|
|
setConfigValue("bmsNameInput", bms.name||"");
|
|
|
|
|
setConfigValue("bmsAddressInput", bms.address||"");
|
|
|
|
|
setConfigValue("bmsAddressTypeInput", bms.address_type||"public");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveDeviceConfig(){
|
|
|
|
@@ -1069,6 +1073,46 @@ async function saveDeviceConfig(){
|
|
|
|
|
clearConfigTouched(["deviceNameInput"]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function scanBmsDevices(){
|
|
|
|
|
const status=$("bmsScanStatus");
|
|
|
|
|
const box=$("bmsScanResults");
|
|
|
|
|
if(status) status.textContent="Scanning for BLE BMS devices...";
|
|
|
|
|
if(box) box.innerHTML="";
|
|
|
|
|
|
|
|
|
|
try{
|
|
|
|
|
const res=await fetch(api("/bms/scan"),{method:"POST"});
|
|
|
|
|
const data=await res.json();
|
|
|
|
|
const devices=data.devices||[];
|
|
|
|
|
|
|
|
|
|
if(status) status.textContent=devices.length ? `Found ${devices.length} device(s).` : "No BLE devices found.";
|
|
|
|
|
|
|
|
|
|
if(box){
|
|
|
|
|
box.innerHTML=devices.map(d=>`
|
|
|
|
|
<div class="configrow">
|
|
|
|
|
<div>
|
|
|
|
|
<div><b>${d.name||"Unnamed BLE device"}</b></div>
|
|
|
|
|
<div class="muted">${d.address||""} RSSI ${d.rssi??"--"}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button onclick="selectScannedBms('${d.address||""}','${(d.name||"BMS").replace(/'/g,"\\'")}')">Select</button>
|
|
|
|
|
</div>
|
|
|
|
|
`).join("");
|
|
|
|
|
}
|
|
|
|
|
}catch(e){
|
|
|
|
|
if(status) status.textContent="BMS scan failed.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectScannedBms(address,name){
|
|
|
|
|
setConfigValue("bmsNameInput",name||"BMS",true);
|
|
|
|
|
setConfigValue("bmsAddressInput",address||"",true);
|
|
|
|
|
setConfigValue("bmsAddressTypeInput","public",true);
|
|
|
|
|
markConfigFieldTouched("bmsNameInput");
|
|
|
|
|
markConfigFieldTouched("bmsAddressInput");
|
|
|
|
|
markConfigFieldTouched("bmsAddressTypeInput");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function saveBmsFullConfig(){
|
|
|
|
|
const name=$("bmsNameInput")?.value.trim()||"BMS";
|
|
|
|
|
const address=$("bmsAddressInput")?.value.trim()||"";
|
|
|
|
@@ -1198,12 +1242,18 @@ function renderConfigControls(data){
|
|
|
|
|
|
|
|
|
|
const relayBox=$("relayConfigList");
|
|
|
|
|
if(relayBox){
|
|
|
|
|
relayBox.innerHTML=relays.map((r,i)=>`
|
|
|
|
|
<div class="configrow">
|
|
|
|
|
<label>${r.id}</label>
|
|
|
|
|
<input id="relayName${i}" value="${r.name||""}" placeholder="Relay name">
|
|
|
|
|
<label>${r.enabled?"Enabled":"Disabled"}</label>
|
|
|
|
|
</div>`).join("");
|
|
|
|
|
const signature=relays.map(r=>r.id).join("|");
|
|
|
|
|
if(relayBox.dataset.signature!==signature){
|
|
|
|
|
relayBox.dataset.signature=signature;
|
|
|
|
|
relayBox.innerHTML=relays.map((r,i)=>`
|
|
|
|
|
<div class="configrow">
|
|
|
|
|
<label>${r.id}</label>
|
|
|
|
|
<input id="relayName${i}" placeholder="Relay name" oninput="markConfigFieldTouched('relayName${i}')">
|
|
|
|
|
<label>${r.enabled?"Enabled":"Disabled"}</label>
|
|
|
|
|
</div>`).join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
relays.forEach((r,i)=>setConfigValue("relayName"+i,r.name||""));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setConfigValue("tempEnabledCount", temps.filter(t=>t.enabled).length);
|
|
|
|
@@ -1459,6 +1509,9 @@ setInterval(load,3000);
|
|
|
|
|
|
|
|
|
|
WebServer server(80);
|
|
|
|
|
|
|
|
|
|
unsigned long lastSensorUpdate = 0;
|
|
|
|
|
const unsigned long SENSOR_INTERVAL = 5000;
|
|
|
|
|
|
|
|
|
|
#define API_V1(path) "/api/v1" path
|
|
|
|
|
|
|
|
|
|
int findTempConfigIndexById(const String& id) {
|
|
|
|
@@ -1649,7 +1702,6 @@ void sendSimpleJsonError(int status, const char* error, const char* detail) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const char OTA_HTML[] PROGMEM = R"rawliteral(
|
|
|
|
|
<!doctype html>
|
|
|
|
|
<html>
|
|
|
|
@@ -2428,9 +2480,6 @@ int findStatusFieldIndex(const String& field) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void buildStatusDocument(JsonDocument& doc) {
|
|
|
|
|
doc["type"] = MSG_STATUS_RESPONSE;
|
|
|
|
|
doc["timestamp"] = millis();
|
|
|
|
@@ -3656,7 +3705,6 @@ void handleFactoryResetConfig() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void sendWifiConfigJson() {
|
|
|
|
|
DynamicJsonDocument doc(2048);
|
|
|
|
|
|
|
|
|
@@ -4067,67 +4115,53 @@ void handleStatus() {
|
|
|
|
|
server.send(200, "application/json", output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setup() {
|
|
|
|
|
Serial.begin(115200);
|
|
|
|
|
|
|
|
|
|
#if DASHBOARD_UART_ENABLED
|
|
|
|
|
DashboardSerial.begin(
|
|
|
|
|
DASHBOARD_UART_BAUD,
|
|
|
|
|
SERIAL_8N1,
|
|
|
|
|
0,
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
Serial.println("Legacy dashboard UART enabled");
|
|
|
|
|
#endif
|
|
|
|
|
delay(2000);
|
|
|
|
|
|
|
|
|
|
Serial.println();
|
|
|
|
|
Serial.println("==================================");
|
|
|
|
|
Serial.println("Overland Controller Booting");
|
|
|
|
|
Serial.println("==================================");
|
|
|
|
|
|
|
|
|
|
oledBegin();
|
|
|
|
|
Serial.println("Overland Controller LilyGO T-Relay S3 Booting");
|
|
|
|
|
|
|
|
|
|
loadConfig();
|
|
|
|
|
|
|
|
|
|
printConfig();
|
|
|
|
|
|
|
|
|
|
pinMode(IGNITION_PIN, INPUT);
|
|
|
|
|
|
|
|
|
|
initRelays();
|
|
|
|
|
|
|
|
|
|
initSensors();
|
|
|
|
|
|
|
|
|
|
initBms();
|
|
|
|
|
|
|
|
|
|
loadWifiConfig();
|
|
|
|
|
|
|
|
|
|
loadApConfig();
|
|
|
|
|
|
|
|
|
|
WiFi.persistent(false);
|
|
|
|
|
WiFi.setSleep(false);
|
|
|
|
|
WiFi.mode(WIFI_AP_STA);
|
|
|
|
|
startAccessPoint();
|
|
|
|
|
oledShowSetupPage();
|
|
|
|
|
|
|
|
|
|
// Do not block setup on STA WiFi. Start the AP and web server first so the
|
|
|
|
|
// local Web UI/API remains available even when no saved WiFi networks exist.
|
|
|
|
|
lastStaReconnectAttempt = millis() - STA_RECONNECT_INTERVAL_MS;
|
|
|
|
|
startAccessPoint();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on("/", HTTP_GET, []() {
|
|
|
|
|
server.send_P(200, "text/html", INDEX_HTML);
|
|
|
|
|
});
|
|
|
|
|
server.on("/ota", HTTP_GET, handleOtaPage);
|
|
|
|
|
server.on(API_V1("/system/ota"), HTTP_POST, handleOtaUploadDone, handleOtaUploadStream);
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/status"), handleStatus);
|
|
|
|
|
server.on(API_V1("/health"), HTTP_GET, handleHealth);
|
|
|
|
|
server.on(API_V1("/capabilities"), HTTP_GET, handleCapabilities);
|
|
|
|
|
server.on(API_V1("/status"), handleStatus);
|
|
|
|
|
server.on(API_V1("/relay/set"), HTTP_POST, handleSetRelayPost);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/config"), HTTP_GET, handleGetConfig);
|
|
|
|
|
server.on(API_V1("/config/export"), HTTP_GET, handleExportConfig);
|
|
|
|
|
server.on(API_V1("/config/import"), HTTP_POST, handleImportConfig);
|
|
|
|
|
server.on(API_V1("/config/wifi"), HTTP_GET, handleGetWifiConfig);
|
|
|
|
|
server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig);
|
|
|
|
|
server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig);
|
|
|
|
|
server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);
|
|
|
|
|
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
|
|
|
|
|
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
|
|
|
|
|
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/config/device"), HTTP_POST, handleUpdateDeviceConfig);
|
|
|
|
|
server.on(API_V1("/config/relay"), HTTP_POST, handleUpdateRelayConfig);
|
|
|
|
@@ -4136,67 +4170,59 @@ void setup() {
|
|
|
|
|
server.on(API_V1("/config/factory-reset"), HTTP_POST, handleFactoryResetConfig);
|
|
|
|
|
server.on(API_V1("/config/save"), HTTP_POST, handleSaveConfig);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
|
|
|
|
|
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
|
|
|
|
|
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/bms/scan"), HTTP_POST, handleBleScan);
|
|
|
|
|
server.on(API_V1("/bms/select"), HTTP_POST, handleSelectBms);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/temps/scan"), HTTP_POST, handleTempScan);
|
|
|
|
|
server.on(API_V1("/temps/assign"), HTTP_POST, handleTempAssign);
|
|
|
|
|
server.on(API_V1("/temps/clear"), HTTP_POST, handleTempClear);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on("/update", HTTP_GET, handleOtaPage);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on("/ota", HTTP_GET, handleOtaPage);
|
|
|
|
|
server.on(API_V1("/system/ota"), HTTP_POST,
|
|
|
|
|
handleOtaUploadDone,
|
|
|
|
|
handleOtaUploadStream);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.on(API_V1("/config/import"), HTTP_POST, handleImportConfig);
|
|
|
|
|
server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);
|
|
|
|
|
server.on(API_V1("/bms/setup/enter"), HTTP_POST, handleEnterBmsSetup);
|
|
|
|
|
server.on(API_V1("/bms/setup/exit"), HTTP_POST, handleExitBmsSetup);
|
|
|
|
|
server.on(API_V1("/bms/scan"), HTTP_POST, handleBleScan);
|
|
|
|
|
server.on(API_V1("/bms/select"), HTTP_POST, handleSelectBms);
|
|
|
|
|
|
|
|
|
|
// Compatibility aliases for pre-versioned local clients.
|
|
|
|
|
server.on("/status", handleStatus);
|
|
|
|
|
server.on("/relay/set", HTTP_POST, handleSetRelayPost);
|
|
|
|
|
server.on("/relay/relay_1/on", HTTP_GET, handleGenericRelayRoute);
|
|
|
|
|
server.on("/relay/relay_1/off", HTTP_GET, handleGenericRelayRoute);
|
|
|
|
|
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/wifi", HTTP_GET, handleGetWifiConfig);
|
|
|
|
|
server.on("/config/wifi", HTTP_POST, handleUpdateWifiConfig);
|
|
|
|
|
server.on("/wifi/connect", HTTP_POST, handleWifiConnect);
|
|
|
|
|
server.on("/wifi/clear", HTTP_POST, handleWifiClear);
|
|
|
|
|
|
|
|
|
|
server.on("/config/device", HTTP_POST, handleUpdateDeviceConfig);
|
|
|
|
|
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("/config/save", HTTP_POST, handleSaveConfig);
|
|
|
|
|
|
|
|
|
|
server.on("/temps/scan", HTTP_POST, handleTempScan);
|
|
|
|
|
server.on("/temps/assign", HTTP_POST, handleTempAssign);
|
|
|
|
|
server.on("/temps/clear", HTTP_POST, handleTempClear);
|
|
|
|
|
|
|
|
|
|
server.on("/bms/setup/enter", HTTP_POST, handleEnterBmsSetup);
|
|
|
|
|
server.on("/bms/setup/exit", HTTP_POST, handleExitBmsSetup);
|
|
|
|
|
server.on("/bms/scan", HTTP_POST, handleBleScan);
|
|
|
|
|
server.on("/bms/select", HTTP_POST, handleSelectBms);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server.begin();
|
|
|
|
|
|
|
|
|
|
Serial.println("Web Server Started");
|
|
|
|
|
lastStaReconnectAttempt = millis() - STA_RECONNECT_INTERVAL_MS + 5000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop() {
|
|
|
|
|
server.handleClient();
|
|
|
|
|
|
|
|
|
|
maintainStaWifi();
|
|
|
|
|
handleDebugSerial();
|
|
|
|
|
updateBms();
|
|
|
|
|
updateAlarms();
|
|
|
|
|
#if DASHBOARD_UART_ENABLED
|
|
|
|
|
pollDashboardUart();
|
|
|
|
|
#endif
|
|
|
|
|
oledLoop();
|
|
|
|
|
|
|
|
|
|
static unsigned long lastSensorUpdate = 0;
|
|
|
|
|
|
|
|
|
|
if (millis() - lastSensorUpdate > 5000) {
|
|
|
|
|
if (millis() - lastSensorUpdate > SENSOR_INTERVAL) {
|
|
|
|
|
updateSensors();
|
|
|
|
|
lastSensorUpdate = millis();
|
|
|
|
|
logDebug("Sensor Update");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (millis() > 30000) {
|
|
|
|
|
updateBms();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if DASHBOARD_UART_ENABLED
|
|
|
|
|
pollDashboardUart();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
delay(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|