Add versioned HTTP API routes
This commit is contained in:
+58
-50
@@ -8,6 +8,12 @@ Default AP address:
|
|||||||
http://192.168.4.1
|
http://192.168.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Current API base path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/api/v1
|
||||||
|
```
|
||||||
|
|
||||||
All documented endpoints are local-first and must continue to work without internet access. Consumers should ignore unknown response fields for forward compatibility.
|
All documented endpoints are local-first and must continue to work without internet access. Consumers should ignore unknown response fields for forward compatibility.
|
||||||
|
|
||||||
Current firmware version:
|
Current firmware version:
|
||||||
@@ -20,34 +26,36 @@ Current firmware version:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
GET /
|
GET /
|
||||||
GET /status
|
GET /api/v1/status
|
||||||
GET /config
|
GET /api/v1/config
|
||||||
|
|
||||||
POST /relay/set
|
POST /api/v1/relay/set
|
||||||
|
|
||||||
POST /config/device
|
POST /api/v1/config/device
|
||||||
POST /config/relay
|
POST /api/v1/config/relay
|
||||||
POST /config/temp
|
POST /api/v1/config/temp
|
||||||
POST /config/bms
|
POST /api/v1/config/bms
|
||||||
POST /config/save
|
POST /api/v1/config/save
|
||||||
POST /config/factory-reset
|
POST /api/v1/config/factory-reset
|
||||||
|
|
||||||
GET /config/wifi
|
GET /api/v1/config/wifi
|
||||||
POST /config/wifi
|
POST /api/v1/config/wifi
|
||||||
POST /wifi/connect
|
POST /api/v1/wifi/connect
|
||||||
POST /wifi/clear
|
POST /api/v1/wifi/clear
|
||||||
|
|
||||||
POST /temps/scan
|
POST /api/v1/temps/scan
|
||||||
POST /temps/assign
|
POST /api/v1/temps/assign
|
||||||
POST /temps/clear
|
POST /api/v1/temps/clear
|
||||||
|
|
||||||
POST /bms/setup/enter
|
POST /api/v1/bms/setup/enter
|
||||||
POST /bms/setup/exit
|
POST /api/v1/bms/setup/exit
|
||||||
POST /bms/scan
|
POST /api/v1/bms/scan
|
||||||
POST /bms/select
|
POST /api/v1/bms/select
|
||||||
```
|
```
|
||||||
|
|
||||||
Legacy relay routes remain available:
|
Pre-versioned root routes remain registered as compatibility aliases for existing local clients. New clients should use `/api/v1`.
|
||||||
|
|
||||||
|
Legacy GET relay routes remain available only as root compatibility aliases:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
GET /relay/relay_1/on
|
GET /relay/relay_1/on
|
||||||
@@ -61,15 +69,15 @@ GET /relay/relay_2/off
|
|||||||
The Waveshare ESP32-S3 dashboard MVP should use only:
|
The Waveshare ESP32-S3 dashboard MVP should use only:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
GET /status
|
GET /api/v1/status
|
||||||
POST /relay/set
|
POST /api/v1/relay/set
|
||||||
```
|
```
|
||||||
|
|
||||||
The dashboard is a client only. It may cache last-known values for display, but the Cargo ESP32 remains the source of truth for relay state, BMS state, alarms, and configuration.
|
The dashboard is a client only. It may cache last-known values for display, but the Cargo ESP32 remains the source of truth for relay state, BMS state, alarms, and configuration.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
### GET /status
|
### GET /api/v1/status
|
||||||
|
|
||||||
Returns complete controller status.
|
Returns complete controller status.
|
||||||
|
|
||||||
@@ -144,7 +152,7 @@ Fields:
|
|||||||
| `online` | Current sensor detection state |
|
| `online` | Current sensor detection state |
|
||||||
| `temperature_f` | Current Fahrenheit value, or `null` when offline |
|
| `temperature_f` | Current Fahrenheit value, or `null` when offline |
|
||||||
|
|
||||||
Config supports `temp_1` through `temp_8`. Current `/status` runtime output is limited to the configured count capped at four sensors.
|
Config supports `temp_1` through `temp_8`. Current `/api/v1/status` runtime output is limited to the configured count capped at four sensors.
|
||||||
|
|
||||||
### relays
|
### relays
|
||||||
|
|
||||||
@@ -228,11 +236,11 @@ The AP remains enabled even when STA WiFi is configured or connected.
|
|||||||
|
|
||||||
### config
|
### config
|
||||||
|
|
||||||
`/status.config` embeds the same core configuration model used by `GET /config`.
|
`/api/v1/status.config` embeds the same core configuration model used by `GET /api/v1/config`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### GET /config
|
### GET /api/v1/config
|
||||||
|
|
||||||
Returns saved controller configuration.
|
Returns saved controller configuration.
|
||||||
|
|
||||||
@@ -264,9 +272,9 @@ Returns saved controller configuration.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: current `GET /config` omits the `weather` field, while `/status.config.temperature_sensors[]` includes it.
|
Note: current `GET /api/v1/config` omits the `weather` field, while `/api/v1/status.config.temperature_sensors[]` includes it.
|
||||||
|
|
||||||
### POST /config/device
|
### POST /api/v1/config/device
|
||||||
|
|
||||||
Updates the controller display name.
|
Updates the controller display name.
|
||||||
|
|
||||||
@@ -278,7 +286,7 @@ Updates the controller display name.
|
|||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
### POST /config/relay
|
### POST /api/v1/config/relay
|
||||||
|
|
||||||
Updates relay configuration.
|
Updates relay configuration.
|
||||||
|
|
||||||
@@ -300,7 +308,7 @@ Fields:
|
|||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
### POST /config/temp
|
### POST /api/v1/config/temp
|
||||||
|
|
||||||
Updates temperature sensor configuration.
|
Updates temperature sensor configuration.
|
||||||
|
|
||||||
@@ -326,7 +334,7 @@ Fields:
|
|||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
### POST /config/bms
|
### POST /api/v1/config/bms
|
||||||
|
|
||||||
Updates BMS configuration.
|
Updates BMS configuration.
|
||||||
|
|
||||||
@@ -348,13 +356,13 @@ random
|
|||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
### POST /config/save
|
### POST /api/v1/config/save
|
||||||
|
|
||||||
Persists the current active configuration.
|
Persists the current active configuration.
|
||||||
|
|
||||||
Returns the current config.
|
Returns the current config.
|
||||||
|
|
||||||
### POST /config/factory-reset
|
### POST /api/v1/config/factory-reset
|
||||||
|
|
||||||
Clears saved configuration and restores firmware defaults.
|
Clears saved configuration and restores firmware defaults.
|
||||||
|
|
||||||
@@ -362,7 +370,7 @@ Returns the reset config.
|
|||||||
|
|
||||||
## Relay Control
|
## Relay Control
|
||||||
|
|
||||||
### POST /relay/set
|
### POST /api/v1/relay/set
|
||||||
|
|
||||||
Preferred relay command endpoint.
|
Preferred relay command endpoint.
|
||||||
|
|
||||||
@@ -407,7 +415,7 @@ Response:
|
|||||||
|
|
||||||
## WiFi
|
## WiFi
|
||||||
|
|
||||||
### GET /config/wifi
|
### GET /api/v1/config/wifi
|
||||||
|
|
||||||
Returns AP/STA WiFi configuration status. Passwords are not returned.
|
Returns AP/STA WiFi configuration status. Passwords are not returned.
|
||||||
|
|
||||||
@@ -435,7 +443,7 @@ Returns AP/STA WiFi configuration status. Passwords are not returned.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST /config/wifi
|
### POST /api/v1/config/wifi
|
||||||
|
|
||||||
Preferred multi-network request:
|
Preferred multi-network request:
|
||||||
|
|
||||||
@@ -473,13 +481,13 @@ Runtime behavior:
|
|||||||
- Lower priority numbers are tried first.
|
- Lower priority numbers are tried first.
|
||||||
- If STA disconnects, saved networks are retried by priority.
|
- If STA disconnects, saved networks are retried by priority.
|
||||||
|
|
||||||
### POST /wifi/connect
|
### POST /api/v1/wifi/connect
|
||||||
|
|
||||||
Attempts STA connection using saved networks by priority.
|
Attempts STA connection using saved networks by priority.
|
||||||
|
|
||||||
Returns WiFi config status.
|
Returns WiFi config status.
|
||||||
|
|
||||||
### POST /wifi/clear
|
### POST /api/v1/wifi/clear
|
||||||
|
|
||||||
Clears saved STA WiFi networks.
|
Clears saved STA WiFi networks.
|
||||||
|
|
||||||
@@ -487,7 +495,7 @@ Returns WiFi config status.
|
|||||||
|
|
||||||
## Temperature Probe Setup
|
## Temperature Probe Setup
|
||||||
|
|
||||||
### POST /temps/scan
|
### POST /api/v1/temps/scan
|
||||||
|
|
||||||
Scans the DS18B20 bus and returns unassigned probe addresses.
|
Scans the DS18B20 bus and returns unassigned probe addresses.
|
||||||
|
|
||||||
@@ -506,7 +514,7 @@ Scans the DS18B20 bus and returns unassigned probe addresses.
|
|||||||
|
|
||||||
Already-assigned probe addresses are hidden from scan results until cleared.
|
Already-assigned probe addresses are hidden from scan results until cleared.
|
||||||
|
|
||||||
### POST /temps/assign
|
### POST /api/v1/temps/assign
|
||||||
|
|
||||||
Assigns a scanned probe to a logical temperature slot.
|
Assigns a scanned probe to a logical temperature slot.
|
||||||
|
|
||||||
@@ -530,7 +538,7 @@ By slot number:
|
|||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
### POST /temps/clear
|
### POST /api/v1/temps/clear
|
||||||
|
|
||||||
Clears one or all temperature assignments.
|
Clears one or all temperature assignments.
|
||||||
|
|
||||||
@@ -560,7 +568,7 @@ Returns the updated config.
|
|||||||
|
|
||||||
These endpoints are for BMS discovery and selection. Do not change JBD/Xiaoxiang BLE behavior without explicit approval.
|
These endpoints are for BMS discovery and selection. Do not change JBD/Xiaoxiang BLE behavior without explicit approval.
|
||||||
|
|
||||||
### POST /bms/setup/enter
|
### POST /api/v1/bms/setup/enter
|
||||||
|
|
||||||
Enters BMS setup mode.
|
Enters BMS setup mode.
|
||||||
|
|
||||||
@@ -574,7 +582,7 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST /bms/setup/exit
|
### POST /api/v1/bms/setup/exit
|
||||||
|
|
||||||
Exits BMS setup mode.
|
Exits BMS setup mode.
|
||||||
|
|
||||||
@@ -588,7 +596,7 @@ Response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### POST /bms/scan
|
### POST /api/v1/bms/scan
|
||||||
|
|
||||||
Scans for BLE devices.
|
Scans for BLE devices.
|
||||||
|
|
||||||
@@ -610,7 +618,7 @@ Response:
|
|||||||
|
|
||||||
Some BMS devices advertise intermittently. Repeated scans may be needed.
|
Some BMS devices advertise intermittently. Repeated scans may be needed.
|
||||||
|
|
||||||
### POST /bms/select
|
### POST /api/v1/bms/select
|
||||||
|
|
||||||
Selects a BMS from the most recent scan result.
|
Selects a BMS from the most recent scan result.
|
||||||
|
|
||||||
@@ -676,9 +684,9 @@ Some serial/UART errors use `message` instead of `error`; HTTP clients should re
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Current stable route prefix is the root API path, for example `GET /status`.
|
Current stable API prefix is `/api/v1`, for example `GET /api/v1/status`.
|
||||||
|
|
||||||
Future versioning may move to `/api/v1`. Do not introduce versioned routes until consumers and docs are updated together.
|
Pre-versioned root routes remain registered as local compatibility aliases. Do not add new root-only HTTP API routes.
|
||||||
|
|
||||||
New fields may be added to existing JSON objects. Consumers should ignore unknown fields.
|
New fields may be added to existing JSON objects. Consumers should ignore unknown fields.
|
||||||
|
|
||||||
@@ -687,8 +695,8 @@ New fields may be added to existing JSON objects. Consumers should ignore unknow
|
|||||||
These routes are not part of the current registered HTTP API:
|
These routes are not part of the current registered HTTP API:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
POST /bms/reconnect
|
POST /api/v1/bms/reconnect
|
||||||
POST /system/restart
|
POST /api/v1/system/restart
|
||||||
```
|
```
|
||||||
|
|
||||||
If added later, update this document and add contract tests.
|
If added later, update this document and add contract tests.
|
||||||
|
|||||||
@@ -93,11 +93,11 @@ HTTP is the primary integration contract for:
|
|||||||
|
|
||||||
Preferred direction:
|
Preferred direction:
|
||||||
|
|
||||||
GET /status
|
GET /api/v1/status
|
||||||
GET /config
|
GET /api/v1/config
|
||||||
POST /relay/set
|
POST /api/v1/relay/set
|
||||||
|
|
||||||
Future versioning may move to /api/v1.
|
Pre-versioned root routes remain registered as compatibility aliases for existing local clients.
|
||||||
|
|
||||||
## Generic IDs
|
## Generic IDs
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
- WiFi priority and runtime reconnect behavior.
|
- WiFi priority and runtime reconnect behavior.
|
||||||
- HTTP/UART/USB serial command parity documentation.
|
- HTTP/UART/USB serial command parity documentation.
|
||||||
- DS18B20 configurable temperature probe support.
|
- DS18B20 configurable temperature probe support.
|
||||||
- Preferred relay command endpoint: `POST /relay/set`.
|
- Preferred relay command endpoint: `POST /api/v1/relay/set`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Serial command:
|
|||||||
|
|
||||||
HTTP endpoint:
|
HTTP endpoint:
|
||||||
|
|
||||||
POST /config/device
|
POST /api/v1/config/device
|
||||||
|
|
||||||
## Relay Configuration
|
## Relay Configuration
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ Serial commands:
|
|||||||
|
|
||||||
HTTP endpoint:
|
HTTP endpoint:
|
||||||
|
|
||||||
POST /config/relay
|
POST /api/v1/config/relay
|
||||||
|
|
||||||
## Temperature Sensor Configuration
|
## Temperature Sensor Configuration
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ Serial commands:
|
|||||||
|
|
||||||
HTTP endpoint:
|
HTTP endpoint:
|
||||||
|
|
||||||
POST /config/temp
|
POST /api/v1/config/temp
|
||||||
|
|
||||||
## BMS Configuration
|
## BMS Configuration
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ Serial commands:
|
|||||||
|
|
||||||
HTTP endpoint:
|
HTTP endpoint:
|
||||||
|
|
||||||
POST /config/bms
|
POST /api/v1/config/bms
|
||||||
|
|
||||||
## BMS Setup Mode
|
## BMS Setup Mode
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ Serial command:
|
|||||||
|
|
||||||
HTTP endpoint:
|
HTTP endpoint:
|
||||||
|
|
||||||
POST /config/factory-reset
|
POST /api/v1/config/factory-reset
|
||||||
|
|
||||||
After factory reset:
|
After factory reset:
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -12,14 +12,14 @@ Control surfaces:
|
|||||||
|
|
||||||
| Feature | USB Serial | UART JSON | HTTP API | Notes |
|
| Feature | USB Serial | UART JSON | HTTP API | Notes |
|
||||||
|---|---:|---:|---:|---|
|
|---|---:|---:|---:|---|
|
||||||
| Status | Yes | Yes | Yes | `/status` equals `status_request` |
|
| Status | Yes | Yes | Yes | `/api/v1/status` equals `status_request` |
|
||||||
| Config view | Yes | Yes | Yes | `/config` equals `config_request` |
|
| Config view | Yes | Yes | Yes | `/api/v1/config` equals `config_request` |
|
||||||
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /relay/set` |
|
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /api/v1/relay/set` |
|
||||||
| Device name config | Yes | Yes | Yes | `devicename`, `config_device`, `/config/device` |
|
| Device name config | Yes | Yes | Yes | `devicename`, `config_device`, `/api/v1/config/device` |
|
||||||
| Relay config | Yes | Yes | Yes | `relayname`, `config_relay`, `/config/relay` |
|
| Relay config | Yes | Yes | Yes | `relayname`, `config_relay`, `/api/v1/config/relay` |
|
||||||
| Temperature config | Yes | Yes | Yes | `tempname`, `config_temp`, `/config/temp` |
|
| Temperature config | Yes | Yes | Yes | `tempname`, `config_temp`, `/api/v1/config/temp` |
|
||||||
| BMS config | Yes | Yes | Yes | `bmsname`/`bmsaddr`, `config_bms`, `/config/bms` |
|
| BMS config | Yes | Yes | Yes | `bmsname`/`bmsaddr`, `config_bms`, `/api/v1/config/bms` |
|
||||||
| Save config | Yes | Yes | Yes | `save`, `save_config`, `/config/save` |
|
| Save config | Yes | Yes | Yes | `save`, `save_config`, `/api/v1/config/save` |
|
||||||
| Factory reset | Yes | Yes | Yes | AP remains recovery path |
|
| Factory reset | Yes | Yes | Yes | AP remains recovery path |
|
||||||
| BMS setup enter | Yes | Yes | Yes | Setup pauses normal BMS reconnect behavior |
|
| BMS setup enter | Yes | Yes | Yes | Setup pauses normal BMS reconnect behavior |
|
||||||
| BMS setup exit | Yes | Yes | Yes | Returns to normal mode |
|
| BMS setup exit | Yes | Yes | Yes | Returns to normal mode |
|
||||||
@@ -58,9 +58,9 @@ Legacy UART relay fields may still be accepted:
|
|||||||
|
|
||||||
Preferred HTTP relay endpoint:
|
Preferred HTTP relay endpoint:
|
||||||
|
|
||||||
POST /relay/set
|
POST /api/v1/relay/set
|
||||||
|
|
||||||
Legacy HTTP relay routes may remain for compatibility:
|
Legacy root HTTP relay routes remain for compatibility:
|
||||||
|
|
||||||
GET /relay/relay_1/on
|
GET /relay/relay_1/on
|
||||||
GET /relay/relay_1/off
|
GET /relay/relay_1/off
|
||||||
|
|||||||
@@ -47,15 +47,15 @@ All grounds remain common.
|
|||||||
## API endpoints consumed
|
## API endpoints consumed
|
||||||
|
|
||||||
Minimum dashboard MVP:
|
Minimum dashboard MVP:
|
||||||
- GET /status
|
- GET /api/v1/status
|
||||||
- POST /relay/set
|
- POST /api/v1/relay/set
|
||||||
|
|
||||||
Potential later use:
|
Potential later use:
|
||||||
- GET /config
|
- GET /api/v1/config
|
||||||
- POST /config/save
|
- POST /api/v1/config/save
|
||||||
- POST /temps/scan
|
- POST /api/v1/temps/scan
|
||||||
- POST /temps/assign
|
- POST /api/v1/temps/assign
|
||||||
- POST /temps/clear
|
- POST /api/v1/temps/clear
|
||||||
|
|
||||||
## MVP Display Goals
|
## MVP Display Goals
|
||||||
|
|
||||||
@@ -120,10 +120,10 @@ Then interactive:
|
|||||||
|
|
||||||
1. Dashboard boots.
|
1. Dashboard boots.
|
||||||
2. Connects to cargo ESP32 AP.
|
2. Connects to cargo ESP32 AP.
|
||||||
3. Polls GET /status.
|
3. Polls GET /api/v1/status.
|
||||||
4. Shows disconnected state until valid JSON is received.
|
4. Shows disconnected state until valid JSON is received.
|
||||||
5. Updates dashboard on a fixed interval.
|
5. Updates dashboard on a fixed interval.
|
||||||
6. Relay buttons call POST /relay/set.
|
6. Relay buttons call POST /api/v1/relay/set.
|
||||||
|
|
||||||
## Polling Guidance
|
## Polling Guidance
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
|
|||||||
</div>
|
</div>
|
||||||
<div class="btnrow">
|
<div class="btnrow">
|
||||||
<button onclick="setBmsEnabled(true)">Enable BMS</button>
|
<button onclick="setBmsEnabled(true)">Enable BMS</button>
|
||||||
<button onclick="reconnectBms()">Reconnect BMS</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -218,7 +217,6 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
|
|||||||
<div class="label">System Actions</div>
|
<div class="label">System Actions</div>
|
||||||
<div class="sub">Use carefully. Factory reset clears install-specific configuration.</div>
|
<div class="sub">Use carefully. Factory reset clears install-specific configuration.</div>
|
||||||
<div class="btnrow">
|
<div class="btnrow">
|
||||||
<button onclick="restartController()">Restart</button>
|
|
||||||
<button class="off" onclick="factoryReset()">Factory Reset</button>
|
<button class="off" onclick="factoryReset()">Factory Reset</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -240,6 +238,8 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const API_BASE="/api/v1";
|
||||||
|
function api(path){return API_BASE+path;}
|
||||||
const $=id=>document.getElementById(id);
|
const $=id=>document.getElementById(id);
|
||||||
function fmt(n,d=1){return typeof n==="number"?n.toFixed(d):"--"}
|
function fmt(n,d=1){return typeof n==="number"?n.toFixed(d):"--"}
|
||||||
function temp(v){return typeof v==="number"?fmt(v,1)+"°F":"Offline"}
|
function temp(v){return typeof v==="number"?fmt(v,1)+"°F":"Offline"}
|
||||||
@@ -323,12 +323,12 @@ function showTab(name){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function setBmsEnabled(enabled){
|
async function setBmsEnabled(enabled){
|
||||||
await fetch("/config/bms",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled})});
|
await fetch(api("/config/bms"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled})});
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
async function relay(id,on){
|
async function relay(id,on){
|
||||||
await fetch("/relay/set",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id,state:on})});
|
await fetch(api("/relay/set"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id,state:on})});
|
||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
function render(data){
|
function render(data){
|
||||||
@@ -411,13 +411,13 @@ async function saveDeviceConfig(){
|
|||||||
const name=$("deviceNameInput")?.value.trim();
|
const name=$("deviceNameInput")?.value.trim();
|
||||||
if(!name){ alert("Device name is required"); return; }
|
if(!name){ alert("Device name is required"); return; }
|
||||||
|
|
||||||
await fetch("/config/device",{
|
await fetch(api("/config/device"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({device_name:name})
|
body:JSON.stringify({device_name:name})
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
alert("Device name saved");
|
alert("Device name saved");
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ async function saveBmsFullConfig(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch("/config/bms",{
|
await fetch(api("/config/bms"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({
|
body:JSON.stringify({
|
||||||
@@ -443,22 +443,16 @@ async function saveBmsFullConfig(){
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await reconnectBms(false);
|
|
||||||
await load();
|
await load();
|
||||||
alert("BMS config saved");
|
alert("BMS config saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reconnectBms(showAlert=true){
|
|
||||||
await fetch("/bms/reconnect",{method:"POST"});
|
|
||||||
if(showAlert) alert("BMS reconnect requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function scanTemps(){
|
async function scanTemps(){
|
||||||
const box=$("tempScanResults");
|
const box=$("tempScanResults");
|
||||||
if(box) box.innerHTML=`<div class="item"><span>Scanning...</span><strong class="muted">wait</strong></div>`;
|
if(box) box.innerHTML=`<div class="item"><span>Scanning...</span><strong class="muted">wait</strong></div>`;
|
||||||
|
|
||||||
const r=await fetch("/temps/scan",{method:"POST"});
|
const r=await fetch(api("/temps/scan"),{method:"POST"});
|
||||||
const d=await r.json();
|
const d=await r.json();
|
||||||
const devices=d.devices||[];
|
const devices=d.devices||[];
|
||||||
|
|
||||||
@@ -487,13 +481,13 @@ async function scanTemps(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function assignTemp(id,index){
|
async function assignTemp(id,index){
|
||||||
await fetch("/temps/assign",{
|
await fetch(api("/temps/assign"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({id:id,index:index})
|
body:JSON.stringify({id:id,index:index})
|
||||||
});
|
});
|
||||||
|
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
alert("Temp probe assigned to "+id);
|
alert("Temp probe assigned to "+id);
|
||||||
}
|
}
|
||||||
@@ -501,7 +495,7 @@ async function assignTemp(id,index){
|
|||||||
async function clearTempAssignment(id){
|
async function clearTempAssignment(id){
|
||||||
if(!confirm("Clear probe assignment for "+id+"?")) return;
|
if(!confirm("Clear probe assignment for "+id+"?")) return;
|
||||||
|
|
||||||
const r=await fetch("/temps/clear",{
|
const r=await fetch(api("/temps/clear"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({id:id})
|
body:JSON.stringify({id:id})
|
||||||
@@ -517,17 +511,11 @@ async function clearTempAssignment(id){
|
|||||||
alert("Temp probe assignment cleared for "+id);
|
alert("Temp probe assignment cleared for "+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartController(){
|
|
||||||
if(!confirm("Restart the controller now?")) return;
|
|
||||||
await fetch("/system/restart",{method:"POST"});
|
|
||||||
alert("Restart requested");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function factoryReset(){
|
async function factoryReset(){
|
||||||
if(!confirm("Factory reset all controller configuration?")) return;
|
if(!confirm("Factory reset all controller configuration?")) return;
|
||||||
if(!confirm("This clears WiFi/BMS/relay/temp config. Continue?")) return;
|
if(!confirm("This clears WiFi/BMS/relay/temp config. Continue?")) return;
|
||||||
|
|
||||||
await fetch("/config/factory-reset",{method:"POST"});
|
await fetch(api("/config/factory-reset"),{method:"POST"});
|
||||||
alert("Factory reset requested. Reconnect to the controller AP if needed.");
|
alert("Factory reset requested. Reconnect to the controller AP if needed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,7 +562,7 @@ function renderConfigControls(data){
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveRelayConfig(){
|
async function saveRelayConfig(){
|
||||||
const r=await fetch("/status",{cache:"no-store"});
|
const r=await fetch(api("/status"),{cache:"no-store"});
|
||||||
const data=await r.json();
|
const data=await r.json();
|
||||||
const relays=data.config?.relays||[];
|
const relays=data.config?.relays||[];
|
||||||
|
|
||||||
@@ -582,7 +570,7 @@ async function saveRelayConfig(){
|
|||||||
const name=$("relayName"+i)?.value.trim();
|
const name=$("relayName"+i)?.value.trim();
|
||||||
if(!name) continue;
|
if(!name) continue;
|
||||||
|
|
||||||
await fetch("/config/relay",{
|
await fetch(api("/config/relay"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({
|
body:JSON.stringify({
|
||||||
@@ -593,13 +581,13 @@ async function saveRelayConfig(){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
alert("Relay names saved");
|
alert("Relay names saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTempConfig(){
|
async function saveTempConfig(){
|
||||||
const r=await fetch("/status",{cache:"no-store"});
|
const r=await fetch(api("/status"),{cache:"no-store"});
|
||||||
const data=await r.json();
|
const data=await r.json();
|
||||||
const temps=data.config?.temperature_sensors||[];
|
const temps=data.config?.temperature_sensors||[];
|
||||||
|
|
||||||
@@ -613,7 +601,7 @@ async function saveTempConfig(){
|
|||||||
const enabled=$("tempEnabled"+i)?.checked || false;
|
const enabled=$("tempEnabled"+i)?.checked || false;
|
||||||
const weather=$("tempWeather"+i)?.checked || false;
|
const weather=$("tempWeather"+i)?.checked || false;
|
||||||
|
|
||||||
await fetch("/config/temp",{
|
await fetch(api("/config/temp"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
headers:{"Content-Type":"application/json"},
|
headers:{"Content-Type":"application/json"},
|
||||||
body:JSON.stringify({
|
body:JSON.stringify({
|
||||||
@@ -621,20 +609,19 @@ async function saveTempConfig(){
|
|||||||
name:name,
|
name:name,
|
||||||
address:temps[i].address||"",
|
address:temps[i].address||"",
|
||||||
enabled:enabled,
|
enabled:enabled,
|
||||||
weather:weather,
|
|
||||||
weather:weather
|
weather:weather
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch("/config/save",{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
alert("Temperature config saved");
|
alert("Temperature config saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadWifiConfig(){
|
async function loadWifiConfig(){
|
||||||
try{
|
try{
|
||||||
const r=await fetch("/config/wifi",{cache:"no-store"});
|
const r=await fetch(api("/config/wifi"),{cache:"no-store"});
|
||||||
const d=await r.json();
|
const d=await r.json();
|
||||||
const nets=d.wifi?.networks||[];
|
const nets=d.wifi?.networks||[];
|
||||||
for(let i=0;i<3;i++){
|
for(let i=0;i<3;i++){
|
||||||
@@ -652,17 +639,17 @@ async function saveWifi(){
|
|||||||
const priority=parseInt($("w"+i+"r").value||i,10);
|
const priority=parseInt($("w"+i+"r").value||i,10);
|
||||||
if(ssid) networks.push({ssid,password,priority});
|
if(ssid) networks.push({ssid,password,priority});
|
||||||
}
|
}
|
||||||
await fetch("/config/wifi",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({networks})});
|
await fetch(api("/config/wifi"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({networks})});
|
||||||
await loadWifiConfig();
|
await loadWifiConfig();
|
||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
async function connectWifi(){
|
async function connectWifi(){
|
||||||
await fetch("/wifi/connect",{method:"POST"});
|
await fetch(api("/wifi/connect"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
}
|
}
|
||||||
async function load(){
|
async function load(){
|
||||||
try{
|
try{
|
||||||
const r=await fetch("/status",{cache:"no-store"});
|
const r=await fetch(api("/status"),{cache:"no-store"});
|
||||||
render(await r.json());
|
render(await r.json());
|
||||||
}catch(e){
|
}catch(e){
|
||||||
$("conn").textContent="Offline";
|
$("conn").textContent="Offline";
|
||||||
@@ -678,6 +665,8 @@ setInterval(load,3000);
|
|||||||
)rawliteral";
|
)rawliteral";
|
||||||
|
|
||||||
WebServer server(80);
|
WebServer server(80);
|
||||||
|
|
||||||
|
#define API_V1(path) "/api/v1" path
|
||||||
HardwareSerial DashboardSerial(2);
|
HardwareSerial DashboardSerial(2);
|
||||||
|
|
||||||
String uartLineBuffer;
|
String uartLineBuffer;
|
||||||
@@ -2445,6 +2434,32 @@ void setup() {
|
|||||||
server.send_P(200, "text/html", INDEX_HTML);
|
server.send_P(200, "text/html", INDEX_HTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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/wifi"), HTTP_GET, handleGetWifiConfig);
|
||||||
|
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);
|
||||||
|
server.on(API_V1("/config/bms"), HTTP_POST, handleUpdateBmsConfig);
|
||||||
|
server.on(API_V1("/config/temp"), HTTP_POST, handleUpdateTempSensorConfig);
|
||||||
|
server.on(API_V1("/config/factory-reset"), HTTP_POST, handleFactoryResetConfig);
|
||||||
|
server.on(API_V1("/config/save"), HTTP_POST, handleSaveConfig);
|
||||||
|
|
||||||
|
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(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("/status", handleStatus);
|
||||||
server.on("/relay/set", HTTP_POST, handleSetRelayPost);
|
server.on("/relay/set", HTTP_POST, handleSetRelayPost);
|
||||||
server.on("/relay/relay_1/on", HTTP_GET, handleGenericRelayRoute);
|
server.on("/relay/relay_1/on", HTTP_GET, handleGenericRelayRoute);
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class HttpClient:
|
class HttpClient:
|
||||||
def __init__(self, requests_module, base_url="http://192.168.4.1"):
|
def __init__(self, requests_module, base_url="http://192.168.4.1"):
|
||||||
self.requests = requests_module
|
self.requests = requests_module
|
||||||
self.base_url = base_url.rstrip("/")
|
self.base_url = base_url.rstrip("/")
|
||||||
|
if not self.base_url.endswith("/api/v1"):
|
||||||
|
self.base_url = f"{self.base_url}/api/v1"
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
response = self.requests.get(f"{self.base_url}/status")
|
response = self.requests.get(f"{self.base_url}/status")
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def set_relay(self, relay, enabled):
|
def set_relay(self, relay, enabled):
|
||||||
state = "on" if enabled else "off"
|
response = self.requests.post(
|
||||||
response = self.requests.get(f"{self.base_url}/relay/{relay}/{state}")
|
f"{self.base_url}/relay/set",
|
||||||
|
json={"id": relay, "state": enabled},
|
||||||
|
)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ class AppState:
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.relays = {
|
self.relays = {
|
||||||
|
"relay_1": False,
|
||||||
|
"relay_2": False,
|
||||||
"starlink": False,
|
"starlink": False,
|
||||||
"fridge": False,
|
"fridge": False,
|
||||||
}
|
}
|
||||||
@@ -51,8 +53,8 @@ class AppState:
|
|||||||
self.network.update(message.get("network", {}))
|
self.network.update(message.get("network", {}))
|
||||||
|
|
||||||
def update_from_relay_response(self, message):
|
def update_from_relay_response(self, message):
|
||||||
relay = message.get("relay")
|
relay = message.get("relay", message.get("id"))
|
||||||
enabled = message.get("enabled")
|
enabled = message.get("enabled", message.get("state"))
|
||||||
|
|
||||||
if relay in self.relays and enabled is not None:
|
if relay in self.relays and enabled is not None:
|
||||||
self.relays[relay] = bool(enabled)
|
self.relays[relay] = bool(enabled)
|
||||||
|
|||||||
@@ -22,14 +22,41 @@ def assert_keys(payload, keys):
|
|||||||
|
|
||||||
|
|
||||||
def registered_routes():
|
def registered_routes():
|
||||||
pattern = re.compile(r'server\.on\("([^"]+)"(?:,\s*(HTTP_[A-Z]+))?')
|
source = firmware_source()
|
||||||
return {(match.group(1), match.group(2) or "ANY") for match in pattern.finditer(firmware_source())}
|
literal_pattern = re.compile(r'server\.on\("([^"]+)"(?:,\s*(HTTP_[A-Z]+))?')
|
||||||
|
macro_pattern = re.compile(r'server\.on\(API_V1\("([^"]+)"\)(?:,\s*(HTTP_[A-Z]+))?')
|
||||||
|
|
||||||
|
routes = {(match.group(1), match.group(2) or "ANY") for match in literal_pattern.finditer(source)}
|
||||||
|
routes.update({
|
||||||
|
(f"/api/v1{match.group(1)}", match.group(2) or "ANY")
|
||||||
|
for match in macro_pattern.finditer(source)
|
||||||
|
})
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
def test_firmware_registers_current_http_contract_routes():
|
def test_firmware_registers_current_http_contract_routes():
|
||||||
routes = registered_routes()
|
routes = registered_routes()
|
||||||
|
|
||||||
expected_routes = {
|
expected_routes = {
|
||||||
|
("/api/v1/status", "ANY"),
|
||||||
|
("/api/v1/config", "HTTP_GET"),
|
||||||
|
("/api/v1/relay/set", "HTTP_POST"),
|
||||||
|
("/api/v1/config/wifi", "HTTP_GET"),
|
||||||
|
("/api/v1/config/wifi", "HTTP_POST"),
|
||||||
|
("/api/v1/wifi/connect", "HTTP_POST"),
|
||||||
|
("/api/v1/wifi/clear", "HTTP_POST"),
|
||||||
|
("/api/v1/temps/scan", "HTTP_POST"),
|
||||||
|
("/api/v1/temps/assign", "HTTP_POST"),
|
||||||
|
("/api/v1/temps/clear", "HTTP_POST"),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected_routes <= routes
|
||||||
|
|
||||||
|
|
||||||
|
def test_firmware_keeps_root_compatibility_aliases():
|
||||||
|
routes = registered_routes()
|
||||||
|
|
||||||
|
compatibility_routes = {
|
||||||
("/status", "ANY"),
|
("/status", "ANY"),
|
||||||
("/config", "HTTP_GET"),
|
("/config", "HTTP_GET"),
|
||||||
("/relay/set", "HTTP_POST"),
|
("/relay/set", "HTTP_POST"),
|
||||||
@@ -42,7 +69,21 @@ def test_firmware_registers_current_http_contract_routes():
|
|||||||
("/temps/clear", "HTTP_POST"),
|
("/temps/clear", "HTTP_POST"),
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected_routes <= routes
|
assert compatibility_routes <= routes
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_uses_versioned_api_routes():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert 'const API_BASE="/api/v1";' in source
|
||||||
|
assert 'fetch(api("' in source
|
||||||
|
assert 'fetch("/status"' not in source
|
||||||
|
assert 'fetch("/config' not in source
|
||||||
|
assert 'fetch("/relay' not in source
|
||||||
|
assert 'fetch("/temps' not in source
|
||||||
|
assert 'fetch("/wifi' not in source
|
||||||
|
assert "/bms/reconnect" not in source
|
||||||
|
assert "/system/restart" not in source
|
||||||
|
|
||||||
|
|
||||||
def test_status_fixture_matches_dashboard_contract_shape():
|
def test_status_fixture_matches_dashboard_contract_shape():
|
||||||
|
|||||||
+33
-11
@@ -241,6 +241,7 @@ class FakeResponse:
|
|||||||
class FakeRequests:
|
class FakeRequests:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.urls = []
|
self.urls = []
|
||||||
|
self.posts = []
|
||||||
self.responses = []
|
self.responses = []
|
||||||
|
|
||||||
def get(self, url):
|
def get(self, url):
|
||||||
@@ -251,6 +252,15 @@ class FakeRequests:
|
|||||||
|
|
||||||
return FakeResponse({"ok": True})
|
return FakeResponse({"ok": True})
|
||||||
|
|
||||||
|
def post(self, url, json=None):
|
||||||
|
self.urls.append(url)
|
||||||
|
self.posts.append({"url": url, "json": json})
|
||||||
|
|
||||||
|
if self.responses:
|
||||||
|
return FakeResponse(self.responses.pop(0))
|
||||||
|
|
||||||
|
return FakeResponse({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
def test_http_client_get_status():
|
def test_http_client_get_status():
|
||||||
from comms.http_client import HttpClient
|
from comms.http_client import HttpClient
|
||||||
@@ -264,7 +274,7 @@ def test_http_client_get_status():
|
|||||||
|
|
||||||
payload = client.get_status()
|
payload = client.get_status()
|
||||||
|
|
||||||
assert fake_requests.urls == ["http://192.168.4.1/status"]
|
assert fake_requests.urls == ["http://192.168.4.1/api/v1/status"]
|
||||||
assert payload["type"] == "status_response"
|
assert payload["type"] == "status_response"
|
||||||
assert payload["battery"]["soc"] == 82
|
assert payload["battery"]["soc"] == 82
|
||||||
|
|
||||||
@@ -275,9 +285,13 @@ def test_http_client_set_relay_on():
|
|||||||
fake_requests = FakeRequests()
|
fake_requests = FakeRequests()
|
||||||
client = HttpClient(fake_requests)
|
client = HttpClient(fake_requests)
|
||||||
|
|
||||||
payload = client.set_relay("starlink", True)
|
payload = client.set_relay("relay_1", True)
|
||||||
|
|
||||||
assert fake_requests.urls == ["http://192.168.4.1/relay/starlink/on"]
|
assert fake_requests.urls == ["http://192.168.4.1/api/v1/relay/set"]
|
||||||
|
assert fake_requests.posts == [{
|
||||||
|
"url": "http://192.168.4.1/api/v1/relay/set",
|
||||||
|
"json": {"id": "relay_1", "state": True},
|
||||||
|
}]
|
||||||
assert payload == {"ok": True}
|
assert payload == {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -287,9 +301,13 @@ def test_http_client_set_relay_off():
|
|||||||
fake_requests = FakeRequests()
|
fake_requests = FakeRequests()
|
||||||
client = HttpClient(fake_requests)
|
client = HttpClient(fake_requests)
|
||||||
|
|
||||||
payload = client.set_relay("fridge", False)
|
payload = client.set_relay("relay_2", False)
|
||||||
|
|
||||||
assert fake_requests.urls == ["http://192.168.4.1/relay/fridge/off"]
|
assert fake_requests.urls == ["http://192.168.4.1/api/v1/relay/set"]
|
||||||
|
assert fake_requests.posts == [{
|
||||||
|
"url": "http://192.168.4.1/api/v1/relay/set",
|
||||||
|
"json": {"id": "relay_2", "state": False},
|
||||||
|
}]
|
||||||
assert payload == {"ok": True}
|
assert payload == {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@@ -317,7 +335,7 @@ def test_communication_service_http_fallback_status():
|
|||||||
assert response["type"] == "status_response"
|
assert response["type"] == "status_response"
|
||||||
assert state.battery["soc"] == 66
|
assert state.battery["soc"] == 66
|
||||||
assert fake_uart.writes == []
|
assert fake_uart.writes == []
|
||||||
assert fake_requests.urls == ["http://192.168.4.1/status"]
|
assert fake_requests.urls == ["http://192.168.4.1/api/v1/status"]
|
||||||
|
|
||||||
|
|
||||||
def test_communication_service_http_fallback_relay():
|
def test_communication_service_http_fallback_relay():
|
||||||
@@ -330,8 +348,8 @@ def test_communication_service_http_fallback_relay():
|
|||||||
fake_requests.responses = [
|
fake_requests.responses = [
|
||||||
{
|
{
|
||||||
"type": "relay_response",
|
"type": "relay_response",
|
||||||
"relay": "fridge",
|
"id": "relay_1",
|
||||||
"enabled": True,
|
"state": True,
|
||||||
"ok": True,
|
"ok": True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -344,12 +362,16 @@ def test_communication_service_http_fallback_relay():
|
|||||||
)
|
)
|
||||||
|
|
||||||
service.enable_http_fallback()
|
service.enable_http_fallback()
|
||||||
response = service.set_relay("fridge", True)
|
response = service.set_relay("relay_1", True)
|
||||||
|
|
||||||
assert response["type"] == "relay_response"
|
assert response["type"] == "relay_response"
|
||||||
assert state.relays["fridge"] is True
|
assert state.relays["relay_1"] is True
|
||||||
assert fake_uart.writes == []
|
assert fake_uart.writes == []
|
||||||
assert fake_requests.urls == ["http://192.168.4.1/relay/fridge/on"]
|
assert fake_requests.urls == ["http://192.168.4.1/api/v1/relay/set"]
|
||||||
|
assert fake_requests.posts == [{
|
||||||
|
"url": "http://192.168.4.1/api/v1/relay/set",
|
||||||
|
"json": {"id": "relay_1", "state": True},
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
def test_communication_service_marks_uart_connected_on_status():
|
def test_communication_service_marks_uart_connected_on_status():
|
||||||
|
|||||||
Reference in New Issue
Block a user