Make ESP32 firmware API fully generic
This commit is contained in:
+37
-199
@@ -6,79 +6,29 @@ Base URL while connected to the ESP32 access point:
|
|||||||
|
|
||||||
http://192.168.4.1
|
http://192.168.4.1
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
### GET /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:
|
Relay states use generic IDs:
|
||||||
|
|
||||||
- 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_1
|
relay_1
|
||||||
relay_2
|
relay_2
|
||||||
|
|
||||||
Temperature sensors use generic sensor IDs:
|
Temperature sensors use generic IDs:
|
||||||
|
|
||||||
temp_1
|
temp_1 through temp_8
|
||||||
temp_2
|
|
||||||
temp_3
|
|
||||||
temp_4
|
|
||||||
temp_5
|
|
||||||
temp_6
|
|
||||||
temp_7
|
|
||||||
temp_8
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Relay Control API
|
## 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.
|
Relay display names are configurable. Firmware and API clients should not assume what a relay controls.
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration API
|
## Configuration API
|
||||||
|
|
||||||
@@ -86,13 +36,9 @@ Future work should remove these aliases after the Pico/dashboard clients use gen
|
|||||||
|
|
||||||
Returns saved configuration.
|
Returns saved configuration.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### POST /config/relay
|
### POST /config/relay
|
||||||
|
|
||||||
Updates relay config.
|
Example:
|
||||||
|
|
||||||
Example body:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": "relay_1",
|
"id": "relay_1",
|
||||||
@@ -100,18 +46,9 @@ Example body:
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
|
|
||||||
Valid IDs:
|
|
||||||
|
|
||||||
- relay_1
|
|
||||||
- relay_2
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### POST /config/bms
|
### POST /config/bms
|
||||||
|
|
||||||
Updates BMS config.
|
Example:
|
||||||
|
|
||||||
Example body:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -120,13 +57,9 @@ Example body:
|
|||||||
"address_type": "public"
|
"address_type": "public"
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### POST /config/temp
|
### POST /config/temp
|
||||||
|
|
||||||
Updates temperature sensor config.
|
Example:
|
||||||
|
|
||||||
Example body:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": "temp_1",
|
"id": "temp_1",
|
||||||
@@ -135,24 +68,9 @@ Example body:
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
|
|
||||||
Valid IDs:
|
|
||||||
|
|
||||||
- temp_1
|
|
||||||
- temp_2
|
|
||||||
- temp_3
|
|
||||||
- temp_4
|
|
||||||
- temp_5
|
|
||||||
- temp_6
|
|
||||||
- temp_7
|
|
||||||
- temp_8
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### POST /config/factory-reset
|
### POST /config/factory-reset
|
||||||
|
|
||||||
Clears saved config and restores generic defaults.
|
Clears saved configuration and restores generic defaults.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Serial Monitor
|
## Serial Monitor
|
||||||
|
|
||||||
@@ -164,135 +82,59 @@ Line ending:
|
|||||||
|
|
||||||
Newline
|
Newline
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Status and Config Commands
|
## Status and Config Commands
|
||||||
|
|
||||||
### status
|
status
|
||||||
|
config
|
||||||
Prints full JSON status to Serial.
|
save
|
||||||
|
factory reset
|
||||||
### config
|
|
||||||
|
|
||||||
Prints current configuration to Serial.
|
|
||||||
|
|
||||||
### save
|
|
||||||
|
|
||||||
Saves current in-memory configuration to NVS.
|
|
||||||
|
|
||||||
### factory reset
|
|
||||||
|
|
||||||
Clears saved config and restores defaults.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Log Level Commands
|
## Log Level Commands
|
||||||
|
|
||||||
### log quiet
|
log quiet
|
||||||
|
log info
|
||||||
Minimizes serial output.
|
log debug
|
||||||
|
|
||||||
### log info
|
|
||||||
|
|
||||||
Normal serial output.
|
|
||||||
|
|
||||||
### log debug
|
|
||||||
|
|
||||||
Enables recurring debug output.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Relay Control Commands
|
## Relay Control Commands
|
||||||
|
|
||||||
Legacy relay control commands may still exist during development:
|
relay 1 on
|
||||||
|
relay 1 off
|
||||||
relay starlink on
|
relay 2 on
|
||||||
relay starlink off
|
relay 2 off
|
||||||
relay fridge on
|
|
||||||
relay fridge off
|
|
||||||
|
|
||||||
Future work should replace these with generic serial relay commands.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Relay Configuration Commands
|
## Relay Configuration Commands
|
||||||
|
|
||||||
### relayname 1 Aux Power
|
relayname 1 Aux Power
|
||||||
|
relayname 2 Accessory Power
|
||||||
Sets relay 1 display name.
|
|
||||||
|
|
||||||
### relayname 2 Accessory Power
|
|
||||||
|
|
||||||
Sets relay 2 display name.
|
|
||||||
|
|
||||||
Run `save` after changing names.
|
Run `save` after changing names.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Temperature Sensor Configuration Commands
|
## Temperature Sensor Configuration Commands
|
||||||
|
|
||||||
### tempname 1 Cabin
|
tempname 1 Cabin
|
||||||
|
tempname 2 Outside Air
|
||||||
Sets temp sensor 1 display name.
|
|
||||||
|
|
||||||
### tempname 2 Outside Air
|
|
||||||
|
|
||||||
Sets temp sensor 2 display name.
|
|
||||||
|
|
||||||
Run `save` after changing names.
|
Run `save` after changing names.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## BMS Configuration Commands
|
## BMS Configuration Commands
|
||||||
|
|
||||||
### bmsname House Battery
|
bmsname House Battery
|
||||||
|
bmsaddr aa:bb:cc:dd:ee:ff
|
||||||
Sets BMS display name.
|
|
||||||
|
|
||||||
### bmsaddr aa:bb:cc:dd:ee:ff
|
|
||||||
|
|
||||||
Sets BMS BLE address.
|
|
||||||
|
|
||||||
Run `save` after changing BMS config manually.
|
Run `save` after changing BMS config manually.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## BMS Setup Mode Commands
|
## BMS Setup Mode Commands
|
||||||
|
|
||||||
### enter setup
|
enter setup
|
||||||
|
scan ble
|
||||||
Enters BMS setup mode.
|
select bms 1
|
||||||
|
exit setup
|
||||||
Effects:
|
|
||||||
|
|
||||||
- Disconnects BMS client
|
|
||||||
- Pauses BMS reconnect
|
|
||||||
- Allows repeated BLE scans
|
|
||||||
|
|
||||||
### scan ble
|
|
||||||
|
|
||||||
Scans for BLE devices.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Some BMS modules advertise intermittently.
|
- Some BMS modules advertise intermittently.
|
||||||
- Multiple scans may be required.
|
- Multiple BLE scans may be required.
|
||||||
|
- Once a BMS address is configured, BMS telemetry reconnects directly using the saved address.
|
||||||
### 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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Typical First-Time Setup Flow
|
## Typical First-Time Setup Flow
|
||||||
|
|
||||||
@@ -308,11 +150,7 @@ Leaves BMS setup mode and allows BMS reconnect.
|
|||||||
save
|
save
|
||||||
config
|
config
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Notes
|
## 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.
|
- 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.
|
||||||
|
|||||||
+2
-237
@@ -1,238 +1,3 @@
|
|||||||
# Xterra Overland Dashboard API Specification
|
# Overland Controller API
|
||||||
|
|
||||||
## Overview
|
See `docs/api-and-serial-commands.md` for the current HTTP API and Serial command reference.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ void loadDefaultConfig() {
|
|||||||
appConfig.relays[0] = {
|
appConfig.relays[0] = {
|
||||||
"relay_1",
|
"relay_1",
|
||||||
"Relay 1",
|
"Relay 1",
|
||||||
RELAY_STARLINK_PIN,
|
RELAY_1_PIN,
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
appConfig.relays[1] = {
|
appConfig.relays[1] = {
|
||||||
"relay_2",
|
"relay_2",
|
||||||
"Relay 2",
|
"Relay 2",
|
||||||
RELAY_FRIDGE_PIN,
|
RELAY_2_PIN,
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ void loadDefaultConfig() {
|
|||||||
void loadConfig() {
|
void loadConfig() {
|
||||||
loadDefaultConfig();
|
loadDefaultConfig();
|
||||||
|
|
||||||
preferences.begin("xterra", true);
|
preferences.begin("overland", true);
|
||||||
|
|
||||||
bool configured = preferences.getBool("configured", false);
|
bool configured = preferences.getBool("configured", false);
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ void loadConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void saveConfig() {
|
void saveConfig() {
|
||||||
preferences.begin("xterra", false);
|
preferences.begin("overland", false);
|
||||||
|
|
||||||
preferences.putBool("configured", true);
|
preferences.putBool("configured", true);
|
||||||
preferences.putString("device", appConfig.deviceName);
|
preferences.putString("device", appConfig.deviceName);
|
||||||
@@ -135,7 +135,7 @@ void saveConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void factoryResetConfig() {
|
void factoryResetConfig() {
|
||||||
preferences.begin("xterra", false);
|
preferences.begin("overland", false);
|
||||||
preferences.clear();
|
preferences.clear();
|
||||||
preferences.end();
|
preferences.end();
|
||||||
|
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ static bool parseCellVoltages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void initBms() {
|
void initBms() {
|
||||||
NimBLEDevice::init("XterraESP32");
|
NimBLEDevice::init("OverlandController");
|
||||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||||
resetResponseBuffer();
|
resetResponseBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
#define DEVICE_NAME "Overland Controller"
|
#define DEVICE_NAME "Overland Controller"
|
||||||
|
|
||||||
// Relay Outputs
|
// Relay Outputs
|
||||||
#define RELAY_STARLINK_PIN 16
|
#define RELAY_1_PIN 16
|
||||||
#define RELAY_FRIDGE_PIN 17
|
#define RELAY_2_PIN 17
|
||||||
|
|
||||||
// DS18B20 Bus
|
// DS18B20 Bus
|
||||||
#define ONEWIRE_PIN 4
|
#define ONEWIRE_PIN 4
|
||||||
|
|||||||
@@ -18,15 +18,9 @@ HardwareSerial DashboardSerial(2);
|
|||||||
String uartLineBuffer;
|
String uartLineBuffer;
|
||||||
|
|
||||||
float calculateRuntimeHours() {
|
float calculateRuntimeHours() {
|
||||||
if (!bmsData.valid || bmsData.current >= 0) {
|
if (!bmsData.valid || bmsData.current >= 0) return 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
float dischargeAmps = -bmsData.current;
|
float dischargeAmps = -bmsData.current;
|
||||||
if (dischargeAmps <= 0.1) {
|
if (dischargeAmps <= 0.1) return 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bmsData.remainingAh / dischargeAmps;
|
return bmsData.remainingAh / dischargeAmps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +29,6 @@ void buildStatusDocument(JsonDocument& doc) {
|
|||||||
doc["timestamp"] = millis();
|
doc["timestamp"] = millis();
|
||||||
|
|
||||||
JsonObject battery = doc.createNestedObject("battery");
|
JsonObject battery = doc.createNestedObject("battery");
|
||||||
|
|
||||||
if (bmsData.valid) {
|
if (bmsData.valid) {
|
||||||
battery["source"] = "jbd_bms";
|
battery["source"] = "jbd_bms";
|
||||||
battery["connected"] = bmsData.connected;
|
battery["connected"] = bmsData.connected;
|
||||||
@@ -52,9 +45,7 @@ void buildStatusDocument(JsonDocument& doc) {
|
|||||||
|
|
||||||
JsonArray cells = battery.createNestedArray("cell_voltages");
|
JsonArray cells = battery.createNestedArray("cell_voltages");
|
||||||
if (bmsData.cellsValid) {
|
if (bmsData.cellsValid) {
|
||||||
for (int i = 0; i < bmsData.cellCount; i++) {
|
for (int i = 0; i < bmsData.cellCount; i++) cells.add(bmsData.cellVoltages[i]);
|
||||||
cells.add(bmsData.cellVoltages[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
battery["cell_min_voltage"] = bmsData.cellMinVoltage;
|
battery["cell_min_voltage"] = bmsData.cellMinVoltage;
|
||||||
@@ -62,55 +53,52 @@ void buildStatusDocument(JsonDocument& doc) {
|
|||||||
battery["cell_delta_mv"] = bmsData.cellDeltaMv;
|
battery["cell_delta_mv"] = bmsData.cellDeltaMv;
|
||||||
battery["cells_valid"] = bmsData.cellsValid;
|
battery["cells_valid"] = bmsData.cellsValid;
|
||||||
} else {
|
} else {
|
||||||
battery["source"] = "placeholder";
|
battery["source"] = "unconfigured";
|
||||||
battery["connected"] = false;
|
battery["connected"] = false;
|
||||||
battery["soc"] = 0;
|
battery["soc"] = 0;
|
||||||
battery["voltage"] = 0;
|
battery["voltage"] = 0;
|
||||||
battery["current"] = 0;
|
battery["current"] = 0;
|
||||||
battery["remaining_ah"] = 0;
|
battery["remaining_ah"] = 0;
|
||||||
battery["capacity_ah"] = 150;
|
battery["capacity_ah"] = 0;
|
||||||
battery["runtime_hours"] = 0;
|
battery["runtime_hours"] = 0;
|
||||||
battery["temperature_f"] = 0;
|
battery["temperature_f"] = 0;
|
||||||
battery["cycle_count"] = 0;
|
battery["cycle_count"] = 0;
|
||||||
battery["cell_count"] = 0;
|
battery["cell_count"] = 0;
|
||||||
battery["ntc_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) {
|
JsonArray temps = doc.createNestedArray("temps");
|
||||||
temps["fridge_zone_1"] = sensors.temp1;
|
int tempCount = appConfig.tempSensorCount > 4 ? 4 : appConfig.tempSensorCount;
|
||||||
} else {
|
|
||||||
temps["fridge_zone_1"] = nullptr;
|
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) {
|
JsonArray relayStates = doc.createNestedArray("relays");
|
||||||
temps["fridge_zone_2"] = sensors.temp2;
|
|
||||||
} else {
|
|
||||||
temps["fridge_zone_2"] = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sensors.temp3Online) {
|
JsonObject relay1 = relayStates.createNestedObject();
|
||||||
temps["rear_seat"] = sensors.temp3;
|
relay1["id"] = appConfig.relays[0].id;
|
||||||
} else {
|
relay1["name"] = appConfig.relays[0].name;
|
||||||
temps["rear_seat"] = nullptr;
|
relay1["pin"] = appConfig.relays[0].pin;
|
||||||
}
|
relay1["enabled"] = appConfig.relays[0].enabled;
|
||||||
|
relay1["state"] = relays.relay1;
|
||||||
|
|
||||||
if (sensors.temp4Online) {
|
JsonObject relay2 = relayStates.createNestedObject();
|
||||||
temps["outside"] = sensors.temp4;
|
relay2["id"] = appConfig.relays[1].id;
|
||||||
} else {
|
relay2["name"] = appConfig.relays[1].name;
|
||||||
temps["outside"] = nullptr;
|
relay2["pin"] = appConfig.relays[1].pin;
|
||||||
}
|
relay2["enabled"] = appConfig.relays[1].enabled;
|
||||||
|
relay2["state"] = relays.relay2;
|
||||||
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 vehicle = doc.createNestedObject("vehicle");
|
JsonObject vehicle = doc.createNestedObject("vehicle");
|
||||||
vehicle["ignition_on"] = digitalRead(IGNITION_PIN);
|
vehicle["ignition_on"] = digitalRead(IGNITION_PIN);
|
||||||
@@ -163,15 +151,10 @@ void buildStatusDocument(JsonDocument& doc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sendStatus(Stream& output, bool pretty = false) {
|
void sendStatus(Stream& output, bool pretty = false) {
|
||||||
DynamicJsonDocument doc(2048);
|
DynamicJsonDocument doc(4096);
|
||||||
buildStatusDocument(doc);
|
buildStatusDocument(doc);
|
||||||
|
if (pretty) serializeJsonPretty(doc, output);
|
||||||
if (pretty) {
|
else serializeJson(doc, output);
|
||||||
serializeJsonPretty(doc, output);
|
|
||||||
} else {
|
|
||||||
serializeJson(doc, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.println();
|
output.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,23 +166,19 @@ void sendError(Stream& output, const char* message) {
|
|||||||
output.println();
|
output.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setRelayByName(const char* relayName, bool enabled) {
|
bool setRelayById(const String& relayId, bool enabled) {
|
||||||
if (strcmp(relayName, "starlink") == 0) {
|
if (relayId == "relay_1") relays.relay1 = enabled;
|
||||||
relays.relay1 = enabled;
|
else if (relayId == "relay_2") relays.relay2 = enabled;
|
||||||
} else if (strcmp(relayName, "fridge") == 0) {
|
else return false;
|
||||||
relays.relay2 = enabled;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRelayOutputs();
|
updateRelayOutputs();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendRelayResponse(Stream& output, const char* relayName, bool enabled) {
|
void sendRelayResponse(Stream& output, const String& relayId, bool enabled) {
|
||||||
DynamicJsonDocument doc(256);
|
DynamicJsonDocument doc(256);
|
||||||
doc["type"] = MSG_RELAY_RESPONSE;
|
doc["type"] = MSG_RELAY_RESPONSE;
|
||||||
doc["relay"] = relayName;
|
doc["relay"] = relayId;
|
||||||
doc["enabled"] = enabled;
|
doc["enabled"] = enabled;
|
||||||
doc["ok"] = true;
|
doc["ok"] = true;
|
||||||
serializeJson(doc, output);
|
serializeJson(doc, output);
|
||||||
@@ -223,15 +202,15 @@ void handleUartMessage(const String& line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(type, MSG_SET_RELAY) == 0) {
|
if (strcmp(type, MSG_SET_RELAY) == 0) {
|
||||||
const char* relayName = doc["relay"] | "";
|
String relayId = doc["relay"] | "";
|
||||||
bool enabled = doc["enabled"] | false;
|
bool enabled = doc["enabled"] | false;
|
||||||
|
|
||||||
if (!setRelayByName(relayName, enabled)) {
|
if (!setRelayById(relayId, enabled)) {
|
||||||
sendError(DashboardSerial, "unknown_relay");
|
sendError(DashboardSerial, "unknown_relay");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRelayResponse(DashboardSerial, relayName, enabled);
|
sendRelayResponse(DashboardSerial, relayId, enabled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,15 +223,10 @@ void pollDashboardUart() {
|
|||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
uartLineBuffer.trim();
|
uartLineBuffer.trim();
|
||||||
|
if (uartLineBuffer.length() > 0) handleUartMessage(uartLineBuffer);
|
||||||
if (uartLineBuffer.length() > 0) {
|
|
||||||
handleUartMessage(uartLineBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
uartLineBuffer = "";
|
uartLineBuffer = "";
|
||||||
} else if (c != '\r') {
|
} else if (c != '\r') {
|
||||||
uartLineBuffer += c;
|
uartLineBuffer += c;
|
||||||
|
|
||||||
if (uartLineBuffer.length() > 512) {
|
if (uartLineBuffer.length() > 512) {
|
||||||
uartLineBuffer = "";
|
uartLineBuffer = "";
|
||||||
sendError(DashboardSerial, "message_too_long");
|
sendError(DashboardSerial, "message_too_long");
|
||||||
@@ -261,11 +235,8 @@ void pollDashboardUart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void handleDebugSerial() {
|
void handleDebugSerial() {
|
||||||
if (!Serial.available()) {
|
if (!Serial.available()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String command = Serial.readStringUntil('\n');
|
String command = Serial.readStringUntil('\n');
|
||||||
command.trim();
|
command.trim();
|
||||||
@@ -275,30 +246,6 @@ void handleDebugSerial() {
|
|||||||
return;
|
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") {
|
if (command == "config") {
|
||||||
printConfig();
|
printConfig();
|
||||||
return;
|
return;
|
||||||
@@ -328,17 +275,44 @@ void handleDebugSerial() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.startsWith("relayname ")) {
|
if (command == "save") {
|
||||||
|
saveConfig();
|
||||||
|
Serial.println("OK config saved");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.startsWith("relay ")) {
|
||||||
int firstSpace = command.indexOf(' ');
|
int firstSpace = command.indexOf(' ');
|
||||||
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
||||||
|
|
||||||
if (secondSpace > 0) {
|
if (secondSpace > 0) {
|
||||||
int relayNumber = command.substring(firstSpace + 1, secondSpace).toInt();
|
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) {
|
if (relayNumber >= 1 && relayNumber <= MAX_RELAYS) {
|
||||||
appConfig.relays[relayNumber - 1].name =
|
appConfig.relays[relayNumber - 1].name = command.substring(secondSpace + 1);
|
||||||
command.substring(secondSpace + 1);
|
|
||||||
|
|
||||||
Serial.println("OK relay name updated");
|
Serial.println("OK relay name updated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -348,14 +322,10 @@ void handleDebugSerial() {
|
|||||||
if (command.startsWith("tempname ")) {
|
if (command.startsWith("tempname ")) {
|
||||||
int firstSpace = command.indexOf(' ');
|
int firstSpace = command.indexOf(' ');
|
||||||
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
int secondSpace = command.indexOf(' ', firstSpace + 1);
|
||||||
|
|
||||||
if (secondSpace > 0) {
|
if (secondSpace > 0) {
|
||||||
int sensorNumber = command.substring(firstSpace + 1, secondSpace).toInt();
|
int sensorNumber = command.substring(firstSpace + 1, secondSpace).toInt();
|
||||||
|
|
||||||
if (sensorNumber >= 1 && sensorNumber <= MAX_TEMP_SENSORS) {
|
if (sensorNumber >= 1 && sensorNumber <= MAX_TEMP_SENSORS) {
|
||||||
appConfig.tempSensors[sensorNumber - 1].name =
|
appConfig.tempSensors[sensorNumber - 1].name = command.substring(secondSpace + 1);
|
||||||
command.substring(secondSpace + 1);
|
|
||||||
|
|
||||||
Serial.println("OK temp sensor name updated");
|
Serial.println("OK temp sensor name updated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,6 +340,7 @@ void handleDebugSerial() {
|
|||||||
|
|
||||||
if (command.startsWith("bmsaddr ")) {
|
if (command.startsWith("bmsaddr ")) {
|
||||||
appConfig.bms.address = command.substring(8);
|
appConfig.bms.address = command.substring(8);
|
||||||
|
appConfig.bms.enabled = appConfig.bms.address.length() > 0;
|
||||||
Serial.println("OK bms address updated");
|
Serial.println("OK bms address updated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -398,7 +369,6 @@ void handleDebugSerial() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BleScanResult* result = getBleScanResult(selected - 1);
|
const BleScanResult* result = getBleScanResult(selected - 1);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
Serial.println("Invalid BMS selection. Run: scan ble");
|
Serial.println("Invalid BMS selection. Run: scan ble");
|
||||||
return;
|
return;
|
||||||
@@ -407,12 +377,7 @@ void handleDebugSerial() {
|
|||||||
appConfig.bms.enabled = true;
|
appConfig.bms.enabled = true;
|
||||||
appConfig.bms.address = result->address;
|
appConfig.bms.address = result->address;
|
||||||
appConfig.bms.addressType = "public";
|
appConfig.bms.addressType = "public";
|
||||||
|
appConfig.bms.name = result->name.length() > 0 ? result->name : "BMS";
|
||||||
if (result->name.length() > 0) {
|
|
||||||
appConfig.bms.name = result->name;
|
|
||||||
} else {
|
|
||||||
appConfig.bms.name = "BMS";
|
|
||||||
}
|
|
||||||
|
|
||||||
saveConfig();
|
saveConfig();
|
||||||
|
|
||||||
@@ -428,42 +393,16 @@ void handleDebugSerial() {
|
|||||||
if (command.length() > 0) {
|
if (command.length() > 0) {
|
||||||
Serial.print("Unknown command: ");
|
Serial.print("Unknown command: ");
|
||||||
Serial.println(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 <name>, tempname N <name>, bmsname <name>, bmsaddr <addr>, enter setup, scan ble, select bms N, exit setup");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void sendConfigJson() {
|
void sendConfigJson() {
|
||||||
DynamicJsonDocument doc(4096);
|
DynamicJsonDocument doc(4096);
|
||||||
|
addConfigStatus(doc);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
String output;
|
String output;
|
||||||
serializeJson(doc, output);
|
serializeJson(doc["config"], output);
|
||||||
server.send(200, "application/json", output);
|
server.send(200, "application/json", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,18 +413,14 @@ void sendOkConfig() {
|
|||||||
|
|
||||||
int findRelayConfigIndex(const String& id) {
|
int findRelayConfigIndex(const String& id) {
|
||||||
for (int i = 0; i < MAX_RELAYS; i++) {
|
for (int i = 0; i < MAX_RELAYS; i++) {
|
||||||
if (appConfig.relays[i].id == id) {
|
if (appConfig.relays[i].id == id) return i;
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int findTempSensorConfigIndex(const String& id) {
|
int findTempSensorConfigIndex(const String& id) {
|
||||||
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
||||||
if (appConfig.tempSensors[i].id == id) {
|
if (appConfig.tempSensors[i].id == id) return i;
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -497,7 +432,6 @@ void handleGetConfig() {
|
|||||||
void handleUpdateRelayConfig() {
|
void handleUpdateRelayConfig() {
|
||||||
DynamicJsonDocument doc(1024);
|
DynamicJsonDocument doc(1024);
|
||||||
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
||||||
return;
|
return;
|
||||||
@@ -505,19 +439,13 @@ void handleUpdateRelayConfig() {
|
|||||||
|
|
||||||
String id = doc["id"] | "";
|
String id = doc["id"] | "";
|
||||||
int index = findRelayConfigIndex(id);
|
int index = findRelayConfigIndex(id);
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc["name"].is<String>()) {
|
if (doc["name"].is<String>()) appConfig.relays[index].name = doc["name"].as<String>();
|
||||||
appConfig.relays[index].name = doc["name"].as<String>();
|
if (doc["enabled"].is<bool>()) appConfig.relays[index].enabled = doc["enabled"].as<bool>();
|
||||||
}
|
|
||||||
|
|
||||||
if (doc["enabled"].is<bool>()) {
|
|
||||||
appConfig.relays[index].enabled = doc["enabled"].as<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendOkConfig();
|
sendOkConfig();
|
||||||
}
|
}
|
||||||
@@ -525,27 +453,15 @@ void handleUpdateRelayConfig() {
|
|||||||
void handleUpdateBmsConfig() {
|
void handleUpdateBmsConfig() {
|
||||||
DynamicJsonDocument doc(1024);
|
DynamicJsonDocument doc(1024);
|
||||||
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc["enabled"].is<bool>()) {
|
if (doc["enabled"].is<bool>()) appConfig.bms.enabled = doc["enabled"].as<bool>();
|
||||||
appConfig.bms.enabled = doc["enabled"].as<bool>();
|
if (doc["name"].is<String>()) appConfig.bms.name = doc["name"].as<String>();
|
||||||
}
|
if (doc["address"].is<String>()) appConfig.bms.address = doc["address"].as<String>();
|
||||||
|
if (doc["address_type"].is<String>()) appConfig.bms.addressType = doc["address_type"].as<String>();
|
||||||
if (doc["name"].is<String>()) {
|
|
||||||
appConfig.bms.name = doc["name"].as<String>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc["address"].is<String>()) {
|
|
||||||
appConfig.bms.address = doc["address"].as<String>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc["address_type"].is<String>()) {
|
|
||||||
appConfig.bms.addressType = doc["address_type"].as<String>();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendOkConfig();
|
sendOkConfig();
|
||||||
}
|
}
|
||||||
@@ -553,7 +469,6 @@ void handleUpdateBmsConfig() {
|
|||||||
void handleUpdateTempSensorConfig() {
|
void handleUpdateTempSensorConfig() {
|
||||||
DynamicJsonDocument doc(1024);
|
DynamicJsonDocument doc(1024);
|
||||||
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
DeserializationError error = deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_json\"}");
|
||||||
return;
|
return;
|
||||||
@@ -561,23 +476,14 @@ void handleUpdateTempSensorConfig() {
|
|||||||
|
|
||||||
String id = doc["id"] | "";
|
String id = doc["id"] | "";
|
||||||
int index = findTempSensorConfigIndex(id);
|
int index = findTempSensorConfigIndex(id);
|
||||||
|
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_temp_sensor\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc["name"].is<String>()) {
|
if (doc["name"].is<String>()) appConfig.tempSensors[index].name = doc["name"].as<String>();
|
||||||
appConfig.tempSensors[index].name = doc["name"].as<String>();
|
if (doc["address"].is<String>()) appConfig.tempSensors[index].address = doc["address"].as<String>();
|
||||||
}
|
if (doc["enabled"].is<bool>()) appConfig.tempSensors[index].enabled = doc["enabled"].as<bool>();
|
||||||
|
|
||||||
if (doc["address"].is<String>()) {
|
|
||||||
appConfig.tempSensors[index].address = doc["address"].as<String>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc["enabled"].is<bool>()) {
|
|
||||||
appConfig.tempSensors[index].enabled = doc["enabled"].as<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendOkConfig();
|
sendOkConfig();
|
||||||
}
|
}
|
||||||
@@ -587,17 +493,10 @@ void handleFactoryResetConfig() {
|
|||||||
sendConfigJson();
|
sendConfigJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void handleGenericRelayRoute() {
|
void handleGenericRelayRoute() {
|
||||||
String uri = server.uri();
|
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/";
|
String prefix = "/relay/";
|
||||||
|
|
||||||
if (!uri.startsWith(prefix)) {
|
if (!uri.startsWith(prefix)) {
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_route\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_route\"}");
|
||||||
return;
|
return;
|
||||||
@@ -616,26 +515,18 @@ void handleGenericRelayRoute() {
|
|||||||
|
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
if (action == "on") {
|
if (action == "on") enabled = true;
|
||||||
enabled = true;
|
else if (action == "off") enabled = false;
|
||||||
} else if (action == "off") {
|
else {
|
||||||
enabled = false;
|
|
||||||
} else {
|
|
||||||
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_action\"}");
|
server.send(400, "application/json", "{\"ok\":false,\"error\":\"invalid_relay_action\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relayId == "relay_1") {
|
if (!setRelayById(relayId, enabled)) {
|
||||||
relays.relay1 = enabled;
|
|
||||||
} else if (relayId == "relay_2") {
|
|
||||||
relays.relay2 = enabled;
|
|
||||||
} else {
|
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRelayOutputs();
|
|
||||||
|
|
||||||
DynamicJsonDocument doc(512);
|
DynamicJsonDocument doc(512);
|
||||||
doc["ok"] = true;
|
doc["ok"] = true;
|
||||||
doc["id"] = relayId;
|
doc["id"] = relayId;
|
||||||
@@ -647,7 +538,7 @@ void handleGenericRelayRoute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleStatus() {
|
void handleStatus() {
|
||||||
DynamicJsonDocument doc(2048);
|
DynamicJsonDocument doc(4096);
|
||||||
buildStatusDocument(doc);
|
buildStatusDocument(doc);
|
||||||
|
|
||||||
String output;
|
String output;
|
||||||
@@ -656,40 +547,6 @@ void handleStatus() {
|
|||||||
server.send(200, "application/json", output);
|
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() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
@@ -702,9 +559,6 @@ void setup() {
|
|||||||
|
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.println("==================================");
|
Serial.println("==================================");
|
||||||
|
|
||||||
loadConfig();
|
|
||||||
printConfig();
|
|
||||||
Serial.println("Overland Controller Booting");
|
Serial.println("Overland Controller Booting");
|
||||||
Serial.println("==================================");
|
Serial.println("==================================");
|
||||||
|
|
||||||
@@ -718,7 +572,6 @@ void setup() {
|
|||||||
initBms();
|
initBms();
|
||||||
|
|
||||||
WiFi.mode(WIFI_AP);
|
WiFi.mode(WIFI_AP);
|
||||||
|
|
||||||
bool apResult = WiFi.softAP("OverlandController");
|
bool apResult = WiFi.softAP("OverlandController");
|
||||||
|
|
||||||
if (apResult) {
|
if (apResult) {
|
||||||
@@ -733,20 +586,12 @@ void setup() {
|
|||||||
server.on("/relay/relay_2/on", HTTP_GET, handleGenericRelayRoute);
|
server.on("/relay/relay_2/on", HTTP_GET, handleGenericRelayRoute);
|
||||||
server.on("/relay/relay_2/off", HTTP_GET, handleGenericRelayRoute);
|
server.on("/relay/relay_2/off", HTTP_GET, handleGenericRelayRoute);
|
||||||
|
|
||||||
|
|
||||||
server.on("/config", HTTP_GET, handleGetConfig);
|
server.on("/config", HTTP_GET, handleGetConfig);
|
||||||
server.on("/config/relay", HTTP_POST, handleUpdateRelayConfig);
|
server.on("/config/relay", HTTP_POST, handleUpdateRelayConfig);
|
||||||
server.on("/config/bms", HTTP_POST, handleUpdateBmsConfig);
|
server.on("/config/bms", HTTP_POST, handleUpdateBmsConfig);
|
||||||
server.on("/config/temp", HTTP_POST, handleUpdateTempSensorConfig);
|
server.on("/config/temp", HTTP_POST, handleUpdateTempSensorConfig);
|
||||||
server.on("/config/factory-reset", HTTP_POST, handleFactoryResetConfig);
|
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();
|
server.begin();
|
||||||
|
|
||||||
Serial.println("Web Server Started");
|
Serial.println("Web Server Started");
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
RelayState relays;
|
RelayState relays;
|
||||||
|
|
||||||
void initRelays() {
|
void initRelays() {
|
||||||
pinMode(RELAY_STARLINK_PIN, OUTPUT);
|
pinMode(RELAY_1_PIN, OUTPUT);
|
||||||
pinMode(RELAY_FRIDGE_PIN, OUTPUT);
|
pinMode(RELAY_2_PIN, OUTPUT);
|
||||||
|
|
||||||
updateRelayOutputs();
|
updateRelayOutputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRelayOutputs() {
|
void updateRelayOutputs() {
|
||||||
digitalWrite(RELAY_STARLINK_PIN, relays.relay1);
|
digitalWrite(RELAY_1_PIN, relays.relay1);
|
||||||
digitalWrite(RELAY_FRIDGE_PIN, relays.relay2);
|
digitalWrite(RELAY_2_PIN, relays.relay2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ echo "1. Status:"
|
|||||||
curl -s "$BASE_URL/status" | python3 -m json.tool
|
curl -s "$BASE_URL/status" | python3 -m json.tool
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "2. Starlink ON:"
|
echo "2. Relay 1 ON:"
|
||||||
curl -s "$BASE_URL/relay/starlink/on" | python3 -m json.tool
|
curl -s "$BASE_URL/relay/relay_1/on" | python3 -m json.tool
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "3. Starlink OFF:"
|
echo "3. Relay 1 OFF:"
|
||||||
curl -s "$BASE_URL/relay/starlink/off" | python3 -m json.tool
|
curl -s "$BASE_URL/relay/relay_1/off" | python3 -m json.tool
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "4. Fridge ON:"
|
echo "4. Relay 2 ON:"
|
||||||
curl -s "$BASE_URL/relay/fridge/on" | python3 -m json.tool
|
curl -s "$BASE_URL/relay/relay_2/on" | python3 -m json.tool
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "5. Fridge OFF:"
|
echo "5. Relay 2 OFF:"
|
||||||
curl -s "$BASE_URL/relay/fridge/off" | python3 -m json.tool
|
curl -s "$BASE_URL/relay/relay_2/off" | python3 -m json.tool
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "ESP32 HTTP bring-up test complete."
|
echo "ESP32 HTTP bring-up test complete."
|
||||||
|
|||||||
Reference in New Issue
Block a user