Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2966518da | ||
|
|
90c4490a3d | ||
|
|
f1683897bc | ||
|
|
19538781e8 | ||
|
|
cf722a7287 | ||
|
|
f5e2c5fd24 | ||
|
|
9cb06b86ff | ||
|
|
6acfa61ddb | ||
|
|
8004ce86e1 | ||
|
|
fc4999b9d2 | ||
|
|
989e0458aa | ||
|
|
094bfb33a4 | ||
|
|
bd00b6e7ca | ||
|
|
c49361488d | ||
|
|
8dfcc63688 | ||
|
|
69db0d7fe5 | ||
|
|
0f7697e9dd | ||
|
|
c43f121dc8 | ||
|
|
730da5482f | ||
|
|
954bef4291 | ||
|
|
75c1d4d909 | ||
|
|
46d52052e3 | ||
|
|
bc56514489 | ||
|
|
d4dff6d49c | ||
|
|
567b84261e | ||
|
|
dc70e4f8e5 | ||
|
|
9c2069dd28 | ||
|
|
0f7b5206b6 | ||
|
|
2f973a279c | ||
|
|
40ce727986 | ||
|
|
b665bf1c82 | ||
|
|
594edc3dd9 | ||
|
|
4474b6d11a | ||
|
|
e7965f4b99 | ||
|
|
512a1aa302 | ||
|
|
a721f6b539 | ||
|
|
6cc93667ab | ||
|
|
d87080d23f | ||
|
|
ec39182bbd | ||
|
|
d9b851aa53 | ||
|
|
f609f2ea43 | ||
|
|
dc8340a1ec | ||
|
|
fdf3d657b6 | ||
|
|
e1ea6a19e0 | ||
|
|
a0cad804a2 | ||
|
|
3a386437fa | ||
|
|
63f1d691ec | ||
|
|
a2b747d741 | ||
|
|
78f6a9e53e | ||
|
|
4fe7491afa | ||
|
|
a3b335e708 | ||
|
|
7ab75408b7 | ||
|
|
df9b49292d | ||
|
|
f05f259523 | ||
|
|
3f02f6146e | ||
|
|
5d8afd2e4a | ||
|
|
757b9acdd9 | ||
|
|
2f61c968a2 | ||
|
|
573f24e0a9 | ||
|
|
a86a0b58c4 | ||
|
|
47dd9a7b8b | ||
|
|
1ee73ad00d | ||
|
|
261d8983df | ||
|
|
8a31048646 | ||
|
|
594fa3b9ce | ||
|
|
af8de8fa1e | ||
|
|
175630cda2 |
@@ -0,0 +1,41 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
- Inspect the repo before changing code.
|
||||||
|
- Make minimal, targeted changes.
|
||||||
|
- Preserve existing architecture.
|
||||||
|
- Update docs when APIs, behavior, wiring, or hardware assumptions change.
|
||||||
|
- Run available validation/tests before finishing.
|
||||||
|
- Use meaningful commits.
|
||||||
|
- Explain what changed, why, and any side effects.
|
||||||
|
|
||||||
|
## Critical Constraints
|
||||||
|
|
||||||
|
- Do not modify JBD/Xiaoxiang BLE behavior without explicit approval.
|
||||||
|
- Do not change BLE timeout, reconnect, scan, or BMS-selection behavior without approval.
|
||||||
|
- Cargo ESP32 remains the controller, API server, WebUI host, and source of truth.
|
||||||
|
- Dashboard ESP32-S3 is a client only.
|
||||||
|
- Do not move relay/BMS/control authority into the dashboard.
|
||||||
|
- Do not hardcode install-specific names into firmware.
|
||||||
|
- Preserve API compatibility when practical.
|
||||||
|
- Prefer HTTP API integration over custom protocols.
|
||||||
|
|
||||||
|
## Hardware Rules
|
||||||
|
|
||||||
|
- ESP32 relay board outputs trigger external fused automotive relays/contactors.
|
||||||
|
- Do not route fridge, Starlink, inverter, or other major load current through the ESP32 relay board.
|
||||||
|
- Dashboard target is Waveshare ESP32-S3-Touch-LCD-5.
|
||||||
|
- Dashboard connects to Cargo ESP32 AP over WiFi.
|
||||||
|
- Dashboard is powered from ACC switched 12V into VIN.
|
||||||
|
- Do not power dashboard from OBD-II pin 16 in the permanent install.
|
||||||
|
- CAN termination must stay disabled when connected to vehicle CAN.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Primary docs:
|
||||||
|
|
||||||
|
- docs/ARCHITECTURE.md
|
||||||
|
- docs/HARDWARE.md
|
||||||
|
- docs/ROADMAP.md
|
||||||
|
- docs/API.md when API changes
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Relay Wiring
|
|
||||||
|
|
||||||
Current architecture uses the onboard relay contacts
|
|
||||||
as the load switching device.
|
|
||||||
|
|
||||||
Positive-side switching:
|
|
||||||
|
|
||||||
Battery +
|
|
||||||
|
|
|
||||||
Fuse
|
|
||||||
|
|
|
||||||
Relay COM
|
|
||||||
|
|
|
||||||
Relay NO
|
|
||||||
|
|
|
||||||
Load +
|
|
||||||
|
|
||||||
Load negative returns directly to the ground bus.
|
|
||||||
|
|
||||||
Relay 1:
|
|
||||||
|
|
||||||
Starlink Mini
|
|
||||||
|
|
||||||
Relay 2:
|
|
||||||
|
|
||||||
Fridge
|
|
||||||
|
|
||||||
@@ -4,13 +4,13 @@ Overland Controller is a local-first vehicle power and monitoring system designe
|
|||||||
|
|
||||||
Current target vehicle:
|
Current target vehicle:
|
||||||
|
|
||||||
Nissan Xterra
|
overland vehicle
|
||||||
|
|
||||||
The firmware itself is intended to stay vehicle-agnostic.
|
The firmware itself is intended to stay vehicle-agnostic.
|
||||||
|
|
||||||
## Current Version
|
## Current Version
|
||||||
|
|
||||||
0.4.0
|
0.5.0
|
||||||
|
|
||||||
## What It Does
|
## What It Does
|
||||||
|
|
||||||
@@ -34,15 +34,15 @@ Current controller:
|
|||||||
|
|
||||||
- ESP32 2-relay board
|
- ESP32 2-relay board
|
||||||
|
|
||||||
Planned dashboard:
|
Primary dashboard target:
|
||||||
|
|
||||||
- Raspberry Pi Pico 2 W
|
- Waveshare ESP32-S3-Touch-LCD-5
|
||||||
- Touchscreen display
|
- WiFi client to the Cargo ESP32 access point
|
||||||
- UART connection to ESP32
|
- HTTP API integration
|
||||||
|
|
||||||
Optional future expansion:
|
Optional future expansion:
|
||||||
|
|
||||||
- Pi Zero 2 W for advanced logging/charts/web app
|
- Pi Zero for long-term logging, charts, and local integrations
|
||||||
|
|
||||||
## WebUI
|
## WebUI
|
||||||
|
|
||||||
@@ -68,22 +68,27 @@ The Config tab is the preferred initial setup workflow.
|
|||||||
|
|
||||||
The ESP32 is the source of truth.
|
The ESP32 is the source of truth.
|
||||||
|
|
||||||
The Pico dashboard will be an instant-on physical display and control surface.
|
The Waveshare ESP32-S3 dashboard is a client-only local display and control surface.
|
||||||
|
|
||||||
The WebUI is used for setup, configuration, and phone access from camp.
|
The WebUI is used for setup, configuration, and phone access from camp.
|
||||||
|
|
||||||
|
A future Pi Zero may be added for long-term logging or richer local integrations, but it must not become required for core control.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Important docs:
|
Important docs:
|
||||||
|
|
||||||
- `docs/project-state.md`
|
- `docs/project-state.md`
|
||||||
- `docs/api.md`
|
- `docs/API.md`
|
||||||
|
- `docs/ARCHITECTURE.md`
|
||||||
|
- `docs/HARDWARE.md`
|
||||||
|
- `docs/dashboard-esp32s3.md`
|
||||||
|
- `docs/CONFIGURATION.md`
|
||||||
- `docs/UART_PROTOCOL.md`
|
- `docs/UART_PROTOCOL.md`
|
||||||
- `docs/SERIAL_COMMANDS.md`
|
- `docs/SERIAL_COMMANDS.md`
|
||||||
- `docs/PARITY_MATRIX.md`
|
- `docs/PARITY_MATRIX.md`
|
||||||
- `docs/wiring.md`
|
|
||||||
- `docs/TEMP_SETUP.md`
|
- `docs/TEMP_SETUP.md`
|
||||||
- `docs/roadmap.md`
|
- `docs/ROADMAP.md`
|
||||||
- `docs/CHANGELOG.md`
|
- `docs/CHANGELOG.md`
|
||||||
|
|
||||||
## Safety
|
## Safety
|
||||||
@@ -99,4 +104,4 @@ Use:
|
|||||||
- Strain relief
|
- Strain relief
|
||||||
- Testing before connecting expensive loads
|
- Testing before connecting expensive loads
|
||||||
|
|
||||||
Do not connect vehicle 12V directly to ESP32 or Pico GPIO pins.
|
Do not connect vehicle 12V directly to ESP32 GPIO pins or dashboard GPIO pins.
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Legacy Pico Dashboard
|
||||||
|
|
||||||
|
This directory contains the archived Raspberry Pi Pico dashboard implementation.
|
||||||
|
|
||||||
|
It is no longer active project firmware.
|
||||||
|
|
||||||
|
## Why this was archived
|
||||||
|
|
||||||
|
The current dashboard direction is the Waveshare ESP32-S3 touchscreen dashboard communicating with the Cargo ESP32 over WiFi/HTTP REST.
|
||||||
|
|
||||||
|
The Pico dashboard path was based around:
|
||||||
|
|
||||||
|
- Raspberry Pi Pico / Pico 2 W
|
||||||
|
- UART communication
|
||||||
|
- Pico-specific display/touch/buzzer hardware
|
||||||
|
- Python/MicroPython-style dashboard structure
|
||||||
|
|
||||||
|
That architecture is no longer the current plan.
|
||||||
|
|
||||||
|
## What may still be useful
|
||||||
|
|
||||||
|
Some concepts may still be useful as reference material when building the ESP32-S3 dashboard:
|
||||||
|
|
||||||
|
- HTTP client flow
|
||||||
|
- State model ideas
|
||||||
|
- Alarm logic concepts
|
||||||
|
- Screen/view-model structure
|
||||||
|
- API payload expectations
|
||||||
|
|
||||||
|
Treat this as reference only. Do not add new active dashboard code here.
|
||||||
|
|
||||||
|
## Current dashboard target
|
||||||
|
|
||||||
|
Active dashboard work should go under:
|
||||||
|
|
||||||
|
firmware/esp32-s3-dashboard/
|
||||||
|
|
||||||
|
or another explicitly approved ESP32-S3 dashboard firmware location.
|
||||||
+6
-5
@@ -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()
|
||||||
+4
-2
@@ -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)
|
||||||
@@ -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():
|
||||||
+806
-512
File diff suppressed because it is too large
Load Diff
+177
-212
@@ -1,93 +1,107 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
Overland Controller is a distributed monitoring and control platform for vehicles, trailers, campers, and mobile power systems.
|
Overland Controller is a distributed monitoring and control platform for mobile power systems and overland vehicles.
|
||||||
|
|
||||||
The system separates electrical control from user interface logic.
|
Core principle:
|
||||||
|
|
||||||
## Main Components
|
Cargo ESP32 = controller / source of truth
|
||||||
|
Waveshare ESP32-S3 dashboard = local touchscreen client
|
||||||
|
Phone/laptop WebUI = configuration interface
|
||||||
|
Home server = future optional remote access layer
|
||||||
|
|
||||||
### ESP32 Controller
|
The system must work locally without internet.
|
||||||
|
|
||||||
The ESP32 is the system authority.
|
## Cargo ESP32 Controller
|
||||||
|
|
||||||
|
The Cargo ESP32 owns critical control state and configuration.
|
||||||
|
|
||||||
Responsibilities:
|
Responsibilities:
|
||||||
|
|
||||||
- Read battery/BMS data
|
- Read JBD/Xiaoxiang BMS data over BLE
|
||||||
- Read temperature sensors
|
- Read DS18B20 temperature sensors
|
||||||
- Control relay outputs
|
- Control relay trigger outputs
|
||||||
- Store configuration
|
- Store configuration
|
||||||
- Serve the HTTP API
|
- Serve HTTP API
|
||||||
- Communicate with the Pico dashboard over UART
|
- Serve local WebUI
|
||||||
- Maintain system status
|
- Run WiFi AP mode
|
||||||
- Handle alarms
|
- Maintain status, alarms, and fault state
|
||||||
|
|
||||||
### Pico Dashboard
|
The Cargo ESP32 should not depend on the dashboard, phone, home server, or internet for core operation.
|
||||||
|
|
||||||
The Pico is the primary user interface.
|
## Waveshare ESP32-S3 Dashboard
|
||||||
|
|
||||||
|
Target hardware:
|
||||||
|
|
||||||
|
- Waveshare ESP32-S3-Touch-LCD-5
|
||||||
|
- 5 inch 800x480 capacitive touchscreen
|
||||||
|
- ESP32-S3
|
||||||
|
- 16MB flash
|
||||||
|
- 8MB PSRAM
|
||||||
|
- WiFi + BLE
|
||||||
|
- CAN with onboard TJA1051 transceiver
|
||||||
|
- microSD
|
||||||
|
- 7-36V VIN support
|
||||||
|
|
||||||
Responsibilities:
|
Responsibilities:
|
||||||
|
|
||||||
- Request status from ESP32
|
- Native LVGL dashboard
|
||||||
- Display battery data
|
- Connect to Cargo ESP32 AP as WiFi client
|
||||||
- Display temperature data
|
- Poll Cargo ESP32 HTTP API
|
||||||
- Display relay states
|
- Send simple control commands through HTTP API
|
||||||
- Send relay commands
|
- Display battery, relay, temp, vehicle, and alert data
|
||||||
- Send configuration commands
|
- Future CAN vehicle data
|
||||||
- Provide setup workflows
|
- Future transmission temperature sender display
|
||||||
- Operate without internet
|
|
||||||
|
|
||||||
The Pico should not own critical control state. The ESP32 remains the source of truth.
|
The dashboard is a client only. It must not own relay state, BMS config, alarms, or persistent configuration.
|
||||||
|
|
||||||
## Communication Paths
|
## WebUI
|
||||||
|
|
||||||
### UART
|
The WebUI remains hosted by the Cargo ESP32.
|
||||||
|
|
||||||
Primary dashboard/control path.
|
Use WebUI for:
|
||||||
|
|
||||||
Used for:
|
- Setup/configuration
|
||||||
|
- Relay names
|
||||||
- Status requests
|
- Temperature sensor names
|
||||||
- Relay control
|
|
||||||
- Configuration updates
|
|
||||||
- BMS setup
|
- BMS setup
|
||||||
- BLE scan requests
|
|
||||||
- BMS selection
|
|
||||||
|
|
||||||
UART should eventually support every core management action needed by the Pico.
|
|
||||||
|
|
||||||
### HTTP
|
|
||||||
|
|
||||||
Secondary setup/debug path.
|
|
||||||
|
|
||||||
Used for:
|
|
||||||
|
|
||||||
- Browser testing
|
|
||||||
- Phone/laptop setup
|
|
||||||
- Future web UI
|
|
||||||
- API debugging
|
|
||||||
|
|
||||||
HTTP should mirror the same data model as UART where practical.
|
|
||||||
|
|
||||||
### USB Serial
|
|
||||||
|
|
||||||
Service/debug path.
|
|
||||||
|
|
||||||
Used for:
|
|
||||||
|
|
||||||
- Development
|
|
||||||
- Recovery
|
- Recovery
|
||||||
- Troubleshooting
|
- API testing
|
||||||
- Emergency configuration
|
|
||||||
|
|
||||||
## Data Model
|
Avoid complex configuration on the touchscreen dashboard.
|
||||||
|
|
||||||
The same generic data model should be used across:
|
## Network
|
||||||
|
|
||||||
- HTTP API
|
Cargo ESP32 runs WPA2 AP mode continuously.
|
||||||
- UART protocol
|
|
||||||
- Pico dashboard
|
Expected local URL:
|
||||||
|
|
||||||
|
http://192.168.4.1
|
||||||
|
|
||||||
|
Dashboard connects to that AP and talks HTTP.
|
||||||
|
|
||||||
|
No UART cable is planned between dashboard and Cargo ESP32.
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
|
||||||
|
HTTP is the primary integration contract for:
|
||||||
|
|
||||||
|
- WebUI
|
||||||
|
- Dashboard
|
||||||
- Simulator
|
- Simulator
|
||||||
- Future MQTT/Home Assistant integrations
|
- Future home server
|
||||||
|
- Future MQTT/Home Assistant/Grafana integrations
|
||||||
|
|
||||||
|
Preferred direction:
|
||||||
|
|
||||||
|
GET /api/v1/health
|
||||||
|
GET /api/v1/capabilities
|
||||||
|
GET /api/v1/status
|
||||||
|
GET /api/v1/config
|
||||||
|
GET /api/v1/config/export
|
||||||
|
POST /api/v1/config/import
|
||||||
|
POST /api/v1/relay/set
|
||||||
|
|
||||||
|
Pre-versioned root routes remain registered as compatibility aliases for existing local clients.
|
||||||
|
|
||||||
## Generic IDs
|
## Generic IDs
|
||||||
|
|
||||||
@@ -109,9 +123,9 @@ Temperature sensors:
|
|||||||
temp_7
|
temp_7
|
||||||
temp_8
|
temp_8
|
||||||
|
|
||||||
Installation-specific names belong only in saved configuration.
|
Install-specific names belong in saved configuration only.
|
||||||
|
|
||||||
Examples of configuration names:
|
Examples:
|
||||||
|
|
||||||
Fridge
|
Fridge
|
||||||
Starlink
|
Starlink
|
||||||
@@ -119,33 +133,11 @@ Examples of configuration names:
|
|||||||
Outside
|
Outside
|
||||||
House Battery
|
House Battery
|
||||||
|
|
||||||
These names should not be hardcoded into firmware behavior.
|
## Status Model
|
||||||
|
|
||||||
## Configuration Ownership
|
Dashboard should be able to render from status/config responses without duplicating business logic.
|
||||||
|
|
||||||
Configuration is stored on the ESP32.
|
Expected status groups:
|
||||||
|
|
||||||
Stored configuration includes:
|
|
||||||
|
|
||||||
- Device name
|
|
||||||
- Relay names
|
|
||||||
- Relay enable states
|
|
||||||
- Temperature sensor names
|
|
||||||
- Temperature sensor addresses
|
|
||||||
- Temperature sensor enable states
|
|
||||||
- BMS name
|
|
||||||
- BMS BLE address
|
|
||||||
- BMS address type
|
|
||||||
|
|
||||||
The Pico may edit configuration through UART, but the ESP32 stores and owns it.
|
|
||||||
|
|
||||||
## Status Flow
|
|
||||||
|
|
||||||
Pico sends:
|
|
||||||
|
|
||||||
status_request
|
|
||||||
|
|
||||||
ESP32 responds with the same shape as HTTP /status:
|
|
||||||
|
|
||||||
battery
|
battery
|
||||||
temps
|
temps
|
||||||
@@ -156,176 +148,149 @@ ESP32 responds with the same shape as HTTP /status:
|
|||||||
system
|
system
|
||||||
config
|
config
|
||||||
|
|
||||||
## Relay Control Flow
|
## Vehicle Data
|
||||||
|
|
||||||
Pico sends a generic relay command:
|
Use standard OBD-II over CAN first.
|
||||||
|
|
||||||
relay_1 on
|
Planned standard PIDs:
|
||||||
relay_1 off
|
|
||||||
relay_2 on
|
|
||||||
relay_2 off
|
|
||||||
|
|
||||||
ESP32 updates the relay output and reports the new state.
|
0105 coolant temperature
|
||||||
|
010C RPM
|
||||||
|
010D vehicle speed
|
||||||
|
|
||||||
## BMS Setup Flow
|
Transmission temperature is planned through a dedicated sender rather than Nissan CAN reverse engineering.
|
||||||
|
|
||||||
BMS setup can be started from the Pico.
|
## Future Home Server
|
||||||
|
|
||||||
Expected flow:
|
Future optional architecture:
|
||||||
|
|
||||||
enter_bms_setup
|
Phone/PWA
|
||||||
scan_ble
|
-> Home server HTTPS
|
||||||
scan_ble
|
-> MQTT/WebSocket/HTTPS bridge
|
||||||
select_bms
|
-> Cargo ESP32 outbound connection
|
||||||
save_config
|
|
||||||
|
|
||||||
The ESP32 handles BLE scanning and BMS selection.
|
Remote features must not break local operation.
|
||||||
|
|
||||||
The Pico only displays options and sends the selected index or address.
|
|
||||||
|
|
||||||
## HTTP / UART Parity
|
## Cargo-led AP Pairing and Credential Migration
|
||||||
|
|
||||||
Long-term goal:
|
The Cargo ESP32 owns the local overland network. It is the AP owner, source of truth, and configuration authority.
|
||||||
|
|
||||||
- HTTP /status equals UART status_request
|
Target behavior:
|
||||||
- HTTP /config equals UART config_request
|
|
||||||
- HTTP /config/device equals UART config_device
|
|
||||||
- HTTP /config/relay equals UART config_relay
|
|
||||||
- HTTP /config/temp equals UART config_temp
|
|
||||||
- HTTP /config/bms equals UART config_bms
|
|
||||||
- HTTP relay control equals UART set_relay
|
|
||||||
- HTTP factory reset equals UART factory_reset
|
|
||||||
|
|
||||||
## Current Priority
|
1. Cargo ESP32 starts a WPA2-protected AP.
|
||||||
|
2. Cargo OLED shows setup credentials and critical recovery/status information.
|
||||||
|
3. Phone connects to the Cargo AP and uses the WebUI for setup/admin.
|
||||||
|
4. Dashboard ESP32-S3 joins the Cargo AP as a client.
|
||||||
|
5. If the user changes the Cargo AP SSID/password, the Cargo ESP stages the new credentials and coordinates dashboard migration.
|
||||||
|
6. Dashboard stores the new credentials before the Cargo AP restarts.
|
||||||
|
7. Cargo AP applies the new credentials.
|
||||||
|
8. Dashboard reconnects automatically.
|
||||||
|
|
||||||
The next implementation priority is UART parity.
|
The dashboard should not own network authority. A temporary dashboard setup AP may exist only as a recovery/fallback mechanism, not the normal pairing path.
|
||||||
|
|
||||||
The Pico should be able to do the important setup and control operations without relying on WiFi.
|
Recovery target:
|
||||||
|
|
||||||
Minimum UART v1 scope:
|
- A setup/status button on the Cargo ESP enclosure can show AP credentials on the OLED.
|
||||||
|
- A long hold can restore factory AP credentials or enter recovery mode.
|
||||||
|
|
||||||
- status_request
|
|
||||||
- set_relay
|
|
||||||
- config_request
|
|
||||||
- config_device
|
|
||||||
- config_relay
|
|
||||||
- config_temp
|
|
||||||
- config_bms
|
|
||||||
- save_config
|
|
||||||
- factory_reset
|
|
||||||
- enter_bms_setup
|
|
||||||
- scan_ble
|
|
||||||
- select_bms
|
|
||||||
- exit_bms_setup
|
|
||||||
|
|
||||||
## Future Integrations
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
Possible future integrations:
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
- MQTT
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
- Home Assistant
|
|
||||||
- Grafana
|
|
||||||
- InfluxDB
|
|
||||||
- OBD-II
|
|
||||||
- CAN bus
|
|
||||||
- GPS
|
|
||||||
- Data logging
|
|
||||||
- OTA updates
|
|
||||||
|
|
||||||
All future integrations should consume the same generic status/config model.
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
---
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
## Networking Architecture
|
## Role Boundary Summary
|
||||||
|
|
||||||
### Current Network Mode
|
See `docs/controller-roles.md` for the authoritative role split.
|
||||||
|
|
||||||
The ESP32 currently runs in Access Point mode.
|
Committed v1 architecture:
|
||||||
|
|
||||||
Default access:
|
Dashboard ESP32-S3
|
||||||
|
<-> WiFi / HTTP REST
|
||||||
|
Cargo ESP32 AP
|
||||||
|
<-> control outputs
|
||||||
|
Automotive load-switching layer
|
||||||
|
<-> fused loads
|
||||||
|
Fridge / Starlink / lights / spare outputs
|
||||||
|
|
||||||
http://192.168.4.1
|
The Cargo ESP32 owns power-system control.
|
||||||
|
|
||||||
The AP is the recovery/setup network.
|
The Dashboard ESP32-S3 renders the touchscreen UI and future vehicle visualization.
|
||||||
|
|
||||||
### Planned Network Mode
|
For v1, controller-to-dashboard communication is WiFi/HTTP only. ESP-NOW, MQTT, UART, and CAN-between-controllers are not part of the v1 dashboard link.
|
||||||
|
|
||||||
The planned long-term network mode is AP + STA.
|
The load-switching hardware is not finalized. Bosch relays, WUPP-style modules, smart high-side switching, or a future custom PCB remain implementation options until explicitly selected.
|
||||||
|
|
||||||
AP mode:
|
## Dashboard Hardware Target
|
||||||
|
|
||||||
Always available
|
The current dashboard hardware target is the Waveshare ESP32-S3 Touch LCD 5B.
|
||||||
Used for recovery and direct setup
|
|
||||||
|
|
||||||
STA mode:
|
Selected dashboard hardware:
|
||||||
|
|
||||||
Connects to a configured WiFi network
|
- Waveshare ESP32-S3 Touch LCD 5B
|
||||||
Used for camp/home access from phone or laptop
|
- 5-inch touchscreen
|
||||||
|
- 1024x600 display resolution
|
||||||
|
- Capacitive touch
|
||||||
|
- Native WiFi
|
||||||
|
- LVGL-capable ESP32-S3 platform
|
||||||
|
- Integrated CAN transceiver available for future vehicle integration
|
||||||
|
|
||||||
Example networks:
|
This replaces the earlier Pico dashboard concept as the active dashboard architecture.
|
||||||
|
|
||||||
Starlink
|
The Dashboard ESP32-S3 communicates with the Cargo ESP32 over WiFi/HTTP REST for v1. CAN/OBD-II support remains a future dashboard-side vehicle-visualization feature and is not part of the Cargo ESP32 role.
|
||||||
Home WiFi
|
|
||||||
Shop WiFi
|
|
||||||
|
|
||||||
### Multiple WiFi Profiles
|
## Existing DS18B20 Temperature Support
|
||||||
|
|
||||||
The ESP32 should eventually store multiple WiFi profiles and try them in priority order.
|
DS18B20 temperature sensor support is already part of the Cargo ESP architecture.
|
||||||
|
|
||||||
Example:
|
Current responsibility split:
|
||||||
|
|
||||||
1. Starlink
|
- Cargo ESP32 owns DS18B20 sensor reading.
|
||||||
2. Home WiFi
|
- Cargo ESP32 exposes temperature state through the WebUI/API path.
|
||||||
3. Shop WiFi
|
- Dashboard ESP32-S3 should consume temperature data from the Cargo ESP API.
|
||||||
|
- Dashboard ESP32-S3 should not wire directly to cargo-area DS18B20 sensors for v1.
|
||||||
|
|
||||||
Expected behavior:
|
This means future dashboard work should display existing Cargo ESP temperature telemetry rather than reimplementing temperature sensing on the dashboard.
|
||||||
|
|
||||||
Start AP first
|
## Cargo ESP API Contract
|
||||||
Try configured STA networks
|
|
||||||
Connect to the first available network
|
|
||||||
Keep AP available even after STA connects
|
|
||||||
|
|
||||||
Reason:
|
The Cargo ESP API contract is documented in `docs/cargo-api-contract.md`.
|
||||||
|
|
||||||
- Starlink may be off
|
The Cargo ESP32 is the source of truth for:
|
||||||
- Home WiFi may only be available in the driveway
|
|
||||||
- AP must remain available for recovery
|
|
||||||
- User should not lose access because of a bad WiFi password
|
|
||||||
|
|
||||||
### Phone Dashboard Access
|
- Relay/output state
|
||||||
|
- DS18B20 temperature readings
|
||||||
|
- BMS telemetry
|
||||||
|
- Alarm/fault state
|
||||||
|
- Local configuration
|
||||||
|
|
||||||
When connected to the ESP32 AP:
|
The ESP32-S3 dashboard should consume Cargo ESP API data over WiFi/HTTP REST instead of directly owning cargo sensors or relay state.
|
||||||
|
|
||||||
http://192.168.4.1
|
## Cargo ESP Hardware Transition Plan
|
||||||
|
|
||||||
When connected through Starlink/home WiFi:
|
Current active development and testing remains on the existing 2-channel relay board.
|
||||||
|
|
||||||
http://controller-ip
|
A LILYGO T-Relay-S3 6-way board has been ordered as a future Cargo ESP candidate.
|
||||||
|
|
||||||
Future mDNS target:
|
Transition strategy:
|
||||||
|
|
||||||
http://overland-controller.local
|
- Keep current 2-channel board working.
|
||||||
|
- Make the Cargo ESP API output-count agnostic.
|
||||||
|
- Represent outputs as a list of output objects.
|
||||||
|
- Let clients render whatever output list the Cargo ESP exposes.
|
||||||
|
- Add a hardware profile/config layer before migrating firmware to LilyGO.
|
||||||
|
- Bench test LilyGO relay boot behavior before connecting it to the WUPP block.
|
||||||
|
|
||||||
### Dashboard Roles
|
The API should not require redesign when moving from 2 outputs to 6 outputs.
|
||||||
|
|
||||||
ESP32:
|
|
||||||
|
|
||||||
Source of truth
|
|
||||||
Lightweight phone dashboard
|
|
||||||
HTTP API
|
|
||||||
Recovery AP
|
|
||||||
|
|
||||||
Pico 2 W:
|
|
||||||
|
|
||||||
Instant physical dashboard
|
|
||||||
UART client
|
|
||||||
Touchscreen UI
|
|
||||||
|
|
||||||
Optional future Pi Zero:
|
|
||||||
|
|
||||||
Advanced web dashboard
|
|
||||||
Logging
|
|
||||||
Charts
|
|
||||||
Integrations
|
|
||||||
|
|
||||||
The Pi Zero should be optional, not required for core operation.
|
|
||||||
|
|||||||
+25
-3
@@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.5.0 - 2026-06-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- WPA2 AP configuration API and WebUI controls.
|
||||||
|
- AP reset recovery action with regenerated password.
|
||||||
|
- Status API field filtering for smaller dashboard/WebUI payloads.
|
||||||
|
- Config page subtabs for General, WiFi/AP, Relays, Temperatures, BMS, and Maintenance.
|
||||||
|
- Relay button state/command feedback in the WebUI.
|
||||||
|
- Optional Cargo OLED display scaffold.
|
||||||
|
- GPIO plan assigning OLED I2C to GPIO 21/22 and setup button to GPIO 25.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Dashboard communication direction is now WiFi/HTTP API.
|
||||||
|
- Retired active dashboard UART pin usage.
|
||||||
|
- Improved WebUI visual styling, mobile temperature settings layout, and config form behavior during polling.
|
||||||
|
- Removed vehicle-specific branding from active project docs/examples.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Config inputs no longer get overwritten while typing during polling.
|
||||||
|
- Temperature config lookup remains available outside disabled UART code.
|
||||||
|
- Misleading WebUI connection badge text.
|
||||||
|
|
||||||
## 0.4.0 - 2026-06-04
|
## 0.4.0 - 2026-06-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -21,14 +43,14 @@
|
|||||||
- 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
|
||||||
|
|
||||||
- ESP32 remains the controller/source of truth.
|
- ESP32 remains the controller/source of truth.
|
||||||
- WebUI is now the preferred initial configuration workflow.
|
- WebUI is now the preferred initial configuration workflow.
|
||||||
- Pico dashboard remains planned as the instant-on physical dashboard.
|
- Waveshare ESP32-S3 is the primary physical dashboard target.
|
||||||
- Built-in ESP32 relays are the current planned relay switching method for Starlink Mini and fridge.
|
- ESP32 relay board outputs are treated as control/trigger outputs for external fused automotive relays/contactors on major loads.
|
||||||
- BMS source now reports configured JBD BMS even when disconnected.
|
- BMS source now reports configured JBD BMS even when disconnected.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
# ChatGPT Handoff
|
||||||
|
|
||||||
|
Last updated: 2026-06-07
|
||||||
|
|
||||||
|
Use this document to bring a new ChatGPT/Codex session up to speed quickly. Keep it current when project goals, architecture, APIs, hardware assumptions, validation status, or near-term priorities change.
|
||||||
|
|
||||||
|
## How to Use This
|
||||||
|
|
||||||
|
At the start of a new assistant session, provide this file along with:
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `docs/project-state.md`
|
||||||
|
- `docs/ARCHITECTURE.md`
|
||||||
|
- `docs/HARDWARE.md`
|
||||||
|
- `docs/API.md`
|
||||||
|
- `docs/ROADMAP.md`
|
||||||
|
|
||||||
|
The assistant should treat the Cargo ESP32 firmware and primary docs as the source of truth, then update this handoff before finishing any meaningful change.
|
||||||
|
|
||||||
|
## Non-Negotiable Constraints
|
||||||
|
|
||||||
|
- Cargo ESP32 remains the controller, API server, WebUI host, and source of truth.
|
||||||
|
- Waveshare ESP32-S3 dashboard is a client only.
|
||||||
|
- Do not move relay, BMS, or control authority into the dashboard.
|
||||||
|
- Do not modify JBD/Xiaoxiang BLE behavior without explicit approval.
|
||||||
|
- Do not change BLE timeout, reconnect, scan, or BMS-selection behavior without approval.
|
||||||
|
- Do not hardcode install-specific names into firmware.
|
||||||
|
- Preserve API compatibility when practical.
|
||||||
|
- Prefer HTTP API integration over custom protocols.
|
||||||
|
- ESP32 relay board outputs trigger external fused automotive relays/contactors only.
|
||||||
|
- Do not route fridge, Starlink, inverter, or other major load current through the ESP32 relay board.
|
||||||
|
- Dashboard target is Waveshare ESP32-S3-Touch-LCD-5.
|
||||||
|
- Dashboard connects to Cargo ESP32 AP over WiFi and is powered from ACC switched 12V into VIN.
|
||||||
|
- Do not power dashboard from OBD-II pin 16 in the permanent install.
|
||||||
|
- CAN termination must stay disabled when connected to vehicle CAN.
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
Current firmware version:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Current architecture:
|
||||||
|
|
||||||
|
- ESP32 relay board is the cargo controller.
|
||||||
|
- Embedded WebUI is served by the Cargo ESP32.
|
||||||
|
- HTTP API base path is `/api/v1`.
|
||||||
|
- Root HTTP routes remain as compatibility aliases for existing local clients.
|
||||||
|
- Dashboard direction is Waveshare ESP32-S3 over WiFi/HTTP, not Pico/UART.
|
||||||
|
- UART JSON remains optional for diagnostics or alternate clients.
|
||||||
|
|
||||||
|
Current working feature areas:
|
||||||
|
|
||||||
|
- Relay control through WebUI, HTTP API, UART JSON, and USB serial.
|
||||||
|
- JBD/Xiaoxiang BLE BMS telemetry.
|
||||||
|
- DS18B20 temperature scan, assignment, clear, and configured naming workflow.
|
||||||
|
- WiFi AP recovery network at `192.168.4.1`.
|
||||||
|
- STA mode with saved WiFi networks and priorities.
|
||||||
|
- Config-driven generic relay and temperature IDs.
|
||||||
|
- Config backup/restore API.
|
||||||
|
- BMS setup workflow API.
|
||||||
|
|
||||||
|
## API Direction
|
||||||
|
|
||||||
|
New clients should use `/api/v1`.
|
||||||
|
|
||||||
|
Dashboard MVP should initially consume only:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/v1/status
|
||||||
|
POST /api/v1/relay/set
|
||||||
|
```
|
||||||
|
|
||||||
|
Other current HTTP API areas include:
|
||||||
|
|
||||||
|
- Health and capability discovery.
|
||||||
|
- Configuration read/update/save/factory-reset.
|
||||||
|
- Config export/import.
|
||||||
|
- WiFi config/connect/clear.
|
||||||
|
- Temperature scan/assign/clear.
|
||||||
|
- BMS setup enter/exit, scan, and select.
|
||||||
|
|
||||||
|
See `docs/API.md` for the current contract and `tests/test_http_api_contract.py` for contract coverage.
|
||||||
|
|
||||||
|
## Near-Term Goals
|
||||||
|
|
||||||
|
1. Verify WebUI setup workflow after a fresh flash.
|
||||||
|
2. Clean up DS18B20 physical distribution hardware.
|
||||||
|
3. Test 2-4 temperature probes after the distribution board is installed.
|
||||||
|
4. Start the Waveshare ESP32-S3 dashboard MVP over WiFi/HTTP.
|
||||||
|
5. Improve BMS out-of-range handling only with explicit approval for BLE behavior changes.
|
||||||
|
6. Define future Pi Zero logging/integration role without moving control authority off the Cargo ESP32.
|
||||||
|
|
||||||
|
## Recent Context
|
||||||
|
|
||||||
|
- The project recently standardized current HTTP routes under `/api/v1`.
|
||||||
|
- Health, capabilities, config backup/restore, configuration update, WiFi, temperature setup, and BMS setup endpoints are documented in `docs/API.md`.
|
||||||
|
- The embedded WebUI uses `const API_BASE="/api/v1";`.
|
||||||
|
- Pre-versioned HTTP routes remain registered as compatibility aliases.
|
||||||
|
- `docs/archive/` contains older or superseded planning material and should not override the primary docs.
|
||||||
|
|
||||||
|
## Validation Status
|
||||||
|
|
||||||
|
Available validation is driven by:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 run_tests.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The script performs Python compile checks for the dashboard code, then runs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 -m pytest tests/test_pico_core.py
|
||||||
|
python3 -m pytest tests/test_http_api_contract.py
|
||||||
|
```
|
||||||
|
|
||||||
|
As of this update, the compile step passes in the local environment, but full test execution is blocked because `pytest` is not installed for the active `python3`.
|
||||||
|
|
||||||
|
## Handoff Maintenance Checklist
|
||||||
|
|
||||||
|
Update this document when any of these change:
|
||||||
|
|
||||||
|
- Active project phase or near-term goals.
|
||||||
|
- Cargo ESP32 vs dashboard responsibility boundaries.
|
||||||
|
- Current firmware version.
|
||||||
|
- HTTP API route list, response shape, or compatibility policy.
|
||||||
|
- Hardware assumptions, wiring, power, relay, CAN, or dashboard target.
|
||||||
|
- Validation commands, dependencies, or current test status.
|
||||||
|
- Important decisions that a future assistant should not rediscover from scratch.
|
||||||
|
|
||||||
|
|
||||||
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
|
## Current Architecture Guardrails
|
||||||
|
|
||||||
|
- Inspect the repo before proposing code or documentation changes.
|
||||||
|
- Preserve the Cargo ESP vs Dashboard ESP32-S3 role split.
|
||||||
|
- Cargo ESP32 does not own CAN, OBD-II, tilt sensing, vehicle data, or dashboard rendering.
|
||||||
|
- Dashboard ESP32-S3 does not own relay authority, BMS authority, alarm authority, or persistent cargo-controller configuration.
|
||||||
|
- v1 dashboard communication is WiFi/HTTP REST only.
|
||||||
|
- ESP-NOW, MQTT, UART dashboard link, and CAN-between-controllers are not v1 architecture.
|
||||||
|
- Load-switching hardware is not finalized. Bosch relays, WUPP-style fused distribution, smart high-side switching, and custom PCB options remain implementation choices until explicitly selected.
|
||||||
|
|
||||||
|
## Legacy Pico Dashboard Archive
|
||||||
|
|
||||||
|
The old Pico dashboard implementation has been archived at `archive/legacy-pico-dashboard/`.
|
||||||
|
|
||||||
|
Do not treat it as active firmware. It may be referenced for old HTTP/state/alarm/UI ideas, but new dashboard work should target the ESP32-S3 dashboard path.
|
||||||
|
|
||||||
|
Default validation should use the repo-local virtual environment:
|
||||||
|
|
||||||
|
source .venv/bin/activate
|
||||||
|
python run_tests.py
|
||||||
|
|
||||||
|
## DS18B20 Implementation Note
|
||||||
|
|
||||||
|
DS18B20 temperature support is already implemented in the Cargo ESP/WebUI/API path.
|
||||||
|
|
||||||
|
Do not suggest adding DS18B20 support as if it is missing. Future work should preserve existing Cargo ESP temperature ownership and expose/display that telemetry through the API and ESP32-S3 dashboard.
|
||||||
|
|
||||||
|
## Cargo API Contract Guardrail
|
||||||
|
|
||||||
|
The Cargo ESP API contract is documented in `docs/cargo-api-contract.md`.
|
||||||
|
|
||||||
|
Before making dashboard firmware changes, inspect the current API tests and this contract. Do not move relay, BMS, DS18B20, alarm, or configuration ownership from the Cargo ESP to the dashboard.
|
||||||
|
|
||||||
|
## Pre-LilyGO Baseline Checklist
|
||||||
|
|
||||||
|
Before suggesting a release/tag prior to Cargo ESP hardware migration, check `docs/pre-lilygo-baseline-checklist.md`.
|
||||||
|
|
||||||
|
Do not suggest migrating active Cargo ESP firmware to LilyGO hardware until the user explicitly decides to buy/use that board.
|
||||||
|
|
||||||
|
## LilyGO Transition Guardrail
|
||||||
|
|
||||||
|
The user has ordered a LILYGO T-Relay-S3 6-way board, but it will not arrive for a couple weeks.
|
||||||
|
|
||||||
|
Until the user confirms hardware is available and bench tested:
|
||||||
|
|
||||||
|
- Keep the existing 2-channel relay board as the active test target.
|
||||||
|
- Do not remove 2-channel support.
|
||||||
|
- Do not assume LilyGO firmware is active.
|
||||||
|
- Prepare API/config/docs for output-count-agnostic operation.
|
||||||
|
- Treat LilyGO migration as a future hardware-profile change.
|
||||||
+19
-6
@@ -9,7 +9,7 @@ Configuration survives reboot.
|
|||||||
- Keep firmware generic
|
- Keep firmware generic
|
||||||
- Store installation-specific names in config
|
- Store installation-specific names in config
|
||||||
- Support multiple vehicle/camper layouts
|
- Support multiple vehicle/camper layouts
|
||||||
- Allow setup from serial, HTTP, and eventually Pico UART
|
- Allow setup from HTTP/WebUI and service interfaces
|
||||||
- Avoid hardcoded device names, relay names, sensor names, or BMS names
|
- Avoid hardcoded device names, relay names, sensor names, or BMS names
|
||||||
|
|
||||||
## Stored Configuration
|
## Stored Configuration
|
||||||
@@ -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:
|
||||||
|
|
||||||
@@ -166,6 +166,19 @@ After factory reset:
|
|||||||
- Temperature names return to generic defaults
|
- Temperature names return to generic defaults
|
||||||
- BMS is unconfigured until selected or manually entered
|
- BMS is unconfigured until selected or manually entered
|
||||||
|
|
||||||
|
## Backup And Restore
|
||||||
|
|
||||||
|
Configuration backup includes controller config and saved STA WiFi networks.
|
||||||
|
|
||||||
|
HTTP endpoints:
|
||||||
|
|
||||||
|
GET /api/v1/config/export
|
||||||
|
POST /api/v1/config/import
|
||||||
|
|
||||||
|
`config/export` includes saved WiFi passwords so the backup can be restored. Treat exported JSON as sensitive.
|
||||||
|
|
||||||
|
`config/import` persists the restored config. If WiFi networks are imported, run `POST /api/v1/wifi/connect` afterward to attempt STA connection.
|
||||||
|
|
||||||
## Configuration Persistence
|
## Configuration Persistence
|
||||||
|
|
||||||
Configuration is stored using ESP32 non-volatile storage.
|
Configuration is stored using ESP32 non-volatile storage.
|
||||||
|
|||||||
+167
-209
@@ -1,279 +1,237 @@
|
|||||||
# HARDWARE.md
|
# Hardware
|
||||||
|
|
||||||
# Hardware Overview
|
## Cargo ESP32
|
||||||
|
|
||||||
Overland Controller is designed as a distributed system consisting of a control module and a dashboard module.
|
The cargo ESP32 is the controller and source of truth.
|
||||||
|
|
||||||
The architecture intentionally separates power management from user interface functions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# System Architecture
|
|
||||||
|
|
||||||
text House Battery │ │ ▼ +-------------------+ | ESP32 Controller | +-------------------+ │ ├── JBD/Xiaoxiang BMS ├── DS18B20 Sensors ├── Relay Outputs ├── Local Web API └── WiFi Network ⇅ +-------------------+ | Pico 2 W Dashboard| +-------------------+ │ ├── Touchscreen ├── Dashboard UI ├── Vehicle Display └── User Controls
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ESP32 Controller
|
|
||||||
|
|
||||||
The ESP32 controller is installed near the house battery and electrical system.
|
|
||||||
|
|
||||||
Responsibilities:
|
Responsibilities:
|
||||||
|
|
||||||
- Battery monitoring
|
- BMS BLE
|
||||||
- Temperature monitoring
|
- Relays
|
||||||
- Relay control
|
- DS18B20 temps
|
||||||
- Configuration storage
|
- WebUI
|
||||||
- Local API server
|
- HTTP API
|
||||||
- Dashboard communications
|
- AP mode
|
||||||
|
- Alarms
|
||||||
|
|
||||||
Current development target:
|
Relay board outputs are trigger/control outputs only.
|
||||||
|
|
||||||
text ESP32 DevKit
|
High-current loads must use properly fused automotive relays or contactors.
|
||||||
|
|
||||||
Future hardware can be substituted without major firmware changes.
|
Do not directly route fridge, Starlink, inverter, lights, or other major loads through the ESP32 relay board.
|
||||||
|
|
||||||
---
|
## Dashboard
|
||||||
|
|
||||||
# Raspberry Pi Pico Dashboard
|
Target:
|
||||||
|
|
||||||
The dashboard provides the primary user interface.
|
Waveshare ESP32-S3-Touch-LCD-5
|
||||||
|
|
||||||
Current development target:
|
Reasons:
|
||||||
|
|
||||||
text Raspberry Pi Pico 2 W
|
- ESP32-S3 is mature enough for LVGL dashboard
|
||||||
|
- 5 inch 800x480 display gives enough UI space
|
||||||
|
- WiFi connects to Cargo ESP32 AP
|
||||||
|
- CAN interface with onboard TJA1051 supports future OBD-II vehicle data
|
||||||
|
- microSD allows future logging
|
||||||
|
- 7-36V VIN supports vehicle power
|
||||||
|
|
||||||
Planned display hardware:
|
## Dashboard Power
|
||||||
|
|
||||||
text 3.5" Touchscreen
|
Preferred:
|
||||||
|
|
||||||
Responsibilities:
|
ACC switched 12V
|
||||||
|
-> fuse
|
||||||
|
-> Waveshare VIN
|
||||||
|
|
||||||
- Display system status
|
Do not use OBD-II pin 16 for permanent dashboard power.
|
||||||
- Display battery information
|
|
||||||
- Display temperatures
|
|
||||||
- Control relays
|
|
||||||
- Configuration interface
|
|
||||||
- Alarm notifications
|
|
||||||
|
|
||||||
---
|
## CAN / OBD-II
|
||||||
|
|
||||||
# Battery Monitoring
|
OBD-II pins:
|
||||||
|
|
||||||
Supported BMS:
|
Pin 6 = CAN-H
|
||||||
|
Pin 14 = CAN-L
|
||||||
|
Pin 5 = Signal Ground, optional if needed
|
||||||
|
Pin 16 = Battery 12V, do not use for permanent display power
|
||||||
|
|
||||||
text JBD Xiaoxiang
|
Waveshare CAN termination must remain disabled on vehicle CAN.
|
||||||
|
|
||||||
Communication:
|
Preferred CAN wiring:
|
||||||
|
|
||||||
text Bluetooth Low Energy (BLE)
|
OBD pin 6 -> Waveshare CAN-H
|
||||||
|
OBD pin 14 -> Waveshare CAN-L
|
||||||
|
|
||||||
Data collected:
|
Optional:
|
||||||
|
|
||||||
- State of charge
|
OBD pin 5 -> board/system ground
|
||||||
- Voltage
|
|
||||||
- Current
|
|
||||||
- Capacity
|
|
||||||
- Temperature
|
|
||||||
- Cycle count
|
|
||||||
- Cell voltages
|
|
||||||
- Cell imbalance
|
|
||||||
|
|
||||||
---
|
Use twisted pair. Cat5e/Cat6 twisted pair is acceptable for short vehicle runs.
|
||||||
|
|
||||||
# Temperature Sensors
|
Avoid Scotchlok/vampire taps on vehicle CAN. Prefer OBD splitter or proper automotive splice.
|
||||||
|
|
||||||
Supported sensor type:
|
## Vehicle Data
|
||||||
|
|
||||||
text DS18B20
|
Coolant temp:
|
||||||
|
|
||||||
Connection:
|
Standard OBD-II PID 0105
|
||||||
|
|
||||||
text 1-Wire Bus
|
RPM:
|
||||||
|
|
||||||
Current firmware supports:
|
Standard OBD-II PID 010C
|
||||||
|
|
||||||
text Up to 8 sensors
|
Vehicle speed:
|
||||||
|
|
||||||
Recommended uses:
|
Standard OBD-II PID 010D
|
||||||
|
|
||||||
- Refrigerator compartment
|
## Transmission Temperature Sender
|
||||||
- Freezer compartment
|
|
||||||
- Cabin temperature
|
|
||||||
- Exterior temperature
|
|
||||||
- Electronics enclosure
|
|
||||||
- Battery compartment
|
|
||||||
|
|
||||||
---
|
Preferred over Nissan-specific trans-temp CAN reverse engineering.
|
||||||
|
|
||||||
# Relay Outputs
|
Candidate:
|
||||||
|
|
||||||
Current firmware supports:
|
Autometer 2258 / 2259 style sender
|
||||||
|
|
||||||
text 2 relay outputs
|
Resistance table:
|
||||||
|
|
||||||
Default GPIO assignments:
|
100F = 1123 ohms
|
||||||
|
120F = 708 ohms
|
||||||
|
140F = 460 ohms
|
||||||
|
150F = 374 ohms
|
||||||
|
170F = 253 ohms
|
||||||
|
190F = 175 ohms
|
||||||
|
210F = 123 ohms
|
||||||
|
230F = 89 ohms
|
||||||
|
250F = 65 ohms
|
||||||
|
280F = 42 ohms
|
||||||
|
300F = 32 ohms
|
||||||
|
320F = 25 ohms
|
||||||
|
340F = 20 ohms
|
||||||
|
|
||||||
| Relay | GPIO |
|
Implementation:
|
||||||
|---------|---------|
|
|
||||||
| relay_1 | 16 |
|
|
||||||
| relay_2 | 17 |
|
|
||||||
|
|
||||||
These outputs are intended to drive relay coils, not high-current loads directly.
|
- Sender to ADC voltage divider, or
|
||||||
|
- Sender to ADS1115 over I2C if no suitable exposed ADC exists
|
||||||
|
|
||||||
---
|
Use lookup-table interpolation.
|
||||||
|
|
||||||
# High Current Loads
|
|
||||||
|
|
||||||
Recommended design:
|
## Cargo OLED service display
|
||||||
|
|
||||||
text ESP32 GPIO │ ▼ Relay Module │ ▼ Automotive Relay │ ▼ Load
|
Planned optional hardware:
|
||||||
|
|
||||||
Examples:
|
- 0.96 inch SSD1306 OLED
|
||||||
|
- 128x64 resolution
|
||||||
|
- I2C interface
|
||||||
|
- Typical address: 0x3C
|
||||||
|
- Suggested initial pins:
|
||||||
|
- SDA: GPIO 21
|
||||||
|
- SCL: GPIO 22
|
||||||
|
- VCC: 3.3V
|
||||||
|
- GND: common ground
|
||||||
|
|
||||||
- Refrigerator
|
Purpose:
|
||||||
|
|
||||||
|
- Show first-boot AP setup credentials without requiring Serial Monitor.
|
||||||
|
- Show AP SSID, password, and IP address during setup/recovery.
|
||||||
|
- Show quick service status:
|
||||||
|
- battery SOC
|
||||||
|
- voltage/current
|
||||||
|
- relay states
|
||||||
|
- WiFi/IP
|
||||||
|
- active alarms
|
||||||
|
|
||||||
|
Firmware support is optional and disabled by default with OLED_ENABLED set to 0. Enable only after the OLED libraries are installed and the display is wired.
|
||||||
|
|
||||||
|
|
||||||
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
|
## Automotive Load-Switching Layer
|
||||||
|
|
||||||
|
Current v1 load-control assumption:
|
||||||
|
|
||||||
|
Cargo ESP32
|
||||||
|
-> control outputs
|
||||||
|
-> automotive load-switching layer
|
||||||
|
-> fused loads
|
||||||
|
|
||||||
|
Expected fused loads:
|
||||||
|
|
||||||
|
- Fridge
|
||||||
- Starlink
|
- Starlink
|
||||||
- Inverter enable
|
- Lights
|
||||||
- Lighting circuits
|
- Spare output
|
||||||
- Water pump
|
|
||||||
- Air compressor
|
|
||||||
|
|
||||||
---
|
The ESP32 relay board is treated as a trigger/control layer only. Major load current must be handled by properly fused automotive wiring, relays, contactors, smart high-side switches, or another suitable fused distribution module.
|
||||||
|
|
||||||
# Automotive Relay Recommendation
|
The exact load-switching hardware is not finalized yet.
|
||||||
|
|
||||||
Recommended relay type:
|
Candidate options:
|
||||||
|
|
||||||
text Bosch-style 30A or 40A relay
|
- Bosch-style automotive relays
|
||||||
|
- WUPP-style fused relay/distribution module
|
||||||
|
- Smart high-side switching
|
||||||
|
- Future custom PCB
|
||||||
|
|
||||||
Each high-current circuit should have:
|
## Selected Dashboard Hardware
|
||||||
|
|
||||||
- Dedicated fuse
|
The selected dashboard hardware target is the Waveshare ESP32-S3 Touch LCD 5B.
|
||||||
- Appropriate wire gauge
|
|
||||||
- Proper grounding
|
|
||||||
- Automotive-rated connectors
|
|
||||||
|
|
||||||
---
|
Key hardware notes:
|
||||||
|
|
||||||
# Power System
|
- 5-inch touchscreen display
|
||||||
|
- 1024x600 resolution
|
||||||
|
- ESP32-S3 based
|
||||||
|
- Capacitive touch
|
||||||
|
- Native WiFi
|
||||||
|
- Integrated CAN transceiver available for future vehicle integration
|
||||||
|
|
||||||
Typical installation:
|
This is the active dashboard platform for the project. The previous Pico dashboard implementation has been archived and is reference-only.
|
||||||
|
|
||||||
text House Battery │ ├── Main Fuse │ ├── ESP32 Controller │ ├── Relay Circuits │ └── Accessories
|
## DS18B20 Temperature Sensors
|
||||||
|
|
||||||
The ESP32 should remain powered from the house battery so monitoring remains active when the vehicle is parked.
|
DS18B20 temperature sensors are already part of the Cargo ESP implementation and WebUI/API flow.
|
||||||
|
|
||||||
---
|
Current expectation:
|
||||||
|
|
||||||
# Communications
|
- DS18B20 sensors connect to the Cargo ESP side.
|
||||||
|
- Cargo ESP owns sensor reads and temperature state.
|
||||||
|
- The WebUI displays the temperature data.
|
||||||
|
- The Dashboard ESP32-S3 will consume temperature data from the Cargo ESP API.
|
||||||
|
|
||||||
Current communications:
|
Do not move cargo/fridge temperature sensing to the dashboard controller unless the architecture is explicitly changed later.
|
||||||
|
|
||||||
## ESP32 ↔ BMS
|
## Ordered Cargo ESP Hardware: LILYGO T-Relay-S3 6-Way
|
||||||
|
|
||||||
text Bluetooth Low Energy
|
The LILYGO T-Relay-S3 6-way board has been ordered as a future Cargo ESP candidate.
|
||||||
|
|
||||||
---
|
Current active testing should continue on the existing 2-channel relay board until the LilyGO board arrives and is bench tested.
|
||||||
|
|
||||||
## ESP32 ↔ Dashboard
|
Planned LilyGO role:
|
||||||
|
|
||||||
Current:
|
LILYGO T-Relay-S3 6-way
|
||||||
|
-> low-current control/relay outputs
|
||||||
|
WUPP 6-relay fused block
|
||||||
|
-> fused load switching
|
||||||
|
Fridge / Starlink / lights / spare / spare / spare
|
||||||
|
|
||||||
text UART
|
Important guardrails:
|
||||||
|
|
||||||
Future:
|
- Do not migrate active firmware assumptions until the board is physically available.
|
||||||
|
- Bench test relay boot behavior before connecting to WUPP or vehicle loads.
|
||||||
text WiFi HTTP API MQTT
|
- Confirm relay polarity/state behavior.
|
||||||
|
- Confirm available GPIO for DS18B20 and any optional OLED/status hardware.
|
||||||
---
|
- Treat the LilyGO board as the controller, not the high-current load path.
|
||||||
|
|
||||||
# WiFi Network
|
|
||||||
|
|
||||||
The ESP32 currently provides:
|
|
||||||
|
|
||||||
text Access Point Mode
|
|
||||||
|
|
||||||
Default address:
|
|
||||||
|
|
||||||
text 192.168.4.1
|
|
||||||
|
|
||||||
The dashboard and user devices connect directly to the controller.
|
|
||||||
|
|
||||||
No internet connection is required.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Future Hardware Support
|
|
||||||
|
|
||||||
Planned additions:
|
|
||||||
|
|
||||||
## Vehicle Telemetry
|
|
||||||
|
|
||||||
Possible interfaces:
|
|
||||||
|
|
||||||
text OBD-II ELM327 CAN Bus
|
|
||||||
|
|
||||||
Potential data:
|
|
||||||
|
|
||||||
- Engine RPM
|
|
||||||
- Coolant temperature
|
|
||||||
- Fuel level
|
|
||||||
- Vehicle speed
|
|
||||||
- Diagnostic trouble codes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GPS
|
|
||||||
|
|
||||||
Potential features:
|
|
||||||
|
|
||||||
- Location tracking
|
|
||||||
- Trip logging
|
|
||||||
- Route history
|
|
||||||
- Speed tracking
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Environmental Sensors
|
|
||||||
|
|
||||||
Potential additions:
|
|
||||||
|
|
||||||
- Humidity
|
|
||||||
- Barometric pressure
|
|
||||||
- Air quality
|
|
||||||
- Water tank monitoring
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Current Bill of Materials
|
|
||||||
|
|
||||||
## Installed
|
|
||||||
|
|
||||||
- ESP32 Controller
|
|
||||||
- Raspberry Pi Pico 2 W
|
|
||||||
- ESP32 Relay Module
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Planned
|
|
||||||
|
|
||||||
- 3.5" Touchscreen
|
|
||||||
- DS18B20 Sensors
|
|
||||||
- Automotive Relays
|
|
||||||
- Fuse Block
|
|
||||||
- Wiring Harness
|
|
||||||
- Enclosures
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Design Goals
|
|
||||||
|
|
||||||
The hardware platform is intended to be:
|
|
||||||
|
|
||||||
- Modular
|
|
||||||
- Vehicle independent
|
|
||||||
- Serviceable
|
|
||||||
- Expandable
|
|
||||||
- Offline capable
|
|
||||||
- Easy to troubleshoot
|
|
||||||
- Suitable for long-term overland use
|
|
||||||
|
|||||||
+15
-10
@@ -12,14 +12,18 @@ 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` |
|
| Health | No | No | Yes | `/api/v1/health` is lightweight HTTP liveness |
|
||||||
| Config view | Yes | Yes | Yes | `/config` equals `config_request` |
|
| Capabilities | No | No | Yes | `/api/v1/capabilities` supports client discovery |
|
||||||
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /relay/set` |
|
| Status | Yes | Yes | Yes | `/api/v1/status` equals `status_request` |
|
||||||
| Device name config | Yes | Yes | Yes | `devicename`, `config_device`, `/config/device` |
|
| Config view | Yes | Yes | Yes | `/api/v1/config` equals `config_request` |
|
||||||
| Relay config | Yes | Yes | Yes | `relayname`, `config_relay`, `/config/relay` |
|
| Config export | No | No | Yes | `/api/v1/config/export` includes WiFi passwords |
|
||||||
| Temperature config | Yes | Yes | Yes | `tempname`, `config_temp`, `/config/temp` |
|
| Config import | No | No | Yes | `/api/v1/config/import` persists restored config |
|
||||||
| BMS config | Yes | Yes | Yes | `bmsname`/`bmsaddr`, `config_bms`, `/config/bms` |
|
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /api/v1/relay/set` |
|
||||||
| Save config | Yes | Yes | Yes | `save`, `save_config`, `/config/save` |
|
| Device name config | Yes | Yes | Yes | `devicename`, `config_device`, `/api/v1/config/device` |
|
||||||
|
| Relay config | Yes | Yes | Yes | `relayname`, `config_relay`, `/api/v1/config/relay` |
|
||||||
|
| Temperature config | Yes | Yes | Yes | `tempname`, `config_temp`, `/api/v1/config/temp` |
|
||||||
|
| BMS config | Yes | Yes | Yes | `bmsname`/`bmsaddr`, `config_bms`, `/api/v1/config/bms` |
|
||||||
|
| 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 +62,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
|
||||||
@@ -107,3 +111,4 @@ Future additions must be implemented on:
|
|||||||
|
|
||||||
before parity is considered maintained.
|
before parity is considered maintained.
|
||||||
|
|
||||||
|
- Temperature sensor config/status parity now includes the `weather` boolean flag for outside-air display selection.
|
||||||
|
|||||||
+23
-4
@@ -12,15 +12,17 @@ This folder contains project documentation for Overland Controller.
|
|||||||
|
|
||||||
## Project Tracking
|
## Project Tracking
|
||||||
|
|
||||||
|
- `CHATGPT_HANDOFF.md` - Concise assistant handoff with current status, goals, constraints, and validation notes
|
||||||
- `project-state.md` - Current project state
|
- `project-state.md` - Current project state
|
||||||
- `roadmap.md` - Roadmap and future plans
|
- `roadmap.md` - Roadmap and future plans
|
||||||
- `hardware-status.md` - Hardware status and bring-up notes
|
|
||||||
- `decisions.md` - Architecture and implementation decisions
|
- `decisions.md` - Architecture and implementation decisions
|
||||||
|
|
||||||
## Technical References
|
## Technical References
|
||||||
|
|
||||||
- `protocol.md` - Dashboard/controller protocol
|
- `dashboard-esp32s3.md` - Current Waveshare ESP32-S3 dashboard plan
|
||||||
- `wiring.md` - Wiring notes and plans
|
- `CONFIGURATION.md` - Configuration model and setup concepts
|
||||||
|
- `TEMP_SETUP.md` - DS18B20 temperature sensor setup
|
||||||
|
- `UART_PROTOCOL.md` - Optional UART diagnostics protocol
|
||||||
|
|
||||||
## Archive
|
## Archive
|
||||||
|
|
||||||
@@ -28,7 +30,24 @@ Older or duplicate documents have been moved to `archive/`.
|
|||||||
|
|
||||||
Archived documents are kept for history, but the primary documents above should be treated as the current source of truth.
|
Archived documents are kept for history, but the primary documents above should be treated as the current source of truth.
|
||||||
|
|
||||||
|
|
||||||
## Parity
|
## Parity
|
||||||
|
|
||||||
- `PARITY_MATRIX.md` - Command parity across USB serial, UART JSON, and HTTP API
|
- `PARITY_MATRIX.md` - Command parity across USB serial, UART JSON, and HTTP API
|
||||||
|
|
||||||
|
- `controller-roles.md` - Cargo ESP vs Dashboard ESP32-S3 responsibility boundaries
|
||||||
|
|
||||||
|
- `../archive/legacy-pico-dashboard/` - Archived Pico dashboard implementation kept for reference only.
|
||||||
|
- `../firmware/esp32-s3-dashboard/` - Current ESP32-S3 dashboard firmware placeholder.
|
||||||
|
|
||||||
|
- `repo-cleanup-audit.md` - Active repo audit for stale Pico/UART dashboard references and DS18B20 documentation status.
|
||||||
|
- `API.md` - High-level API notes and telemetry ownership.
|
||||||
|
|
||||||
|
- `cargo-api-contract.md` - Cargo ESP API ownership and expected API areas for WebUI/dashboard clients.
|
||||||
|
|
||||||
|
- `pre-lilygo-baseline-checklist.md` - Checklist for creating a known-good Cargo ESP 2-channel baseline before future LilyGO migration.
|
||||||
|
|
||||||
|
## LilyGO Transition
|
||||||
|
|
||||||
|
The LILYGO T-Relay-S3 6-way board has been ordered as a future Cargo ESP candidate.
|
||||||
|
|
||||||
|
Current development remains on the 2-channel relay board while the API and docs are prepared for output-count-agnostic operation.
|
||||||
|
|||||||
+46
-21
@@ -1,8 +1,10 @@
|
|||||||
# UART Protocol
|
# UART Protocol
|
||||||
|
|
||||||
The Pico dashboard communicates with the ESP32 controller over UART.
|
This document describes the optional UART JSON protocol supported by the ESP32 controller.
|
||||||
|
|
||||||
UART is the primary dashboard/control link. HTTP should mirror the same concepts, but the Pico should be able to operate the controller without WiFi.
|
UART is not the primary dashboard/control link. The current dashboard target is the Waveshare ESP32-S3 display over WiFi/HTTP.
|
||||||
|
|
||||||
|
Keep this protocol for diagnostics, simulator work, service tools, or alternate clients. Do not treat it as evidence that a Pico dashboard is planned.
|
||||||
|
|
||||||
## Serial Settings
|
## Serial Settings
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ Temperature sensors:
|
|||||||
|
|
||||||
## Status Request
|
## Status Request
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"status_request"}
|
{"type":"status_request"}
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ ESP32 responds with the same shape as HTTP /status.
|
|||||||
|
|
||||||
## Relay Control
|
## Relay Control
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"set_relay","relay":"relay_1","enabled":true}
|
{"type":"set_relay","relay":"relay_1","enabled":true}
|
||||||
|
|
||||||
@@ -59,7 +61,7 @@ ESP32 responds:
|
|||||||
|
|
||||||
## Config Request
|
## Config Request
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_request"}
|
{"type":"config_request"}
|
||||||
|
|
||||||
@@ -69,37 +71,37 @@ ESP32 responds:
|
|||||||
|
|
||||||
## Device Name Update
|
## Device Name Update
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_device","device_name":"Overland Controller"}
|
{"type":"config_device","device_name":"Overland Controller"}
|
||||||
|
|
||||||
## Relay Config Update
|
## Relay Config Update
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_relay","id":"relay_1","name":"Aux Power","enabled":true}
|
{"type":"config_relay","id":"relay_1","name":"Aux Power","enabled":true}
|
||||||
|
|
||||||
## Temperature Sensor Config Update
|
## Temperature Sensor Config Update
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_temp","id":"temp_1","name":"Cabin","address":"","enabled":true}
|
{"type":"config_temp","id":"temp_1","name":"Cabin","address":"","enabled":true}
|
||||||
|
|
||||||
## BMS Config Update
|
## BMS Config Update
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_bms","enabled":true,"name":"House Battery","address":"aa:bb:cc:dd:ee:ff","address_type":"public"}
|
{"type":"config_bms","enabled":true,"name":"House Battery","address":"aa:bb:cc:dd:ee:ff","address_type":"public"}
|
||||||
|
|
||||||
## Save Config
|
## Save Config
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"save_config"}
|
{"type":"save_config"}
|
||||||
|
|
||||||
## Factory Reset
|
## Factory Reset
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"factory_reset"}
|
{"type":"factory_reset"}
|
||||||
|
|
||||||
@@ -156,14 +158,14 @@ The UART protocol should eventually support the same management capabilities as
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- UART is the preferred dashboard/control path.
|
- HTTP is the preferred dashboard/control path.
|
||||||
- HTTP is useful for setup, debugging, and future web UI.
|
- UART is useful for diagnostics and alternate clients.
|
||||||
- USB serial remains the emergency service/debug interface.
|
- USB serial remains the emergency service/debug interface.
|
||||||
- The Pico should not depend on WiFi for normal control.
|
- No Pico dashboard is currently planned.
|
||||||
|
|
||||||
## WiFi Config Request
|
## WiFi Config Request
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"wifi_request"}
|
{"type":"wifi_request"}
|
||||||
|
|
||||||
@@ -173,19 +175,19 @@ ESP32 responds:
|
|||||||
|
|
||||||
## WiFi Config Update
|
## WiFi Config Update
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"config_wifi","ssid":"Starlink","password":"password_here"}
|
{"type":"config_wifi","ssid":"Starlink","password":"password_here"}
|
||||||
|
|
||||||
## WiFi Connect
|
## WiFi Connect
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"wifi_connect"}
|
{"type":"wifi_connect"}
|
||||||
|
|
||||||
## WiFi Clear
|
## WiFi Clear
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"wifi_clear"}
|
{"type":"wifi_clear"}
|
||||||
|
|
||||||
@@ -239,7 +241,7 @@ Lower priority numbers are tried first.
|
|||||||
|
|
||||||
## Scan Temperature Sensors
|
## Scan Temperature Sensors
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"scan_temps"}
|
{"type":"scan_temps"}
|
||||||
|
|
||||||
@@ -249,7 +251,7 @@ ESP32 responds:
|
|||||||
|
|
||||||
## Assign Temperature Sensor
|
## Assign Temperature Sensor
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"assign_temp","id":"temp_1","index":1}
|
{"type":"assign_temp","id":"temp_1","index":1}
|
||||||
|
|
||||||
@@ -259,10 +261,33 @@ Alternative:
|
|||||||
|
|
||||||
## Clear Temperature Sensor
|
## Clear Temperature Sensor
|
||||||
|
|
||||||
Pico sends:
|
Client sends:
|
||||||
|
|
||||||
{"type":"clear_temp","id":"temp_1"}
|
{"type":"clear_temp","id":"temp_1"}
|
||||||
|
|
||||||
Alternative:
|
Alternative:
|
||||||
|
|
||||||
{"type":"clear_temp","slot":1}
|
{"type":"clear_temp","slot":1}
|
||||||
|
|
||||||
|
### Temperature weather flag
|
||||||
|
|
||||||
|
`status_response.config.temperature_sensors[]` and `status_response.temps[]` include `weather: false`.
|
||||||
|
|
||||||
|
`config_temp` may include `weather: true` to mark one configured sensor as the outside/weather sensor. Clients should treat this as optional and default missing values to `false`.
|
||||||
|
|
||||||
|
|
||||||
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Xterra Overland Dashboard - Codex Handoff
|
# Overland Controller - Codex Handoff
|
||||||
|
|
||||||
## Repository
|
## Repository
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ Before making any code changes:
|
|||||||
|
|
||||||
# Project Overview
|
# Project Overview
|
||||||
|
|
||||||
The project is an overland vehicle monitoring and control system for a Nissan Xterra.
|
The project is an overland vehicle monitoring and control system for a overland vehicle.
|
||||||
|
|
||||||
Architecture:
|
Architecture:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Legacy Current-Cleanup Archive
|
||||||
|
|
||||||
|
These files were moved out of the active docs during the June 2026 documentation cleanup.
|
||||||
|
|
||||||
|
They are retained for historical reference only. Current source-of-truth docs are:
|
||||||
|
|
||||||
|
- `docs/ARCHITECTURE.md`
|
||||||
|
- `docs/HARDWARE.md`
|
||||||
|
- `docs/ROADMAP.md`
|
||||||
|
- `docs/API.md`
|
||||||
|
- `docs/dashboard-esp32s3.md`
|
||||||
|
|
||||||
|
Do not use the archived Pico/UART wiring or handoff notes for current implementation work.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# Overland Controller Handoff
|
||||||
|
|
||||||
|
This handoff is stale and may include legacy Pico/UART dashboard assumptions.
|
||||||
|
|
||||||
|
Current source of truth:
|
||||||
|
|
||||||
|
- `docs/ARCHITECTURE.md`
|
||||||
|
- `docs/HARDWARE.md`
|
||||||
|
- `docs/ROADMAP.md`
|
||||||
|
- `docs/API.md`
|
||||||
|
- `docs/dashboard-esp32s3.md`
|
||||||
|
|
||||||
|
The current dashboard target is the Waveshare ESP32-S3-Touch-LCD-5 over WiFi/HTTP. No Pico dashboard is planned.
|
||||||
|
|
||||||
|
## Current Version
|
||||||
|
|
||||||
|
Firmware: 0.4.0
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
The ESP32 backend is now the source of truth and is mostly Phase 1 complete.
|
||||||
|
|
||||||
|
Working:
|
||||||
|
|
||||||
|
- ESP32 WebUI
|
||||||
|
- AP + STA WiFi
|
||||||
|
- Multiple saved WiFi networks with priority
|
||||||
|
- Relay control through built-in relay board
|
||||||
|
- JBD/Xiaoxiang BLE BMS telemetry
|
||||||
|
- DS18B20 temperature support
|
||||||
|
- Temperature scan/assign/config workflow
|
||||||
|
- HTTP API
|
||||||
|
- UART JSON protocol
|
||||||
|
- USB serial maintenance commands
|
||||||
|
- Config-driven relay/temp/BMS/device naming
|
||||||
|
- WebUI Config tab for initial setup
|
||||||
|
|
||||||
|
## Important Recent Fix
|
||||||
|
|
||||||
|
Do not use `client->setConnectTimeout()` for the JBD/Xiaoxiang BMS.
|
||||||
|
|
||||||
|
It caused Greta/BMS BLE connection failures.
|
||||||
|
|
||||||
|
Known-good BMS behavior uses the default NimBLE connection timeout.
|
||||||
|
|
||||||
|
## Current Hardware Direction
|
||||||
|
|
||||||
|
Rear enclosure:
|
||||||
|
|
||||||
|
- ESP32 2-relay board
|
||||||
|
- Mini fuse block
|
||||||
|
- Ground bus
|
||||||
|
- DS18B20 distribution board
|
||||||
|
- CAT6 keystone
|
||||||
|
- Built-in relays used for Starlink Mini and fridge
|
||||||
|
- Positive-side relay switching
|
||||||
|
|
||||||
|
Dashboard enclosure:
|
||||||
|
|
||||||
|
- Raspberry Pi Pico 2 W / RP2350
|
||||||
|
- 3.5" 320x480 ST7796S display
|
||||||
|
- FT6336U capacitive touch, wired but not required for MVP
|
||||||
|
- 12V over CAT5e from rear enclosure
|
||||||
|
- Local 12V-to-5V buck converter in dash enclosure
|
||||||
|
- Pico and display powered from dash buck converter
|
||||||
|
|
||||||
|
## CAT5e Plan
|
||||||
|
|
||||||
|
Rear to dash:
|
||||||
|
|
||||||
|
- Two conductors for fused +12V
|
||||||
|
- Two conductors for ground
|
||||||
|
- One conductor for ESP32 TX to Pico RX
|
||||||
|
- One conductor for ESP32 RX to Pico TX
|
||||||
|
- Spare pair reserved
|
||||||
|
|
||||||
|
All grounds are common:
|
||||||
|
|
||||||
|
- ESP32 GND
|
||||||
|
- Buck GND
|
||||||
|
- Pico GND
|
||||||
|
- Display GND
|
||||||
|
- UART GND
|
||||||
|
|
||||||
|
## Pico Display Solder Map
|
||||||
|
|
||||||
|
Display:
|
||||||
|
|
||||||
|
- VCC -> 5V buck output
|
||||||
|
- GND -> GND
|
||||||
|
- SCLK -> Pico GPIO18 / Pin 24
|
||||||
|
- MOSI -> Pico GPIO19 / Pin 25
|
||||||
|
- LCD_CS -> Pico GPIO17 / Pin 22
|
||||||
|
- LCD_DC -> Pico GPIO16 / Pin 21
|
||||||
|
- LCD_RST -> Pico GPIO20 / Pin 26
|
||||||
|
- LCD_BL -> Pico GPIO21 / Pin 27
|
||||||
|
|
||||||
|
Touch, wired for future:
|
||||||
|
|
||||||
|
- TP_SDA -> Pico GPIO26 / Pin 31
|
||||||
|
- TP_SCL -> Pico GPIO27 / Pin 32
|
||||||
|
- TP_INT -> Pico GPIO28 / Pin 34
|
||||||
|
- TP_RST -> Pico GPIO15 / Pin 20
|
||||||
|
|
||||||
|
UART to ESP32 dashboard UART:
|
||||||
|
|
||||||
|
- Pico GPIO0 / Pin 1 RX <- ESP32 GPIO21 TX
|
||||||
|
- Pico GPIO1 / Pin 2 TX -> ESP32 GPIO22 RX
|
||||||
|
- Pico GND -> ESP32 GND
|
||||||
|
|
||||||
|
Do not use the ESP32 6-pin UART header for dashboard UART. It has continuity to RXD and is likely UART0/programming/debug.
|
||||||
|
|
||||||
|
## Pico MVP Goal
|
||||||
|
|
||||||
|
MVP means Minimum Viable Product.
|
||||||
|
|
||||||
|
Pico MVP should be read-only first:
|
||||||
|
|
||||||
|
- Request status from ESP32 over UART
|
||||||
|
- Parse JSON status response
|
||||||
|
- Display battery SOC, voltage, current, runtime/time-to-full
|
||||||
|
- Display temps
|
||||||
|
- Display relay states
|
||||||
|
- Display ESP32 connection status
|
||||||
|
|
||||||
|
No touch controls for MVP.
|
||||||
|
|
||||||
|
## Runtime / Time-To-Full Logic
|
||||||
|
|
||||||
|
If BMS current is negative/discharging:
|
||||||
|
|
||||||
|
runtime_hours = remaining_ah / abs(current)
|
||||||
|
|
||||||
|
If BMS current is positive/charging:
|
||||||
|
|
||||||
|
time_to_full_hours = (capacity_ah - remaining_ah) / current
|
||||||
|
|
||||||
|
If current is near zero:
|
||||||
|
|
||||||
|
show Idle
|
||||||
|
|
||||||
|
## Next Work
|
||||||
|
|
||||||
|
1. Build Pico MVP.
|
||||||
|
2. First prove UART status over USB serial.
|
||||||
|
3. Then prove display Hello World.
|
||||||
|
4. Then draw read-only dashboard.
|
||||||
|
5. Later add touch navigation and relay controls.
|
||||||
|
6. Later add config backup/restore.
|
||||||
|
7. Later improve BMS out-of-range behavior without changing NimBLE connect timeout.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Dashboard Direction
|
||||||
|
|
||||||
|
Current dashboard target:
|
||||||
|
|
||||||
|
- Waveshare 5" ESP32-S3 Display
|
||||||
|
- WiFi client
|
||||||
|
- Connects to cargo ESP32 AP
|
||||||
|
- Uses existing HTTP API
|
||||||
|
- Planned LVGL implementation
|
||||||
|
|
||||||
|
Important:
|
||||||
|
|
||||||
|
- ESP32 cargo controller remains source of truth.
|
||||||
|
- Dashboard is a client only.
|
||||||
|
- Existing UART protocol remains supported but is no longer the primary dashboard transport.
|
||||||
|
|
||||||
|
See `docs/dashboard-esp32s3.md` for the current Waveshare ESP32-S3 dashboard plan.
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Last Updated: 2026-06-03
|
Last Updated: 2026-06-03
|
||||||
|
|
||||||
|
This document is stale and records earlier hardware ordering/status notes. Current hardware direction is documented in `docs/HARDWARE.md` and `docs/dashboard-esp32s3.md`.
|
||||||
|
|
||||||
|
The current dashboard target is the Waveshare ESP32-S3-Touch-LCD-5 over WiFi/HTTP. No Pico dashboard is planned.
|
||||||
|
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
## Dashboard Hardware
|
## Dashboard Hardware
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# ESP32 ↔ Pico Communication Protocol
|
# Legacy UART Communication Protocol
|
||||||
|
|
||||||
## Current Decision
|
## Current Decision
|
||||||
|
|
||||||
Primary communication is **UART over CAT5**.
|
Primary dashboard communication is **WiFi HTTP API** between the Waveshare ESP32-S3 dashboard and the Cargo ESP32.
|
||||||
|
|
||||||
Backup communication is the **ESP32 WiFi HTTP API**.
|
This legacy UART protocol is retained for historical reference, diagnostics, simulator work, or alternate clients. It is not the current dashboard architecture.
|
||||||
|
|
||||||
RS-485/MAX3485 is fallback-only and is not currently planned.
|
RS-485/MAX3485 is fallback-only and is not currently planned.
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ RS-485/MAX3485 is fallback-only and is not currently planned.
|
|||||||
* Baud: 115200
|
* Baud: 115200
|
||||||
* Format: 8N1
|
* Format: 8N1
|
||||||
* Framing: newline-delimited JSON
|
* Framing: newline-delimited JSON
|
||||||
* Direction: Pico dashboard ↔ ESP32 cargo controller
|
* Direction: UART client ↔ ESP32 cargo controller
|
||||||
|
|
||||||
## Message Rules
|
## Message Rules
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ Example:
|
|||||||
|
|
||||||
Responses should also be one JSON object followed by a newline.
|
Responses should also be one JSON object followed by a newline.
|
||||||
|
|
||||||
## Pico → ESP32 Messages
|
## Client → ESP32 Messages
|
||||||
|
|
||||||
### Status Request
|
### Status Request
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ Expected response:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## ESP32 → Pico Messages
|
## ESP32 → Client Messages
|
||||||
|
|
||||||
### Status Response
|
### Status Response
|
||||||
|
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
# Wiring
|
# Wiring
|
||||||
|
|
||||||
This document describes the current planned wiring for the Overland Controller install.
|
This document contains older wiring notes and may include legacy Pico/UART dashboard assumptions.
|
||||||
|
|
||||||
The current system uses:
|
Current dashboard and hardware source of truth:
|
||||||
|
|
||||||
- ESP32 2-relay controller board
|
- `docs/HARDWARE.md`
|
||||||
- Raspberry Pi Pico 2 W dashboard
|
- `docs/dashboard-esp32s3.md`
|
||||||
- CAT5/CAT6 cable between rear controller and dash
|
|
||||||
- 12V-to-5V buck converter in the dash enclosure
|
Do not use Pico-specific wiring in this file for the current dashboard build.
|
||||||
|
|
||||||
|
Current planned system uses:
|
||||||
|
|
||||||
|
- Cargo ESP32 2-relay controller board
|
||||||
|
- Waveshare ESP32-S3-Touch-LCD-5 dashboard over WiFi/HTTP
|
||||||
|
- ACC switched 12V fused dashboard power into Waveshare VIN
|
||||||
- DS18B20 temperature sensors
|
- DS18B20 temperature sensors
|
||||||
- JBD/Xiaoxiang BMS over BLE
|
- JBD/Xiaoxiang BMS over BLE
|
||||||
- External automotive relays for high-current loads
|
- External fused automotive relays or contactors for major loads
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -592,3 +598,66 @@ Only three wires need to return to the ESP32:
|
|||||||
3.3V
|
3.3V
|
||||||
GPIO4
|
GPIO4
|
||||||
GND
|
GND
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pico Dashboard Wiring
|
||||||
|
|
||||||
|
The Pico dashboard should be powered from a local 12V-to-5V buck converter in the dash enclosure.
|
||||||
|
|
||||||
|
Rear enclosure sends fused 12V over CAT5e.
|
||||||
|
|
||||||
|
Dash side:
|
||||||
|
|
||||||
|
CAT5e +12V -> buck IN+
|
||||||
|
CAT5e GND -> buck IN-
|
||||||
|
|
||||||
|
buck OUT+ 5V -> Pico VSYS
|
||||||
|
buck OUT+ 5V -> Display VCC
|
||||||
|
|
||||||
|
buck OUT- GND -> Pico GND
|
||||||
|
buck OUT- GND -> Display GND
|
||||||
|
|
||||||
|
Display wiring:
|
||||||
|
|
||||||
|
SCLK -> Pico GPIO18
|
||||||
|
MOSI -> Pico GPIO19
|
||||||
|
LCD_CS -> Pico GPIO17
|
||||||
|
LCD_DC -> Pico GPIO16
|
||||||
|
LCD_RST -> Pico GPIO20
|
||||||
|
LCD_BL -> Pico GPIO21
|
||||||
|
|
||||||
|
Touch wiring, reserved for future:
|
||||||
|
|
||||||
|
TP_SDA -> Pico GPIO26
|
||||||
|
TP_SCL -> Pico GPIO27
|
||||||
|
TP_INT -> Pico GPIO28
|
||||||
|
TP_RST -> Pico GPIO15
|
||||||
|
|
||||||
|
UART wiring:
|
||||||
|
|
||||||
|
ESP32 GPIO21 TX -> Pico GPIO0 RX
|
||||||
|
ESP32 GPIO22 RX -> Pico GPIO1 TX
|
||||||
|
ESP32 GND -> Pico GND
|
||||||
|
|
||||||
|
Do not use the ESP32 6-pin UART header for the dashboard UART. It appears to be connected to RXD/TXD for UART0/programming/debug.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Dashboard Wiring (Current Direction)
|
||||||
|
|
||||||
|
Dashboard Hardware:
|
||||||
|
- Waveshare 5" ESP32-S3 Display
|
||||||
|
|
||||||
|
Communications:
|
||||||
|
- WiFi connection to cargo ESP32 AP
|
||||||
|
- HTTP API polling
|
||||||
|
- No UART required for normal operation
|
||||||
|
|
||||||
|
CAT5e Usage:
|
||||||
|
- 12V power from cargo area to dashboard
|
||||||
|
- Local buck converter in dashboard enclosure
|
||||||
|
- Common ground
|
||||||
|
|
||||||
|
Optional Future:
|
||||||
|
- UART over CAT5e for diagnostics or alternate dashboard hardware
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
The Raspberry Pi Pico 2 W dashboard is the primary user interface for the Xterra Overland Power & Monitoring Dashboard.
|
The Raspberry Pi Pico 2 W dashboard is the primary user interface for the Overland Controller dashboard.
|
||||||
|
|
||||||
Its responsibilities are:
|
Its responsibilities are:
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
The first hardware version will use UART over CAT5 between the Pico dashboard and the ESP32 controller.
|
The first hardware version will use UART over CAT5 between the Pico dashboard and the ESP32 controller.
|
||||||
|
|
||||||
UART is simpler than RS-485 and should be reliable for the short cable run inside the Xterra.
|
UART is simpler than RS-485 and should be reliable for the short cable run inside the vehicle.
|
||||||
|
|
||||||
RS-485 remains a future fallback if testing reveals communication issues.
|
RS-485 remains a future fallback if testing reveals communication issues.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Cargo ESP API Contract
|
||||||
|
|
||||||
|
The Cargo ESP32 is the source of truth for power-system state.
|
||||||
|
|
||||||
|
The WebUI, ESP32-S3 dashboard, and future clients should consume this API rather than reading sensors or relay state directly.
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
Cargo ESP32 owns:
|
||||||
|
|
||||||
|
- Relay/output state
|
||||||
|
- DS18B20 temperature readings
|
||||||
|
- BMS telemetry
|
||||||
|
- Alarm/fault state
|
||||||
|
- Local configuration
|
||||||
|
- WebUI/API service
|
||||||
|
|
||||||
|
Dashboard ESP32-S3 consumes:
|
||||||
|
|
||||||
|
- Status
|
||||||
|
- Battery/BMS telemetry
|
||||||
|
- Temperature telemetry
|
||||||
|
- Relay/output state
|
||||||
|
- Alarm/fault state
|
||||||
|
|
||||||
|
The dashboard may request relay changes through the API, but the Cargo ESP remains authoritative.
|
||||||
|
|
||||||
|
## Expected API Areas
|
||||||
|
|
||||||
|
### System Status
|
||||||
|
|
||||||
|
Used by clients to determine whether the Cargo ESP is online and healthy.
|
||||||
|
|
||||||
|
Expected data:
|
||||||
|
|
||||||
|
- Device name
|
||||||
|
- Firmware version
|
||||||
|
- Uptime
|
||||||
|
- WiFi/AP status
|
||||||
|
- API version
|
||||||
|
- Alarm/fault summary
|
||||||
|
|
||||||
|
### Battery / BMS
|
||||||
|
|
||||||
|
Used for house battery display.
|
||||||
|
|
||||||
|
Expected data:
|
||||||
|
|
||||||
|
- Voltage
|
||||||
|
- Current
|
||||||
|
- State of charge
|
||||||
|
- Charging/discharging state
|
||||||
|
- Cell data when available
|
||||||
|
- BMS online/offline status
|
||||||
|
- Protection/fault state
|
||||||
|
|
||||||
|
### Temperatures
|
||||||
|
|
||||||
|
DS18B20 support is already implemented in the Cargo ESP/WebUI/API path.
|
||||||
|
|
||||||
|
Expected data:
|
||||||
|
|
||||||
|
- Sensor ID
|
||||||
|
- Display name
|
||||||
|
- Enabled state
|
||||||
|
- Online/offline state
|
||||||
|
- Temperature value
|
||||||
|
- Temperature unit
|
||||||
|
- Role/location, such as fridge, cargo, ambient, or spare
|
||||||
|
|
||||||
|
The ESP32-S3 dashboard should display this data from the Cargo ESP API. It should not directly own cargo/fridge DS18B20 sensor wiring for v1.
|
||||||
|
|
||||||
|
### Relays / Outputs
|
||||||
|
|
||||||
|
Used for fridge, Starlink, lights, spare, and future switched outputs.
|
||||||
|
|
||||||
|
Expected data:
|
||||||
|
|
||||||
|
- Output ID
|
||||||
|
- Display name
|
||||||
|
- Enabled state
|
||||||
|
- Current state
|
||||||
|
- Requested state
|
||||||
|
- Fault/lockout state when available
|
||||||
|
- Optional output type or role
|
||||||
|
|
||||||
|
Relay/output writes should go through the Cargo ESP API.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Configuration should remain owned by the Cargo ESP.
|
||||||
|
|
||||||
|
Expected future areas:
|
||||||
|
|
||||||
|
- Output names
|
||||||
|
- Output enabled/disabled state
|
||||||
|
- Temperature sensor names
|
||||||
|
- Temperature sensor roles
|
||||||
|
- Alarm thresholds
|
||||||
|
- Network/AP settings
|
||||||
|
- OTA settings
|
||||||
|
|
||||||
|
## v1 Client Communication
|
||||||
|
|
||||||
|
Current v1 client path:
|
||||||
|
|
||||||
|
ESP32-S3 Dashboard
|
||||||
|
<-> WiFi / HTTP REST
|
||||||
|
Cargo ESP32 AP
|
||||||
|
|
||||||
|
Not v1 API transport:
|
||||||
|
|
||||||
|
- ESP-NOW
|
||||||
|
- MQTT
|
||||||
|
- UART dashboard link
|
||||||
|
- CAN between controllers
|
||||||
|
|
||||||
|
## Compatibility Rule
|
||||||
|
|
||||||
|
When possible, future changes should add fields rather than rename or remove existing fields.
|
||||||
|
|
||||||
|
Dashboard firmware should tolerate unknown fields.
|
||||||
|
|
||||||
|
Cargo ESP firmware should preserve stable field names once dashboard implementation begins.
|
||||||
|
|
||||||
|
## Output Hardware Profiles
|
||||||
|
|
||||||
|
The Cargo ESP API should describe outputs generically instead of assuming a fixed relay board.
|
||||||
|
|
||||||
|
Current active hardware:
|
||||||
|
|
||||||
|
- Generic ESP32 with 2-channel relay board
|
||||||
|
- Used for ongoing development and testing
|
||||||
|
|
||||||
|
Ordered/future hardware:
|
||||||
|
|
||||||
|
- LILYGO T-Relay-S3 6-way
|
||||||
|
- Intended to trigger the 6 relays on the WUPP fuse/relay block
|
||||||
|
- Not active firmware target until hardware arrives and is bench tested
|
||||||
|
|
||||||
|
API clients should not assume exactly 2 outputs or exactly 6 outputs.
|
||||||
|
|
||||||
|
Instead, clients should read the output list exposed by the Cargo ESP and render whatever outputs are available.
|
||||||
|
|
||||||
|
Expected output object fields:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `name`
|
||||||
|
- `role`
|
||||||
|
- `enabled`
|
||||||
|
- `state`
|
||||||
|
- `requested_state`
|
||||||
|
- `available`
|
||||||
|
- `hardware_channel`
|
||||||
|
- `hardware_profile`
|
||||||
|
|
||||||
|
Example 2-channel profile:
|
||||||
|
|
||||||
|
{
|
||||||
|
"hardware_profile": "generic_esp32_2ch_relay",
|
||||||
|
"outputs": [
|
||||||
|
{"id": 1, "name": "Fridge", "role": "fridge", "hardware_channel": 1},
|
||||||
|
{"id": 2, "name": "Starlink", "role": "starlink", "hardware_channel": 2}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Example future 6-channel profile:
|
||||||
|
|
||||||
|
{
|
||||||
|
"hardware_profile": "lilygo_t_relay_s3_6ch_wupp",
|
||||||
|
"outputs": [
|
||||||
|
{"id": 1, "name": "Fridge", "role": "fridge", "hardware_channel": 1},
|
||||||
|
{"id": 2, "name": "Starlink", "role": "starlink", "hardware_channel": 2},
|
||||||
|
{"id": 3, "name": "Lights", "role": "lights", "hardware_channel": 3},
|
||||||
|
{"id": 4, "name": "Spare 1", "role": "spare", "hardware_channel": 4},
|
||||||
|
{"id": 5, "name": "Spare 2", "role": "spare", "hardware_channel": 5},
|
||||||
|
{"id": 6, "name": "Spare 3", "role": "spare", "hardware_channel": 6}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Compatibility rule:
|
||||||
|
|
||||||
|
- The API should support the current 2-channel board while allowing a future 6-output profile without changing dashboard/client assumptions.
|
||||||
|
- Clients should render outputs dynamically from the API response.
|
||||||
|
- Clients should not hardcode relay count.
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# Controller Roles
|
||||||
|
|
||||||
|
This document separates committed v1 architecture from future wishlist items.
|
||||||
|
|
||||||
|
## Cargo ESP32 Controller
|
||||||
|
|
||||||
|
The Cargo ESP32 is the power-system controller and source of truth.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- WiFi AP
|
||||||
|
- Embedded WebUI
|
||||||
|
- REST/HTTP API
|
||||||
|
- Relay/output control
|
||||||
|
- Automotive load-switching control outputs
|
||||||
|
- JBD/Xiaoxiang BLE BMS telemetry
|
||||||
|
- DS18B20 temperature sensors
|
||||||
|
- Optional SSD1306 OLED service/status display
|
||||||
|
- Local configuration storage
|
||||||
|
- Alarm and fault state
|
||||||
|
|
||||||
|
Does not do:
|
||||||
|
|
||||||
|
- CAN sniffing
|
||||||
|
- OBD-II polling
|
||||||
|
- Vehicle data ownership
|
||||||
|
- Tilt/roll sensing
|
||||||
|
- Dashboard rendering
|
||||||
|
- Gauge cluster rendering
|
||||||
|
|
||||||
|
## Dashboard ESP32-S3
|
||||||
|
|
||||||
|
The Dashboard ESP32-S3 is a touchscreen client and future vehicle-visualization node.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
- Touchscreen UI
|
||||||
|
- LVGL dashboard rendering
|
||||||
|
- Vehicle gauges
|
||||||
|
- CAN bus access
|
||||||
|
- OBD-II polling
|
||||||
|
- Future VIN decoding
|
||||||
|
- Future fault-code display
|
||||||
|
- Future transmission temperature display
|
||||||
|
- Future tilt/roll sensor display
|
||||||
|
- Future off-road screen
|
||||||
|
- Vehicle status visualization
|
||||||
|
|
||||||
|
The dashboard must not own relay state, BMS state, alarm authority, or persistent cargo-controller configuration.
|
||||||
|
|
||||||
|
## v1 Communications
|
||||||
|
|
||||||
|
Current planned v1 path:
|
||||||
|
|
||||||
|
Dashboard ESP32-S3
|
||||||
|
<-> WiFi / HTTP REST
|
||||||
|
Cargo ESP32 AP
|
||||||
|
<-> control outputs
|
||||||
|
Automotive load-switching layer
|
||||||
|
<-> fused loads
|
||||||
|
Fridge / Starlink / lights / spare outputs
|
||||||
|
|
||||||
|
Not planned for v1 controller-to-dashboard communication:
|
||||||
|
|
||||||
|
- ESP-NOW
|
||||||
|
- MQTT
|
||||||
|
- CAN between controllers
|
||||||
|
- UART between dashboard and Cargo ESP
|
||||||
|
|
||||||
|
MQTT may be considered later for optional Home Assistant, Grafana, InfluxDB, or home-server integrations, but it is not the v1 dashboard link.
|
||||||
|
|
||||||
|
## Power Distribution
|
||||||
|
|
||||||
|
Current v1 assumption:
|
||||||
|
|
||||||
|
Cargo ESP32 control outputs
|
||||||
|
↓
|
||||||
|
Automotive load-switching layer
|
||||||
|
↓
|
||||||
|
Properly fused loads:
|
||||||
|
- Fridge
|
||||||
|
- Starlink
|
||||||
|
- Lights
|
||||||
|
- Spare
|
||||||
|
|
||||||
|
The ESP32 relay board must not directly carry fridge, Starlink, inverter, compressor, or lighting load current.
|
||||||
|
|
||||||
|
The exact load-switching hardware is not finalized yet. Candidate implementation options include:
|
||||||
|
|
||||||
|
- Bosch-style automotive relays
|
||||||
|
- WUPP-style fused relay/distribution module
|
||||||
|
- Smart high-side switching
|
||||||
|
- Future custom PCB
|
||||||
|
|
||||||
|
## Hardware Wishlist / Future
|
||||||
|
|
||||||
|
Keep these separate from committed architecture until explicitly promoted:
|
||||||
|
|
||||||
|
- BNO085/BNO086 tilt sensor
|
||||||
|
- CAN sniffing
|
||||||
|
- Fault-code support
|
||||||
|
- Standalone dashboard mode
|
||||||
|
- Additional outputs
|
||||||
|
- Custom PCB
|
||||||
|
- Smart high-side switching
|
||||||
|
- GPS
|
||||||
|
- microSD logging
|
||||||
|
- Home Assistant / MQTT / Grafana / InfluxDB
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
# ESP32-S3 Dashboard Plan
|
||||||
|
|
||||||
|
## Direction
|
||||||
|
|
||||||
|
The primary dashboard target is now a Waveshare 5 inch ESP32-S3 display.
|
||||||
|
|
||||||
|
The cargo ESP32 remains the source of truth. The dashboard is a WiFi client that connects to the cargo ESP32 AP and consumes the existing HTTP API.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Cargo ESP32 Controller
|
||||||
|
- AP / STA WiFi
|
||||||
|
- HTTP API
|
||||||
|
- WebUI
|
||||||
|
- Relays
|
||||||
|
- JBD/Xiaoxiang BMS
|
||||||
|
- DS18B20 sensors
|
||||||
|
|
||||||
|
Waveshare ESP32-S3 Dashboard
|
||||||
|
- Connects to cargo ESP32 AP
|
||||||
|
- Polls /status
|
||||||
|
- Renders LVGL dashboard
|
||||||
|
- Sends relay commands over HTTP
|
||||||
|
|
||||||
|
## Transport
|
||||||
|
|
||||||
|
Primary transport:
|
||||||
|
- WiFi
|
||||||
|
- HTTP JSON API
|
||||||
|
|
||||||
|
Not required for normal dashboard operation:
|
||||||
|
- MicroPython display drivers
|
||||||
|
- CAT5e UART data pair
|
||||||
|
|
||||||
|
UART JSON remains useful for diagnostics, alternate displays, or debug clients, but it is not part of the planned dashboard path.
|
||||||
|
|
||||||
|
## Power
|
||||||
|
|
||||||
|
Dashboard power path:
|
||||||
|
|
||||||
|
ACC switched 12V
|
||||||
|
-> fuse
|
||||||
|
-> Waveshare VIN
|
||||||
|
|
||||||
|
All grounds remain common.
|
||||||
|
|
||||||
|
## API endpoints consumed
|
||||||
|
|
||||||
|
Minimum dashboard MVP:
|
||||||
|
- GET /api/v1/health
|
||||||
|
- GET /api/v1/status
|
||||||
|
- POST /api/v1/relay/set
|
||||||
|
|
||||||
|
Potential later use:
|
||||||
|
- GET /api/v1/capabilities
|
||||||
|
- GET /api/v1/config
|
||||||
|
- GET /api/v1/config/export
|
||||||
|
- POST /api/v1/config/import
|
||||||
|
- POST /api/v1/config/save
|
||||||
|
- POST /api/v1/temps/scan
|
||||||
|
- POST /api/v1/temps/assign
|
||||||
|
- POST /api/v1/temps/clear
|
||||||
|
|
||||||
|
## MVP Display Goals
|
||||||
|
|
||||||
|
Read-only first:
|
||||||
|
- ESP32 connection status
|
||||||
|
- Battery SOC
|
||||||
|
- Voltage
|
||||||
|
- Current
|
||||||
|
- Runtime / time-to-full
|
||||||
|
- BMS state
|
||||||
|
- Temperature sensors
|
||||||
|
- Weather/outside temp badge
|
||||||
|
- Relay states
|
||||||
|
|
||||||
|
Then interactive:
|
||||||
|
- Relay ON/OFF buttons
|
||||||
|
- Alarm/details pages
|
||||||
|
- Settings/status pages
|
||||||
|
|
||||||
|
## Suggested LVGL screens
|
||||||
|
|
||||||
|
### Main Dashboard
|
||||||
|
- SOC gauge
|
||||||
|
- Voltage/current
|
||||||
|
- Runtime or time-to-full
|
||||||
|
- Outside temp/weather badge
|
||||||
|
- Fridge/freezer temps
|
||||||
|
- Relay state summary
|
||||||
|
|
||||||
|
### Battery Detail
|
||||||
|
- SOC
|
||||||
|
- Voltage
|
||||||
|
- Current
|
||||||
|
- Remaining Ah
|
||||||
|
- Capacity Ah
|
||||||
|
- Cell voltages
|
||||||
|
- Cell delta
|
||||||
|
- BMS temp
|
||||||
|
- Cycle count
|
||||||
|
|
||||||
|
### Relay Control
|
||||||
|
- Starlink relay
|
||||||
|
- Fridge relay
|
||||||
|
- Future accessory relays
|
||||||
|
- Clear ON/OFF touch targets
|
||||||
|
|
||||||
|
### Temperature Detail
|
||||||
|
- Freezer
|
||||||
|
- Fridge
|
||||||
|
- Outside/weather
|
||||||
|
- Cargo/ambient
|
||||||
|
- Online/offline status
|
||||||
|
|
||||||
|
### System Status
|
||||||
|
- Cargo ESP firmware version
|
||||||
|
- WiFi state
|
||||||
|
- API connectivity
|
||||||
|
- Uptime
|
||||||
|
- Alarm status
|
||||||
|
|
||||||
|
## Connection Flow
|
||||||
|
|
||||||
|
1. Dashboard boots.
|
||||||
|
2. Connects to cargo ESP32 AP.
|
||||||
|
3. Checks GET /api/v1/health.
|
||||||
|
4. Polls GET /api/v1/status.
|
||||||
|
5. Shows disconnected state until valid JSON is received.
|
||||||
|
6. Updates dashboard on a fixed interval.
|
||||||
|
7. Relay buttons call POST /api/v1/relay/set.
|
||||||
|
|
||||||
|
## Polling Guidance
|
||||||
|
|
||||||
|
Initial MVP:
|
||||||
|
- Poll /status every 1-2 seconds.
|
||||||
|
- Treat failed requests as dashboard disconnected.
|
||||||
|
- Avoid blocking UI rendering while waiting for HTTP.
|
||||||
|
|
||||||
|
Later:
|
||||||
|
- Add retry/backoff.
|
||||||
|
- Add cached last-known values.
|
||||||
|
- Add visual stale-data indicator.
|
||||||
|
|
||||||
|
## Important Constraints
|
||||||
|
|
||||||
|
- Do not move source-of-truth logic to the dashboard.
|
||||||
|
- Do not duplicate BMS parsing on the dashboard.
|
||||||
|
- Do not require internet access.
|
||||||
|
- Do not make dashboard config the primary setup workflow.
|
||||||
|
- WebUI Config tab remains the preferred setup workflow.
|
||||||
|
- Cargo ESP32 should continue to work without the dashboard powered on.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
No Pico dashboard is planned. Existing Pico/UART material is legacy unless explicitly revived for diagnostics or alternate hardware.
|
||||||
|
|
||||||
|
|
||||||
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
|
## Not Dashboard Responsibilities
|
||||||
|
|
||||||
|
The Dashboard ESP32-S3 does not own:
|
||||||
|
|
||||||
|
- Relay state authority
|
||||||
|
- BMS connection/configuration
|
||||||
|
- Cargo ESP configuration
|
||||||
|
- Alarm authority
|
||||||
|
- Load-switching output ownership
|
||||||
|
|
||||||
|
It may display state and request changes through the Cargo ESP HTTP API, but the Cargo ESP remains the source of truth.
|
||||||
|
|
||||||
|
## Temperature Data Source
|
||||||
|
|
||||||
|
The ESP32-S3 dashboard should display temperature data from the Cargo ESP API.
|
||||||
|
|
||||||
|
DS18B20 sensors are already implemented on the Cargo ESP/WebUI/API side. For v1, the dashboard should not directly own cargo/fridge DS18B20 wiring or sensor reads.
|
||||||
|
|
||||||
|
## API Dependency
|
||||||
|
|
||||||
|
The ESP32-S3 dashboard depends on the Cargo ESP API contract documented in `docs/cargo-api-contract.md`.
|
||||||
|
|
||||||
|
For v1, the dashboard should consume:
|
||||||
|
|
||||||
|
- System status
|
||||||
|
- Battery/BMS telemetry
|
||||||
|
- DS18B20 temperature telemetry
|
||||||
|
- Relay/output state
|
||||||
|
- Alarm/fault state
|
||||||
|
|
||||||
|
The dashboard may request output changes through the Cargo ESP API, but the Cargo ESP remains the source of truth.
|
||||||
+29
-12
@@ -20,38 +20,45 @@ Reason:
|
|||||||
|
|
||||||
The ESP32 is installed near the electrical system and should continue operating even if the dashboard is disconnected.
|
The ESP32 is installed near the electrical system and should continue operating even if the dashboard is disconnected.
|
||||||
|
|
||||||
## Decision: Pico Is a Dashboard, Not the Controller
|
## Decision: Waveshare ESP32-S3 Is the Dashboard Client
|
||||||
|
|
||||||
The Pico dashboard is responsible for user interaction.
|
The Waveshare ESP32-S3 touchscreen dashboard is responsible for local user interaction.
|
||||||
|
|
||||||
It should not own critical control state.
|
It connects to the Cargo ESP32 access point and consumes the HTTP API. It should not own critical control state.
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
|
|
||||||
The dashboard may reboot, disconnect, or be removed without disabling the controller.
|
The dashboard may reboot, disconnect, or be removed without disabling the controller.
|
||||||
|
|
||||||
## Decision: UART Is the Primary Dashboard Link
|
## Decision: HTTP Is the Primary Dashboard Link
|
||||||
|
|
||||||
UART is the preferred Pico-to-ESP32 communication path.
|
HTTP over the Cargo ESP32 local WiFi network is the preferred dashboard-to-controller communication path.
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
|
|
||||||
- Simple wiring
|
- Matches the WebUI and future integration contract
|
||||||
- Deterministic
|
- Avoids a dedicated dashboard data cable
|
||||||
- No WiFi dependency
|
- Supports the Waveshare ESP32-S3 dashboard target
|
||||||
- Works offline
|
- Keeps the dashboard a simple client
|
||||||
- Easier to debug electrically
|
|
||||||
|
|
||||||
HTTP remains useful for setup, debugging, and future web UI.
|
The Cargo ESP32 AP remains available for local operation without internet.
|
||||||
|
|
||||||
|
## Decision: UART Is Optional Diagnostics
|
||||||
|
|
||||||
|
UART JSON may remain useful for service diagnostics, simulator work, or alternate clients.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
USB serial and UART are useful recovery/debug paths, but the dashboard architecture should not depend on a Pico or UART cable.
|
||||||
|
|
||||||
## Decision: HTTP and UART Should Share a Data Model
|
## Decision: HTTP and UART Should Share a Data Model
|
||||||
|
|
||||||
The same status/config model should be used across:
|
The same status/config model should be used across:
|
||||||
|
|
||||||
- UART
|
|
||||||
- HTTP
|
- HTTP
|
||||||
- Simulator
|
- Simulator
|
||||||
- Future MQTT/Home Assistant integrations
|
- Future MQTT/Home Assistant integrations
|
||||||
|
- Optional UART diagnostics
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
|
|
||||||
@@ -177,3 +184,13 @@ Cloud dashboards and remote access are not current priorities.
|
|||||||
Reason:
|
Reason:
|
||||||
|
|
||||||
The core controller/dashboard system should be reliable locally before remote features are considered.
|
The core controller/dashboard system should be reliable locally before remote features are considered.
|
||||||
|
|
||||||
|
## Decision: Future Pi Zero Is Optional
|
||||||
|
|
||||||
|
A Pi Zero may be added later for long-term logging, charts, dashboards, or local integrations.
|
||||||
|
|
||||||
|
It must not own relay control, BMS state, alarm authority, or persistent controller configuration.
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
|
||||||
|
The Cargo ESP32 must continue operating as the controller and source of truth without any higher-level computer powered on.
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Pre-LilyGO Baseline Checklist
|
||||||
|
|
||||||
|
Use this checklist before creating a release/tag for the current Cargo ESP 2-channel baseline.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Capture a known-good project state before migrating Cargo ESP hardware to a LilyGO T-Relay-S3 board or other expanded-output controller.
|
||||||
|
|
||||||
|
## Required Before Tagging
|
||||||
|
|
||||||
|
- [ ] Tests pass with `source .venv/bin/activate && python run_tests.py`
|
||||||
|
- [ ] Working Cargo ESP WebUI confirmed
|
||||||
|
- [ ] Relay/output control confirmed on current 2-channel board
|
||||||
|
- [ ] DS18B20 temperature display confirmed in WebUI
|
||||||
|
- [ ] Cargo ESP API contract documented
|
||||||
|
- [ ] Controller roles documented
|
||||||
|
- [ ] Pico dashboard archived
|
||||||
|
- [ ] Waveshare ESP32-S3 Touch LCD 5B dashboard target documented
|
||||||
|
- [ ] Load-switching hardware boundaries documented
|
||||||
|
- [ ] No stale active Pico/UART dashboard references remain
|
||||||
|
- [ ] Repo is clean: `git status --short`
|
||||||
|
|
||||||
|
## Suggested Tag Name
|
||||||
|
|
||||||
|
v0.1.0-cargo-esp32-2ch-baseline
|
||||||
|
|
||||||
|
## Suggested Release Title
|
||||||
|
|
||||||
|
Cargo ESP32 2-Channel Relay Board Baseline
|
||||||
|
|
||||||
|
## Suggested Release Notes
|
||||||
|
|
||||||
|
Baseline working release before future Cargo ESP hardware migration.
|
||||||
|
|
||||||
|
This release captures the current Cargo ESP32 controller architecture using the 2-channel relay board.
|
||||||
|
|
||||||
|
Includes:
|
||||||
|
|
||||||
|
- Cargo ESP WebUI/API architecture
|
||||||
|
- DS18B20 temperature support
|
||||||
|
- Relay/output control
|
||||||
|
- HTTP API contract tests
|
||||||
|
- Legacy Pico dashboard archived
|
||||||
|
- ESP32-S3 Waveshare 5B dashboard direction documented
|
||||||
|
- Controller roles clarified
|
||||||
|
- Load-switching hardware boundaries documented
|
||||||
|
|
||||||
|
Known future work:
|
||||||
|
|
||||||
|
- LilyGO T-Relay-S3 hardware evaluation
|
||||||
|
- Expanded relay/output mapping
|
||||||
|
- OTA workflow
|
||||||
|
- ESP32-S3 dashboard firmware
|
||||||
|
- Dashboard screen mockups
|
||||||
+116
-15
@@ -1,12 +1,12 @@
|
|||||||
# Project State
|
# Project State
|
||||||
|
|
||||||
Last updated: 2026-06-04
|
Last updated: 2026-06-07
|
||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
Current firmware version:
|
Current firmware version:
|
||||||
|
|
||||||
0.4.0
|
0.5.0
|
||||||
|
|
||||||
## Current Architecture
|
## Current Architecture
|
||||||
|
|
||||||
@@ -18,11 +18,11 @@ Core controller:
|
|||||||
|
|
||||||
Current dashboard plan:
|
Current dashboard plan:
|
||||||
|
|
||||||
Raspberry Pi Pico 2 W physical dashboard over UART
|
Waveshare ESP32-S3-Touch-LCD-5 over WiFi/HTTP
|
||||||
|
|
||||||
Optional future expansion:
|
Optional future expansion:
|
||||||
|
|
||||||
Pi Zero 2 W for advanced logging, charts, and integrations
|
Pi Zero for long-term logging, charts, and local integrations
|
||||||
|
|
||||||
## ESP32 Responsibilities
|
## ESP32 Responsibilities
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ The ESP32 is the source of truth for:
|
|||||||
- Embedded WebUI
|
- Embedded WebUI
|
||||||
- WiFi AP recovery network
|
- WiFi AP recovery network
|
||||||
- WiFi STA client mode
|
- WiFi STA client mode
|
||||||
- UART protocol for future Pico dashboard
|
- Optional UART JSON protocol for diagnostics or alternate clients
|
||||||
|
|
||||||
## Current Working Features
|
## Current Working Features
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ Shows:
|
|||||||
- Battery SOC
|
- Battery SOC
|
||||||
- Voltage
|
- Voltage
|
||||||
- Current
|
- Current
|
||||||
- Estimated runtime
|
- Estimated runtime / charging time-to-full
|
||||||
- Temperature sensors
|
- Temperature sensors
|
||||||
- Relay controls
|
- Relay controls
|
||||||
- Network status
|
- Network status
|
||||||
@@ -117,15 +117,11 @@ Current rear enclosure target:
|
|||||||
|
|
||||||
Relay strategy:
|
Relay strategy:
|
||||||
|
|
||||||
Battery positive -> fuse -> relay COM -> relay NO -> load positive
|
ESP32 relay board output
|
||||||
Load negative -> ground bus
|
-> external fused automotive relay or contactor trigger
|
||||||
|
-> fused load circuit
|
||||||
|
|
||||||
The built-in relays are currently planned for:
|
The ESP32 relay board outputs are control/trigger outputs only. Fridge, Starlink, inverter, and other major loads must use properly fused automotive relay or contactor circuits.
|
||||||
|
|
||||||
- Starlink Mini
|
|
||||||
- 12V fridge
|
|
||||||
|
|
||||||
External automotive relays remain a future option for higher-current loads.
|
|
||||||
|
|
||||||
## Temperature Sensor Direction
|
## Temperature Sensor Direction
|
||||||
|
|
||||||
@@ -159,6 +155,111 @@ Examples:
|
|||||||
1. Verify WebUI setup workflow after fresh flash.
|
1. Verify WebUI setup workflow after fresh flash.
|
||||||
2. Clean up DS18B20 physical distribution hardware.
|
2. Clean up DS18B20 physical distribution hardware.
|
||||||
3. Test 2-4 temperature probes after distribution board is installed.
|
3. Test 2-4 temperature probes after distribution board is installed.
|
||||||
4. Start Pico dashboard MVP over UART.
|
4. Start Waveshare ESP32-S3 dashboard MVP over WiFi/HTTP.
|
||||||
5. Add config backup/restore.
|
5. Add config backup/restore.
|
||||||
6. Improve BMS out-of-range behavior without changing NimBLE connect timeout.
|
6. Improve BMS out-of-range behavior without changing NimBLE connect timeout.
|
||||||
|
7. Define future Pi Zero logging/integration role without moving control authority off the Cargo ESP32.
|
||||||
|
|
||||||
|
Recent notes:
|
||||||
|
|
||||||
|
- WebUI temperature scan, assign, clear, and weather/outside-air selection are implemented.
|
||||||
|
- DS18B20 slots now require explicit assigned addresses before reporting values.
|
||||||
|
- Relay controls and temperature configuration remain driven by generic IDs plus saved configuration names.
|
||||||
|
|
||||||
|
|
||||||
|
## Dashboard Architecture Update
|
||||||
|
|
||||||
|
Primary dashboard architecture is now:
|
||||||
|
|
||||||
|
Cargo ESP32 Controller
|
||||||
|
→ WiFi AP/STA
|
||||||
|
→ HTTP API
|
||||||
|
→ WebUI
|
||||||
|
|
||||||
|
Dashboard Client:
|
||||||
|
→ Waveshare 5" ESP32-S3 Display
|
||||||
|
→ Connects to cargo ESP32 over WiFi
|
||||||
|
→ Consumes existing HTTP API
|
||||||
|
→ Planned LVGL-based dashboard UI
|
||||||
|
|
||||||
|
There is no planned Pico dashboard path. Existing Pico/UART files are legacy artifacts unless explicitly revived later.
|
||||||
|
|
||||||
|
UART JSON remains optional for diagnostics or alternate clients. New dashboard work should target the Waveshare ESP32-S3 display and the HTTP API.
|
||||||
|
|
||||||
|
|
||||||
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
|
Current Cargo ESP32 GPIO plan:
|
||||||
|
|
||||||
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
|
## Current Role Boundary
|
||||||
|
|
||||||
|
The authoritative role split is documented in `docs/controller-roles.md`.
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- Cargo ESP32: power-system controller, WebUI, REST API, output control, BMS, DS18B20, local config, optional OLED.
|
||||||
|
- Dashboard ESP32-S3: touchscreen UI, gauges, future CAN/OBD-II, future tilt/roll/off-road visualization.
|
||||||
|
- v1 communications: Dashboard connects to Cargo ESP AP over WiFi/HTTP REST.
|
||||||
|
- Not v1 communications: ESP-NOW, MQTT, UART dashboard link, or CAN between controllers.
|
||||||
|
- Load-switching hardware is still an implementation choice, not frozen architecture.
|
||||||
|
|
||||||
|
## Pico Dashboard Archive Cleanup
|
||||||
|
|
||||||
|
The old `pico-dashboard/` implementation has been moved to `archive/legacy-pico-dashboard/`.
|
||||||
|
|
||||||
|
It is retained as reference material only. It is not active firmware and is no longer part of the default validation path.
|
||||||
|
|
||||||
|
Current dashboard work should target the ESP32-S3 dashboard direction documented in `docs/dashboard-esp32s3.md` and the placeholder at `firmware/esp32-s3-dashboard/`.
|
||||||
|
|
||||||
|
## Selected Dashboard Hardware
|
||||||
|
|
||||||
|
The current dashboard target is the Waveshare ESP32-S3 Touch LCD 5B with a 5-inch 1024x600 touchscreen.
|
||||||
|
|
||||||
|
This replaces the earlier Pico dashboard concept. The Pico dashboard has been archived as reference-only material.
|
||||||
|
|
||||||
|
## Implemented DS18B20/WebUI Support
|
||||||
|
|
||||||
|
DS18B20 support is already implemented in the Cargo ESP/WebUI/API path.
|
||||||
|
|
||||||
|
This is not a future feature to add from scratch.
|
||||||
|
|
||||||
|
Future work should focus on:
|
||||||
|
|
||||||
|
- Keeping the API contract accurate
|
||||||
|
- Displaying existing temperature telemetry on the ESP32-S3 dashboard
|
||||||
|
- Preserving Cargo ESP ownership of cargo/fridge temperature sensors
|
||||||
|
|
||||||
|
## Cargo API Contract
|
||||||
|
|
||||||
|
The current Cargo ESP API ownership and expected endpoint areas are documented in `docs/cargo-api-contract.md`.
|
||||||
|
|
||||||
|
This contract should guide future ESP32-S3 dashboard work so dashboard firmware targets stable Cargo ESP data instead of duplicating sensor or relay ownership.
|
||||||
|
|
||||||
|
## Pre-LilyGO Baseline
|
||||||
|
|
||||||
|
Before migrating Cargo ESP hardware to a LilyGO T-Relay-S3 or other expanded-output controller, use `docs/pre-lilygo-baseline-checklist.md` to confirm the current 2-channel Cargo ESP baseline is stable enough to tag.
|
||||||
|
|
||||||
|
## LilyGO Ordered / Transition Plan
|
||||||
|
|
||||||
|
The LILYGO T-Relay-S3 6-way board has been ordered but is not yet the active hardware target.
|
||||||
|
|
||||||
|
Current testing continues on the original 2-channel relay board.
|
||||||
|
|
||||||
|
Near-term work should prepare the Cargo ESP API and configuration model so the future LilyGO migration is a hardware-profile change instead of an API redesign.
|
||||||
|
|
||||||
|
Primary goal:
|
||||||
|
|
||||||
|
- Keep 2-channel testing functional now.
|
||||||
|
- Support future 6-output rendering/control cleanly through the same API shape.
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Repo Cleanup Audit
|
||||||
|
|
||||||
|
Last updated: 2026-06-09
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This audit checks active repository files for stale Pico/UART dashboard references after the Pico dashboard implementation was archived.
|
||||||
|
|
||||||
|
Excluded paths:
|
||||||
|
|
||||||
|
- `archive/`
|
||||||
|
- `.git/`
|
||||||
|
- `.venv/`
|
||||||
|
- build/cache folders
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
The following active references still mention Pico/UART dashboard terms.
|
||||||
|
|
||||||
|
These are not automatically wrong. Some may be intentional history or handoff notes, but each should be reviewed before the next baseline release.
|
||||||
|
|
||||||
|
| File | Line | Term | Text |
|
||||||
|
|---|---:|---|---|
|
||||||
|
| `tools/pico_desktop_sim.py` | 5 | `pico-dashboard` | sys.path.insert(0, str(ROOT / "pico-dashboard")) |
|
||||||
|
| `docs/project-state.md` | 185 | `Pico dashboard` | There is no planned Pico dashboard path. Existing Pico/UART files are legacy artifacts unless explicitly revived later. |
|
||||||
|
| `docs/project-state.md` | 215 | `UART dashboard` | - Not v1 communications: ESP-NOW, MQTT, UART dashboard link, or CAN between controllers. |
|
||||||
|
| `docs/project-state.md` | 218 | `Pico Dashboard` | ## Pico Dashboard Archive Cleanup |
|
||||||
|
| `docs/project-state.md` | 220 | `pico-dashboard` | The old `pico-dashboard/` implementation has been moved to `archive/legacy-pico-dashboard/`. |
|
||||||
|
| `docs/project-state.md` | 230 | `Pico dashboard` | This replaces the earlier Pico dashboard concept. The Pico dashboard has been archived as reference-only material. |
|
||||||
|
| `docs/HARDWARE.md` | 202 | `Pico dashboard` | This is the active dashboard platform for the project. The previous Pico dashboard implementation has been archived and is reference-only. |
|
||||||
|
| `docs/ARCHITECTURE.md` | 250 | `Pico dashboard` | This replaces the earlier Pico dashboard concept as the active dashboard architecture. |
|
||||||
|
| `docs/CHATGPT_HANDOFF.md` | 157 | `UART dashboard` | - ESP-NOW, MQTT, UART dashboard link, and CAN-between-controllers are not v1 architecture. |
|
||||||
|
| `docs/CHATGPT_HANDOFF.md` | 160 | `Pico Dashboard` | ## Legacy Pico Dashboard Archive |
|
||||||
|
| `docs/CHATGPT_HANDOFF.md` | 162 | `pico-dashboard` | The old Pico dashboard implementation has been archived at `archive/legacy-pico-dashboard/`. |
|
||||||
|
| `docs/dashboard-esp32s3.md` | 156 | `Pico dashboard` | No Pico dashboard is planned. Existing Pico/UART material is legacy unless explicitly revived for diagnostics or alternate hardware. |
|
||||||
|
| `docs/README.md` | 39 | `pico-dashboard` | - `../archive/legacy-pico-dashboard/` - Archived Pico dashboard implementation kept for reference only. |
|
||||||
|
| `docs/UART_PROTOCOL.md` | 7 | `Pico dashboard` | Keep this protocol for diagnostics, simulator work, service tools, or alternate clients. Do not treat it as evidence that a Pico dashboard is planned. |
|
||||||
|
| `docs/UART_PROTOCOL.md` | 164 | `Pico dashboard` | - No Pico dashboard is currently planned. |
|
||||||
|
| `firmware/esp32-s3-dashboard/README.md` | 44 | `Pico UART` | - Pico UART |
|
||||||
|
| `firmware/esp32-s3-dashboard/README.md` | 64 | `Pico dashboard` | This is the current active dashboard target. The archived Pico dashboard is not active firmware. |
|
||||||
|
| `firmware/esp32/overland-controller/config.h` | 21 | `UART dashboard` | // Legacy UART dashboard support is retired from active hardware use. |
|
||||||
|
|
||||||
|
## Current Dashboard Direction
|
||||||
|
|
||||||
|
Active dashboard work targets the Waveshare ESP32-S3 Touch LCD 5B with a 5-inch 1024x600 touchscreen.
|
||||||
|
|
||||||
|
The archived Pico dashboard is retained only for historical reference and should not be treated as active firmware.
|
||||||
|
|
||||||
|
## Existing DS18B20 Support
|
||||||
|
|
||||||
|
DS18B20 temperature sensor support is already implemented in the Cargo ESP/WebUI/API path.
|
||||||
|
|
||||||
|
Future work should focus on preserving and exposing that existing support cleanly through the API contract and dashboard client, not re-adding DS18B20 support from scratch.
|
||||||
+118
-396
@@ -1,439 +1,161 @@
|
|||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
This roadmap tracks planned Overland Controller development.
|
## Phase 1: Solid Local Cargo Controller
|
||||||
|
|
||||||
The roadmap is flexible and may change as the project evolves.
|
- Reliable relay control
|
||||||
|
- Reliable DS18B20 temps
|
||||||
|
- Reliable JBD/Xiaoxiang BMS BLE
|
||||||
|
- WPA2 AP mode
|
||||||
|
- Stable WebUI
|
||||||
|
- Stable HTTP API
|
||||||
|
- Clear status/config/control model
|
||||||
|
|
||||||
## Phase 1: ESP32 Controller Foundation
|
## Phase 2: Waveshare ESP32-S3 Dashboard
|
||||||
|
|
||||||
Status: mostly complete
|
- LVGL project scaffold
|
||||||
|
- WiFi client to Cargo ESP32 AP
|
||||||
Goals:
|
- Poll /status
|
||||||
|
- Display battery, relays, temps, and alarms
|
||||||
- Establish ESP32 firmware structure
|
|
||||||
- Implement relay control
|
|
||||||
- Implement status reporting
|
|
||||||
- Implement local configuration
|
|
||||||
- Implement BMS telemetry
|
|
||||||
- Implement generic relay/sensor naming
|
|
||||||
- Establish HTTP and UART communication models
|
|
||||||
|
|
||||||
Completed:
|
|
||||||
|
|
||||||
- Relay control
|
|
||||||
- JSON status API
|
|
||||||
- Configuration persistence
|
|
||||||
- Factory reset
|
|
||||||
- BMS BLE telemetry
|
|
||||||
- Cell voltage telemetry
|
|
||||||
- Generic relay IDs
|
|
||||||
- Generic temperature sensor IDs
|
|
||||||
- HTTP API
|
|
||||||
- Serial console commands
|
|
||||||
- Documentation cleanup
|
|
||||||
|
|
||||||
Remaining:
|
|
||||||
|
|
||||||
- Finish UART parity
|
|
||||||
- Improve BLE setup reliability
|
|
||||||
- Add stronger config validation
|
|
||||||
- Add better command help output
|
|
||||||
|
|
||||||
## Phase 2: UART Parity
|
|
||||||
|
|
||||||
Status: next priority
|
|
||||||
|
|
||||||
Goal:
|
|
||||||
|
|
||||||
The Pico dashboard should be able to configure and control the ESP32 over UART without relying on WiFi.
|
|
||||||
|
|
||||||
Minimum UART v1 messages:
|
|
||||||
|
|
||||||
- status_request
|
|
||||||
- set_relay
|
|
||||||
- config_request
|
|
||||||
- config_device
|
|
||||||
- config_relay
|
|
||||||
- config_temp
|
|
||||||
- config_bms
|
|
||||||
- save_config
|
|
||||||
- factory_reset
|
|
||||||
- enter_bms_setup
|
|
||||||
- scan_ble
|
|
||||||
- select_bms
|
|
||||||
- exit_bms_setup
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
The Pico can:
|
|
||||||
|
|
||||||
- Read full system status
|
|
||||||
- Display current configuration
|
|
||||||
- Control relays
|
|
||||||
- Rename relays
|
|
||||||
- Rename temperature sensors
|
|
||||||
- Configure BMS
|
|
||||||
- Run BMS setup workflow
|
|
||||||
- Save configuration
|
|
||||||
|
|
||||||
## Phase 3: Temperature Sensors
|
|
||||||
|
|
||||||
Status: partially started
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- Complete DS18B20 hardware wiring
|
|
||||||
- Add proper 4.7k pull-up resistor
|
|
||||||
- Detect connected sensors
|
|
||||||
- Report sensor addresses
|
|
||||||
- Assign sensors to config slots
|
|
||||||
- Persist sensor address assignments
|
|
||||||
|
|
||||||
Planned serial commands:
|
|
||||||
|
|
||||||
- scan temps
|
|
||||||
- assign temp 1 <address>
|
|
||||||
- clear temp 1
|
|
||||||
|
|
||||||
Planned UART messages:
|
|
||||||
|
|
||||||
- scan_temps
|
|
||||||
- assign_temp
|
|
||||||
- clear_temp
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
A user can connect multiple DS18B20 sensors, identify each one, name it, and save the configuration.
|
|
||||||
|
|
||||||
## Phase 4: Pico Dashboard Bring-Up
|
|
||||||
|
|
||||||
Status: not started on hardware
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- Bring up Pico 2 W
|
|
||||||
- Bring up touchscreen
|
|
||||||
- Establish UART connection to ESP32
|
|
||||||
- Display status response
|
|
||||||
- Display battery data
|
|
||||||
- Display relay states
|
|
||||||
- Display temperature data
|
|
||||||
- Send relay commands
|
- Send relay commands
|
||||||
|
- Handle offline/stale/failed-command states
|
||||||
|
|
||||||
Initial dashboard screens:
|
UI direction:
|
||||||
|
|
||||||
- Main overview
|
- Dark theme
|
||||||
- Battery screen
|
- Large readable text
|
||||||
- Temperature screen
|
- Circular SOC gauge
|
||||||
- Relay control screen
|
- Card-based layout
|
||||||
- Settings screen
|
- Smooth subtle animations
|
||||||
|
- No stock LVGL demo look
|
||||||
|
|
||||||
## Phase 5: Pico Configuration UI
|
## Phase 3: Vehicle Data
|
||||||
|
|
||||||
Status: planned
|
- Connect Waveshare CAN to OBD-II CAN-H/CAN-L
|
||||||
|
- Keep CAN termination disabled
|
||||||
|
- Read coolant temp via PID 0105
|
||||||
|
- Read RPM via PID 010C
|
||||||
|
- Read vehicle speed via PID 010D
|
||||||
|
|
||||||
Goals:
|
## Phase 4: Transmission Temperature
|
||||||
|
|
||||||
- Configure device name
|
- Install dedicated sender
|
||||||
- Configure relay names
|
- Read through exposed ADC or ADS1115 over I2C
|
||||||
- Configure temperature names
|
- Convert resistance to temp with lookup interpolation
|
||||||
- Configure BMS
|
- Add alert thresholds
|
||||||
- Run BLE scan workflow
|
|
||||||
- Save config
|
|
||||||
- Factory reset
|
|
||||||
|
|
||||||
Expected result:
|
## Phase 5: Future Home Server
|
||||||
|
|
||||||
A new installation can be configured from the dashboard without using a laptop.
|
- Cargo ESP32 outbound connection
|
||||||
|
- Remote read-only dashboard first
|
||||||
|
- Optional commands later
|
||||||
|
- HTTPS reverse proxy/PWA
|
||||||
|
- Possible MQTT/WebSocket bridge
|
||||||
|
- Optional Grafana/Home Assistant/InflDB
|
||||||
|
|
||||||
## Phase 6: Vehicle Inputs
|
## Phase 6: Optional Enhancements
|
||||||
|
|
||||||
Status: planned
|
- microSD logging
|
||||||
|
|
||||||
Possible inputs:
|
|
||||||
|
|
||||||
- Ignition signal
|
|
||||||
- Vehicle battery voltage
|
|
||||||
- OBD-II
|
|
||||||
- CAN bus
|
|
||||||
- GPS
|
- GPS
|
||||||
|
- OTA updates
|
||||||
|
- Dashboard alert sounds
|
||||||
|
- More vehicle PIDs
|
||||||
|
- Historical charts
|
||||||
|
|
||||||
Potential data:
|
|
||||||
|
|
||||||
- Ignition state
|
## AP Security, OLED Setup, and Dashboard Pairing
|
||||||
- Engine RPM
|
|
||||||
- Coolant temperature
|
|
||||||
- Vehicle speed
|
|
||||||
- Fuel level
|
|
||||||
- Diagnostic trouble codes
|
|
||||||
- Location
|
|
||||||
- Trip data
|
|
||||||
|
|
||||||
## Phase 7: Logging and History
|
### Phase 1 — Cargo AP security
|
||||||
|
|
||||||
Status: planned
|
- Require WPA2 on the Cargo ESP32 AP.
|
||||||
|
- Generate a unique AP password on first boot.
|
||||||
|
- Store AP SSID/password in Preferences.
|
||||||
|
- Add /api/v1/config/ap for AP configuration.
|
||||||
|
- Add AP auth/SSID metadata to status responses.
|
||||||
|
- Do not expose AP password in API responses.
|
||||||
|
|
||||||
Goals:
|
### Phase 2 — Cargo OLED setup display
|
||||||
|
|
||||||
- Log battery history
|
- Add 128x64 SSD1306 OLED support on the Cargo ESP32.
|
||||||
- Log temperature history
|
- Show AP SSID/password/IP during first boot and setup mode.
|
||||||
- Log relay events
|
- Show critical operational state during normal operation:
|
||||||
- Log alarm events
|
- SOC
|
||||||
- Show historical charts
|
- voltage/current
|
||||||
|
- relay state
|
||||||
|
- WiFi/IP
|
||||||
|
- alarms
|
||||||
|
- Add setup/status button:
|
||||||
|
- short press cycles pages
|
||||||
|
- long press shows AP credentials
|
||||||
|
- extended hold enters recovery/reset mode
|
||||||
|
|
||||||
Possible storage:
|
### Phase 3 — Dashboard credential migration
|
||||||
|
|
||||||
- ESP32 flash
|
- Dashboard ESP32-S3 connects as a client to the Cargo ESP AP.
|
||||||
- SD card
|
- Cargo ESP remains the network/configuration authority.
|
||||||
- External service
|
- When AP credentials are changed from the phone WebUI, Cargo stages the new credentials.
|
||||||
- Future InfluxDB integration
|
- Dashboard fetches and saves pending credentials before Cargo restarts AP.
|
||||||
|
- Cargo applies the new AP settings.
|
||||||
|
- Dashboard reconnects automatically.
|
||||||
|
- Temporary dashboard setup AP is fallback/recovery only.
|
||||||
|
|
||||||
## Phase 8: Integrations
|
### Phase 4 — User-friendly setup polish
|
||||||
|
|
||||||
Status: planned
|
- Add QR code display for joining Cargo AP.
|
||||||
|
- Add dashboard settings UI.
|
||||||
|
- Add LVGL on-screen keyboard only after core dashboard functionality is stable.
|
||||||
|
|
||||||
Potential integrations:
|
|
||||||
|
|
||||||
- MQTT
|
## Cargo OLED firmware foundation
|
||||||
- Home Assistant
|
|
||||||
- Grafana
|
|
||||||
- InfluxDB
|
|
||||||
- Node-RED
|
|
||||||
|
|
||||||
Design goal:
|
Status: planned / scaffolded
|
||||||
|
|
||||||
Integrations should consume the same generic status/config model used by HTTP and UART.
|
Initial OLED firmware goals:
|
||||||
|
|
||||||
## Phase 9: Enclosure and Installation
|
- Add optional SSD1306 128x64 support behind a compile-time flag.
|
||||||
|
- Keep OLED support disabled by default so the controller firmware builds without extra libraries.
|
||||||
|
- Display boot/setup status.
|
||||||
|
- Display AP SSID/password/IP for phone-first setup.
|
||||||
|
- Display rotating service pages:
|
||||||
|
- battery
|
||||||
|
- relays
|
||||||
|
- network
|
||||||
|
- alarms
|
||||||
|
|
||||||
Status: planned
|
Future OLED goals:
|
||||||
|
|
||||||
Goals:
|
- Add setup/status button.
|
||||||
|
- Short press cycles pages.
|
||||||
|
- Long press shows AP credentials.
|
||||||
|
- Extended hold enters recovery/reset mode.
|
||||||
|
- Add first-boot setup screen once AP password generation is finalized.
|
||||||
|
|
||||||
- Design ESP32 enclosure
|
|
||||||
- Design Pico dashboard enclosure
|
|
||||||
- Design wiring harness
|
|
||||||
- Document installation
|
|
||||||
- Create BOM
|
|
||||||
- Create printable STL files
|
|
||||||
- Validate thermal and vibration behavior
|
|
||||||
|
|
||||||
## Phase 10: Public Release
|
## Current communications and GPIO decision
|
||||||
|
|
||||||
Status: future
|
Active dashboard communication is WiFi/HTTP using `/api/v1`.
|
||||||
|
|
||||||
Goals:
|
The old Pico/dashboard UART path is retired from active hardware use. USB Serial remains available for development, debug, and manual configuration from a computer.
|
||||||
|
|
||||||
- Clean documentation
|
Current Cargo ESP32 GPIO plan:
|
||||||
- Stable API
|
|
||||||
- Stable UART protocol
|
|
||||||
- Installation guide
|
|
||||||
- Wiring guide
|
|
||||||
- Build guide
|
|
||||||
- Example configurations
|
|
||||||
- Versioned releases
|
|
||||||
|
|
||||||
Potential release name:
|
- GPIO 16: Relay 1 trigger output
|
||||||
|
- GPIO 17: Relay 2 trigger output
|
||||||
|
- GPIO 4: DS18B20 OneWire temperature bus
|
||||||
|
- GPIO 34: Ignition sense input
|
||||||
|
- GPIO 21: SSD1306 OLED I2C SDA
|
||||||
|
- GPIO 22: SSD1306 OLED I2C SCL
|
||||||
|
- GPIO 25: OLED setup/status button
|
||||||
|
|
||||||
Overland Controller v1.0
|
## Future Wishlist Parking Lot
|
||||||
|
|
||||||
## Long-Term Ideas
|
These are intentionally not committed v1 architecture until explicitly promoted:
|
||||||
|
|
||||||
Potential future features:
|
- BNO085/BNO086 tilt sensor
|
||||||
|
- CAN sniffing beyond standard OBD-II polling
|
||||||
- OTA firmware updates
|
- Fault-code support
|
||||||
- Multi-controller support
|
- Standalone dashboard mode
|
||||||
- Expansion modules
|
- Additional cargo outputs
|
||||||
- Water tank monitoring
|
- Custom PCB
|
||||||
- Inverter monitoring
|
- Smart high-side switching
|
||||||
- Solar charge controller monitoring
|
- WUPP-style fused distribution module
|
||||||
- Starlink diagnostics
|
- MQTT/Home Assistant/Grafana/InfluxDB integrations
|
||||||
- Bluetooth sensor support
|
|
||||||
- Mobile web dashboard
|
|
||||||
- Plugin-style integrations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Networking Roadmap
|
|
||||||
|
|
||||||
### Current
|
|
||||||
|
|
||||||
The ESP32 currently operates as a local access point.
|
|
||||||
|
|
||||||
Default local access:
|
|
||||||
|
|
||||||
http://192.168.4.1
|
|
||||||
|
|
||||||
This provides a reliable recovery/setup path even when no other WiFi network is available.
|
|
||||||
|
|
||||||
### Planned: AP + STA Mode
|
|
||||||
|
|
||||||
The ESP32 should support AP and STA mode at the same time.
|
|
||||||
|
|
||||||
Access Point mode:
|
|
||||||
|
|
||||||
Overland-Controller
|
|
||||||
192.168.4.1
|
|
||||||
|
|
||||||
Station mode:
|
|
||||||
|
|
||||||
Connects to a configured WiFi network such as Starlink, home WiFi, or shop WiFi.
|
|
||||||
|
|
||||||
The AP should remain enabled even when STA mode is connected.
|
|
||||||
|
|
||||||
Reason:
|
|
||||||
|
|
||||||
- Always available recovery path
|
|
||||||
- Easier setup
|
|
||||||
- Less risk of locking yourself out
|
|
||||||
- Useful when Starlink/home WiFi is unavailable
|
|
||||||
|
|
||||||
### Planned: Multiple Saved WiFi Networks
|
|
||||||
|
|
||||||
The controller should eventually support multiple saved WiFi profiles.
|
|
||||||
|
|
||||||
Example priority order:
|
|
||||||
|
|
||||||
1. Starlink
|
|
||||||
2. Home WiFi
|
|
||||||
3. Shop WiFi
|
|
||||||
|
|
||||||
Expected boot behavior:
|
|
||||||
|
|
||||||
Start AP
|
|
||||||
Try Starlink
|
|
||||||
If Starlink fails, try Home WiFi
|
|
||||||
If Home WiFi fails, try Shop WiFi
|
|
||||||
If all fail, remain AP-only
|
|
||||||
|
|
||||||
The ESP32 only connects to one STA network at a time, but it can remember and try multiple networks.
|
|
||||||
|
|
||||||
### Planned WiFi Configuration
|
|
||||||
|
|
||||||
Future configuration should include:
|
|
||||||
|
|
||||||
ap_enabled
|
|
||||||
sta_enabled
|
|
||||||
hostname
|
|
||||||
saved networks
|
|
||||||
priority order
|
|
||||||
|
|
||||||
Future setup options:
|
|
||||||
|
|
||||||
- Serial console
|
|
||||||
- HTTP API
|
|
||||||
- Pico dashboard UI
|
|
||||||
- Embedded web dashboard
|
|
||||||
|
|
||||||
### Planned mDNS
|
|
||||||
|
|
||||||
The controller should eventually support mDNS.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
http://overland-controller.local
|
|
||||||
|
|
||||||
This would avoid needing to look up the controller IP when connected through Starlink or home WiFi.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Embedded Web Dashboard
|
|
||||||
|
|
||||||
### Current Direction
|
|
||||||
|
|
||||||
The ESP32 should serve a lightweight mobile-friendly dashboard.
|
|
||||||
|
|
||||||
Purpose:
|
|
||||||
|
|
||||||
- Check battery status from a phone
|
|
||||||
- Check fridge/temp status from camp
|
|
||||||
- Toggle relays if needed
|
|
||||||
- View alarms
|
|
||||||
- Provide fallback UI without the Pico display
|
|
||||||
|
|
||||||
Design limits:
|
|
||||||
|
|
||||||
- No React
|
|
||||||
- No external libraries
|
|
||||||
- No images
|
|
||||||
- Small inline HTML/CSS/JS
|
|
||||||
- Poll /status every few seconds
|
|
||||||
- Use existing relay endpoints
|
|
||||||
|
|
||||||
This dashboard is not intended to replace the physical Pico dashboard.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Optional Future Pi Zero Module
|
|
||||||
|
|
||||||
The base system should not require a Pi Zero.
|
|
||||||
|
|
||||||
Base system:
|
|
||||||
|
|
||||||
ESP32 controller
|
|
||||||
Pico 2 W dashboard
|
|
||||||
|
|
||||||
Optional future advanced module:
|
|
||||||
|
|
||||||
Pi Zero 2 W
|
|
||||||
|
|
||||||
Possible Pi Zero functions:
|
|
||||||
|
|
||||||
- Rich web dashboard
|
|
||||||
- Historical logging
|
|
||||||
- Graphs
|
|
||||||
- SQLite database
|
|
||||||
- MQTT bridge
|
|
||||||
- Home Assistant bridge
|
|
||||||
- Advanced configuration UI
|
|
||||||
|
|
||||||
The core controller should remain functional without the Pi Zero.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration Backup and Restore
|
|
||||||
|
|
||||||
Planned future feature.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- Export configuration JSON
|
|
||||||
- Import configuration JSON
|
|
||||||
- Backup install-specific setup
|
|
||||||
- Restore after factory reset
|
|
||||||
- Make support easier for future users
|
|
||||||
|
|
||||||
This is important if the project becomes shareable or sellable.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Post-0.4.0 Roadmap
|
|
||||||
|
|
||||||
### Near Term
|
|
||||||
|
|
||||||
- Verify complete WebUI setup workflow.
|
|
||||||
- Add config backup/export/import.
|
|
||||||
- Improve BMS out-of-range behavior without reducing NimBLE connection reliability.
|
|
||||||
- Build permanent DS18B20 distribution board.
|
|
||||||
- Test multiple temperature probes.
|
|
||||||
- Update enclosure wiring layout.
|
|
||||||
|
|
||||||
### Pico Dashboard MVP
|
|
||||||
|
|
||||||
- UART status request.
|
|
||||||
- Battery summary screen.
|
|
||||||
- Temperature screen.
|
|
||||||
- Relay control screen.
|
|
||||||
- Alarm indicators.
|
|
||||||
- Screen sleep based on ignition signal later.
|
|
||||||
|
|
||||||
### Future
|
|
||||||
|
|
||||||
- mDNS hostname.
|
|
||||||
- OTA update workflow.
|
|
||||||
- Optional Pi Zero logging module.
|
|
||||||
- Historical graphs.
|
|
||||||
- MQTT/Home Assistant bridge.
|
|
||||||
- Ignition sense.
|
|
||||||
- House battery voltage sense.
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# ESP32-S3 Dashboard Firmware
|
||||||
|
|
||||||
|
This is the placeholder for the current dashboard firmware direction.
|
||||||
|
|
||||||
|
## Target hardware
|
||||||
|
|
||||||
|
Current dashboard target:
|
||||||
|
|
||||||
|
- Waveshare ESP32-S3 touchscreen display
|
||||||
|
- WiFi connection to the Cargo ESP32 access point
|
||||||
|
- HTTP/REST communication with the Cargo ESP32 API
|
||||||
|
- Future LVGL-based dashboard UI
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
The ESP32-S3 dashboard is responsible for:
|
||||||
|
|
||||||
|
- Touchscreen UI
|
||||||
|
- Vehicle gauges
|
||||||
|
- Dashboard rendering
|
||||||
|
- Future CAN/OBD-II vehicle visualization
|
||||||
|
- Future tilt/roll/off-road screen visualization
|
||||||
|
|
||||||
|
## Not responsible for
|
||||||
|
|
||||||
|
The ESP32-S3 dashboard does not own:
|
||||||
|
|
||||||
|
- Relay authority
|
||||||
|
- BMS authority
|
||||||
|
- Alarm authority
|
||||||
|
- Cargo ESP configuration storage
|
||||||
|
- Load-switching hardware control as the source of truth
|
||||||
|
|
||||||
|
The Cargo ESP32 remains the power-system controller and source of truth.
|
||||||
|
|
||||||
|
## v1 communication path
|
||||||
|
|
||||||
|
Dashboard ESP32-S3
|
||||||
|
<-> WiFi / HTTP REST
|
||||||
|
Cargo ESP32 AP
|
||||||
|
|
||||||
|
Not v1 dashboard communication:
|
||||||
|
|
||||||
|
- Pico UART
|
||||||
|
- ESP-NOW
|
||||||
|
- MQTT
|
||||||
|
- CAN between controllers
|
||||||
|
|
||||||
|
## Selected Hardware
|
||||||
|
|
||||||
|
The selected dashboard hardware target is:
|
||||||
|
|
||||||
|
Waveshare ESP32-S3 Touch LCD 5B
|
||||||
|
|
||||||
|
Display details:
|
||||||
|
|
||||||
|
- 5-inch touchscreen
|
||||||
|
- 1024x600 resolution
|
||||||
|
- Capacitive touch
|
||||||
|
- ESP32-S3 platform
|
||||||
|
- Native WiFi
|
||||||
|
- Integrated CAN transceiver available for future vehicle integration
|
||||||
|
|
||||||
|
This is the current active dashboard target. The archived Pico dashboard is not active firmware.
|
||||||
@@ -31,7 +31,8 @@ void loadDefaultConfig() {
|
|||||||
"temp_" + String(i + 1),
|
"temp_" + String(i + 1),
|
||||||
"Temperature " + String(i + 1),
|
"Temperature " + String(i + 1),
|
||||||
"",
|
"",
|
||||||
i < appConfig.tempSensorCount
|
i < appConfig.tempSensorCount,
|
||||||
|
false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,10 @@ void loadConfig() {
|
|||||||
(prefix + "enabled").c_str(),
|
(prefix + "enabled").c_str(),
|
||||||
appConfig.tempSensors[i].enabled
|
appConfig.tempSensors[i].enabled
|
||||||
);
|
);
|
||||||
|
appConfig.tempSensors[i].weather = preferences.getBool(
|
||||||
|
(prefix + "weather").c_str(),
|
||||||
|
appConfig.tempSensors[i].weather
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfig.bms.enabled = preferences.getBool("bms_enabled", appConfig.bms.enabled);
|
appConfig.bms.enabled = preferences.getBool("bms_enabled", appConfig.bms.enabled);
|
||||||
@@ -124,6 +129,7 @@ void saveConfig() {
|
|||||||
preferences.putString((prefix + "name").c_str(), appConfig.tempSensors[i].name);
|
preferences.putString((prefix + "name").c_str(), appConfig.tempSensors[i].name);
|
||||||
preferences.putString((prefix + "addr").c_str(), appConfig.tempSensors[i].address);
|
preferences.putString((prefix + "addr").c_str(), appConfig.tempSensors[i].address);
|
||||||
preferences.putBool((prefix + "enabled").c_str(), appConfig.tempSensors[i].enabled);
|
preferences.putBool((prefix + "enabled").c_str(), appConfig.tempSensors[i].enabled);
|
||||||
|
preferences.putBool((prefix + "weather").c_str(), appConfig.tempSensors[i].weather);
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences.putBool("bms_enabled", appConfig.bms.enabled);
|
preferences.putBool("bms_enabled", appConfig.bms.enabled);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ struct TempSensorConfig {
|
|||||||
String name;
|
String name;
|
||||||
String address;
|
String address;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
bool weather;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BmsConfig {
|
struct BmsConfig {
|
||||||
|
|||||||
@@ -12,9 +12,14 @@
|
|||||||
// Ignition Sense
|
// Ignition Sense
|
||||||
#define IGNITION_PIN 34
|
#define IGNITION_PIN 34
|
||||||
|
|
||||||
// UART over CAT5 to Pico dashboard
|
// Cargo OLED service/setup display
|
||||||
#define DASHBOARD_UART_TX_PIN 21
|
#define OLED_I2C_SDA_PIN 21
|
||||||
#define DASHBOARD_UART_RX_PIN 22
|
#define OLED_I2C_SCL_PIN 22
|
||||||
|
#define OLED_SETUP_BUTTON_PIN 25
|
||||||
|
|
||||||
|
// Dashboard communication is WiFi/HTTP.
|
||||||
|
// Legacy UART dashboard support is retired from active hardware use.
|
||||||
|
#define DASHBOARD_UART_ENABLED 0
|
||||||
#define DASHBOARD_UART_BAUD 115200
|
#define DASHBOARD_UART_BAUD 115200
|
||||||
|
|
||||||
// RS-485/MAX3485 is fallback only and not currently planned.
|
// RS-485/MAX3485 is fallback only and not currently planned.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,9 @@ DeviceAddress sensorAddresses[MAX_TEMP_SENSORS];
|
|||||||
String sensorAddressStrings[MAX_TEMP_SENSORS];
|
String sensorAddressStrings[MAX_TEMP_SENSORS];
|
||||||
int sensorCount = 0;
|
int sensorCount = 0;
|
||||||
|
|
||||||
|
int tempScanResultIndexes[MAX_TEMP_SENSORS];
|
||||||
|
int tempScanResultCount = 0;
|
||||||
|
|
||||||
float cToF(float c) {
|
float cToF(float c) {
|
||||||
return (c * 9.0 / 5.0) + 32.0;
|
return (c * 9.0 / 5.0) + 32.0;
|
||||||
}
|
}
|
||||||
@@ -52,6 +55,26 @@ bool addressMatches(DeviceAddress address, const String& expected) {
|
|||||||
return actual == normalizedExpected;
|
return actual == normalizedExpected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isTempAddressAssigned(const String& address) {
|
||||||
|
if (address.length() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalizedAddress = address;
|
||||||
|
normalizedAddress.toUpperCase();
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
||||||
|
String assigned = appConfig.tempSensors[i].address;
|
||||||
|
assigned.toUpperCase();
|
||||||
|
|
||||||
|
if (assigned == normalizedAddress) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void clearSensorRuntimeData() {
|
void clearSensorRuntimeData() {
|
||||||
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
||||||
sensors.tempsF[i] = -127.0;
|
sensors.tempsF[i] = -127.0;
|
||||||
@@ -68,29 +91,48 @@ int scanTempSensors() {
|
|||||||
sensorCount = MAX_TEMP_SENSORS;
|
sensorCount = MAX_TEMP_SENSORS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tempScanResultCount = 0;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
||||||
sensorAddressStrings[i] = "";
|
sensorAddressStrings[i] = "";
|
||||||
|
tempScanResultIndexes[i] = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < sensorCount; i++) {
|
for (int i = 0; i < sensorCount; i++) {
|
||||||
if (ds18b20.getAddress(sensorAddresses[i], i)) {
|
if (!ds18b20.getAddress(sensorAddresses[i], i)) {
|
||||||
sensorAddressStrings[i] = addressToString(sensorAddresses[i]);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String addressString = addressToString(sensorAddresses[i]);
|
||||||
|
sensorAddressStrings[i] = addressString;
|
||||||
|
|
||||||
|
if (isTempAddressAssigned(addressString)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tempScanResultIndexes[tempScanResultCount] = i;
|
||||||
|
tempScanResultCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sensorCount;
|
return tempScanResultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getTempScanResultCount() {
|
int getTempScanResultCount() {
|
||||||
return sensorCount;
|
return tempScanResultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTempScanResultAddress(int index) {
|
String getTempScanResultAddress(int index) {
|
||||||
if (index < 0 || index >= sensorCount) {
|
if (index < 0 || index >= tempScanResultCount) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return sensorAddressStrings[index];
|
int physicalIndex = tempScanResultIndexes[index];
|
||||||
|
|
||||||
|
if (physicalIndex < 0 || physicalIndex >= sensorCount) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return sensorAddressStrings[physicalIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
void printSensorAddresses() {
|
void printSensorAddresses() {
|
||||||
@@ -128,17 +170,8 @@ void updateSensors() {
|
|||||||
String configuredAddress = appConfig.tempSensors[configIndex].address;
|
String configuredAddress = appConfig.tempSensors[configIndex].address;
|
||||||
|
|
||||||
if (configuredAddress.length() == 0) {
|
if (configuredAddress.length() == 0) {
|
||||||
// Backward-compatible behavior:
|
sensors.tempsF[configIndex] = -127.0;
|
||||||
// if no address is assigned, use physical scan order.
|
sensors.online[configIndex] = false;
|
||||||
if (configIndex < sensorCount) {
|
|
||||||
float tempC = ds18b20.getTempC(sensorAddresses[configIndex]);
|
|
||||||
|
|
||||||
if (validTempC(tempC)) {
|
|
||||||
sensors.tempsF[configIndex] = cToF(tempC);
|
|
||||||
sensors.online[configIndex] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +197,32 @@ bool assignTempSensorByIndex(int configIndex, int scanIndex) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scanIndex < 0 || scanIndex >= sensorCount) {
|
if (scanIndex < 0 || scanIndex >= tempScanResultCount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfig.tempSensors[configIndex].address = sensorAddressStrings[scanIndex];
|
int physicalIndex = tempScanResultIndexes[scanIndex];
|
||||||
|
|
||||||
|
if (physicalIndex < 0 || physicalIndex >= sensorCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String selectedAddress = sensorAddressStrings[physicalIndex];
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
|
||||||
|
if (i == configIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConfig.tempSensors[i].address == selectedAddress) {
|
||||||
|
appConfig.tempSensors[i].address = "";
|
||||||
|
appConfig.tempSensors[i].enabled = false;
|
||||||
|
sensors.tempsF[i] = -127.0;
|
||||||
|
sensors.online[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig.tempSensors[configIndex].address = selectedAddress;
|
||||||
appConfig.tempSensors[configIndex].enabled = true;
|
appConfig.tempSensors[configIndex].enabled = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -181,6 +235,9 @@ bool clearTempSensor(int configIndex) {
|
|||||||
|
|
||||||
appConfig.tempSensors[configIndex].address = "";
|
appConfig.tempSensors[configIndex].address = "";
|
||||||
appConfig.tempSensors[configIndex].enabled = false;
|
appConfig.tempSensors[configIndex].enabled = false;
|
||||||
|
appConfig.tempSensors[configIndex].weather = false;
|
||||||
|
sensors.tempsF[configIndex] = -127.0;
|
||||||
|
sensors.online[configIndex] = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define FIRMWARE_VERSION "0.4.0"
|
#define FIRMWARE_VERSION "0.5.0"
|
||||||
#define FIRMWARE_NAME "overland-controller"
|
#define FIRMWARE_NAME "overland-controller"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Hardware Archive
|
||||||
|
|
||||||
|
These pinout notes are legacy references from earlier dashboard and wiring plans.
|
||||||
|
|
||||||
|
The current hardware source of truth is `docs/HARDWARE.md`, with the current dashboard plan in `docs/dashboard-esp32s3.md`.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# CAT5 Pinout
|
# CAT5 Pinout
|
||||||
|
|
||||||
|
Legacy note: this file describes an older Pico/UART dashboard wiring plan. Do not use it for the current Waveshare ESP32-S3 dashboard build.
|
||||||
|
|
||||||
## Primary Configuration
|
## Primary Configuration
|
||||||
|
|
||||||
UART over CAT5
|
UART over CAT5
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# ESP32 Pinout (Planned)
|
# ESP32 Pinout (Planned)
|
||||||
|
|
||||||
|
Legacy note: Pico/UART references in this file are not part of the current dashboard plan. Current dashboard target is Waveshare ESP32-S3 over WiFi/HTTP.
|
||||||
|
|
||||||
GPIO 16 Relay 1 (Starlink)
|
GPIO 16 Relay 1 (Starlink)
|
||||||
GPIO 17 Relay 2 (Fridge)
|
GPIO 17 Relay 2 (Fridge)
|
||||||
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Pico Pinout (Planned)
|
# Pico Pinout (Planned)
|
||||||
|
|
||||||
|
Legacy note: no Pico dashboard is currently planned. This file is retained only as historical/reference material.
|
||||||
|
|
||||||
## Display
|
## Display
|
||||||
|
|
||||||
3.5 inch SPI capacitive touchscreen
|
3.5 inch SPI capacitive touchscreen
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# UART CAT5 Pinout
|
# UART CAT5 Pinout
|
||||||
|
|
||||||
|
Legacy note: this file describes an older Pico/UART dashboard wiring plan. Do not use it for the current Waveshare ESP32-S3 dashboard build.
|
||||||
|
|
||||||
## Pair 1
|
## Pair 1
|
||||||
|
|
||||||
Blue +5V
|
Blue +5V
|
||||||
+3
-6
@@ -2,15 +2,13 @@
|
|||||||
|
|
||||||
## Ordered
|
## Ordered
|
||||||
|
|
||||||
- Raspberry Pi Pico 2 W
|
|
||||||
- ESP32 2-Channel Relay Board
|
- ESP32 2-Channel Relay Board
|
||||||
- 3.5" SPI Capacitive Touchscreen
|
|
||||||
|
|
||||||
## Required
|
## Required
|
||||||
|
|
||||||
- 4x DS18B20 Waterproof Sensors
|
- 4x DS18B20 Waterproof Sensors
|
||||||
- 12V→5V Buck Converter
|
- Waveshare ESP32-S3-Touch-LCD-5 dashboard
|
||||||
- CAT5 Cable
|
- Fused ACC switched 12V dashboard power
|
||||||
- Bosch Relay Sockets
|
- Bosch Relay Sockets
|
||||||
- 30A/40A Bosch Relays
|
- 30A/40A Bosch Relays
|
||||||
- Automotive Fuse Block
|
- Automotive Fuse Block
|
||||||
@@ -18,8 +16,7 @@
|
|||||||
|
|
||||||
## Future / Optional Fallback
|
## Future / Optional Fallback
|
||||||
|
|
||||||
- 2x MAX3485 RS-485 Modules, only if UART over CAT5 proves unreliable
|
- Pi Zero for long-term logging and local integrations
|
||||||
- Pi Zero 2 W
|
|
||||||
- GPS Module
|
- GPS Module
|
||||||
- ELM327 OBD-II Interface
|
- ELM327 OBD-II Interface
|
||||||
- CAN Interface
|
- CAN Interface
|
||||||
|
|||||||
+1
-20
@@ -3,26 +3,7 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
COMMANDS = [
|
COMMANDS = [
|
||||||
[sys.executable, "-m", "py_compile",
|
[sys.executable, "-m", "pytest", "tests/test_http_api_contract.py"],
|
||||||
"pico-dashboard/main.py",
|
|
||||||
"pico-dashboard/app.py",
|
|
||||||
"pico-dashboard/comms/protocol.py",
|
|
||||||
"pico-dashboard/comms/uart_client.py",
|
|
||||||
"pico-dashboard/comms/http_client.py",
|
|
||||||
"pico-dashboard/comms/communication_service.py",
|
|
||||||
"pico-dashboard/state/app_state.py",
|
|
||||||
"pico-dashboard/alarms/alarm_definitions.py",
|
|
||||||
"pico-dashboard/alarms/alarm_manager.py",
|
|
||||||
"pico-dashboard/ui/screen_manager.py",
|
|
||||||
"pico-dashboard/ui/dashboard_view_model.py",
|
|
||||||
"pico-dashboard/ui/detail_view_models.py",
|
|
||||||
"pico-dashboard/ui/alarm_view_model.py",
|
|
||||||
"pico-dashboard/ui/touch_router.py",
|
|
||||||
"pico-dashboard/ui/renderers.py",
|
|
||||||
"pico-dashboard/hardware/display.py",
|
|
||||||
"pico-dashboard/hardware/touch.py",
|
|
||||||
"pico-dashboard/hardware/buzzer.py"],
|
|
||||||
[sys.executable, "-m", "pytest", "tests/test_pico_core.py"],
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
flask
|
flask
|
||||||
|
pytest
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Xterra Dashboard Simulator</title>
|
<title>Overland Controller Simulator</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<h1>Xterra Dashboard</h1>
|
<h1>Overland Controller</h1>
|
||||||
<div id="statusLine">UART: -- | WiFi: --</div>
|
<div id="statusLine">UART: -- | WiFi: --</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"device_name": "Cargo Controller",
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"id": "relay_1",
|
||||||
|
"name": "Aux Power",
|
||||||
|
"pin": 16,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "relay_2",
|
||||||
|
"name": "Fridge",
|
||||||
|
"pin": 17,
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bms": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "House Battery",
|
||||||
|
"address": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"address_type": "public"
|
||||||
|
},
|
||||||
|
"temperature_sensor_count": 4,
|
||||||
|
"temperature_sensors": [
|
||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"name": "Cabin",
|
||||||
|
"address": "28ff001122334455",
|
||||||
|
"enabled": true,
|
||||||
|
"weather": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"ok": false,
|
||||||
|
"error": "invalid_json"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"id": "relay_1",
|
||||||
|
"state": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"type": "relay_response",
|
||||||
|
"ok": true,
|
||||||
|
"id": "relay_1",
|
||||||
|
"state": true
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"type": "status_response",
|
||||||
|
"timestamp": 123456,
|
||||||
|
"battery": {
|
||||||
|
"source": "jbd_bms",
|
||||||
|
"connected": true,
|
||||||
|
"soc": 70,
|
||||||
|
"voltage": 13.34,
|
||||||
|
"current": -5.2,
|
||||||
|
"remaining_ah": 104.8,
|
||||||
|
"capacity_ah": 150.0,
|
||||||
|
"runtime_hours": 20.15,
|
||||||
|
"temperature_f": 76.5,
|
||||||
|
"cycle_count": 3,
|
||||||
|
"cell_count": 4,
|
||||||
|
"ntc_count": 2,
|
||||||
|
"cell_voltages": [3.334, 3.333, 3.334, 3.333],
|
||||||
|
"cell_min_voltage": 3.333,
|
||||||
|
"cell_max_voltage": 3.334,
|
||||||
|
"cell_delta_mv": 1,
|
||||||
|
"cells_valid": true
|
||||||
|
},
|
||||||
|
"temps": [
|
||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"name": "Cabin",
|
||||||
|
"enabled": true,
|
||||||
|
"weather": false,
|
||||||
|
"online": true,
|
||||||
|
"temperature_f": 72.4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"id": "relay_1",
|
||||||
|
"name": "Aux Power",
|
||||||
|
"pin": 16,
|
||||||
|
"enabled": true,
|
||||||
|
"state": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "relay_2",
|
||||||
|
"name": "Fridge",
|
||||||
|
"pin": 17,
|
||||||
|
"enabled": true,
|
||||||
|
"state": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vehicle": {
|
||||||
|
"ignition_on": false
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"wifi_enabled": true,
|
||||||
|
"uart_connected": true,
|
||||||
|
"ap_enabled": true,
|
||||||
|
"ap_ip": "192.168.4.1",
|
||||||
|
"sta_enabled": true,
|
||||||
|
"sta_connected": false,
|
||||||
|
"sta_ssid": "",
|
||||||
|
"sta_ip": "",
|
||||||
|
"saved_network_count": 1,
|
||||||
|
"saved_networks": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"ssid": "Starlink",
|
||||||
|
"priority": 1,
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alarms": {
|
||||||
|
"low_soc": false,
|
||||||
|
"critical_soc": false,
|
||||||
|
"low_voltage": false,
|
||||||
|
"high_battery_temp": false,
|
||||||
|
"cell_imbalance": false,
|
||||||
|
"bms_disconnected": false
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"firmware_name": "overland-controller",
|
||||||
|
"firmware_version": "0.4.0",
|
||||||
|
"build_date": "Jun 07 2026",
|
||||||
|
"build_time": "12:00:00",
|
||||||
|
"uptime_seconds": 42
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"device_name": "Cargo Controller",
|
||||||
|
"relays": [
|
||||||
|
{
|
||||||
|
"id": "relay_1",
|
||||||
|
"name": "Aux Power",
|
||||||
|
"pin": 16,
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bms": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "House Battery",
|
||||||
|
"address": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"address_type": "public"
|
||||||
|
},
|
||||||
|
"temperature_sensor_count": 4,
|
||||||
|
"temperature_sensors": [
|
||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"name": "Cabin",
|
||||||
|
"address": "28ff001122334455",
|
||||||
|
"enabled": true,
|
||||||
|
"weather": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"device_name": "Cargo Controller",
|
||||||
|
"relays": [],
|
||||||
|
"bms": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "House Battery",
|
||||||
|
"address": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"address_type": "public"
|
||||||
|
},
|
||||||
|
"temperature_sensor_count": 4,
|
||||||
|
"temperature_sensors": [
|
||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"name": "Cabin",
|
||||||
|
"address": "28ff001122334455",
|
||||||
|
"enabled": true,
|
||||||
|
"weather": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"id": "temp_1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"device_name": "Cargo Controller",
|
||||||
|
"relays": [],
|
||||||
|
"bms": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "House Battery",
|
||||||
|
"address": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"address_type": "public"
|
||||||
|
},
|
||||||
|
"temperature_sensor_count": 4,
|
||||||
|
"temperature_sensors": [
|
||||||
|
{
|
||||||
|
"id": "temp_1",
|
||||||
|
"name": "Cabin",
|
||||||
|
"address": "",
|
||||||
|
"enabled": false,
|
||||||
|
"weather": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "temp_scan_response",
|
||||||
|
"ok": true,
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"address": "28ff001122334455"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"ssid": "Starlink",
|
||||||
|
"password": "example-password",
|
||||||
|
"priority": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"type": "wifi_config_response",
|
||||||
|
"ok": true,
|
||||||
|
"wifi": {
|
||||||
|
"ap_enabled": true,
|
||||||
|
"sta_enabled": true,
|
||||||
|
"network_count": 1,
|
||||||
|
"active_ssid": "",
|
||||||
|
"sta_connected": false,
|
||||||
|
"ap_ip": "192.168.4.1",
|
||||||
|
"sta_ip": "",
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"ssid": "Starlink",
|
||||||
|
"priority": 1,
|
||||||
|
"password_set": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,566 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
FIXTURES = ROOT / "tests" / "fixtures" / "http_api"
|
||||||
|
FIRMWARE = ROOT / "firmware" / "esp32" / "overland-controller" / "overland-controller.ino"
|
||||||
|
|
||||||
|
|
||||||
|
def load_fixture(name):
|
||||||
|
return json.loads((FIXTURES / name).read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def firmware_source():
|
||||||
|
return FIRMWARE.read_text()
|
||||||
|
|
||||||
|
|
||||||
|
def assert_keys(payload, keys):
|
||||||
|
missing = set(keys) - set(payload)
|
||||||
|
assert not missing, f"missing keys: {sorted(missing)}"
|
||||||
|
|
||||||
|
|
||||||
|
def registered_routes():
|
||||||
|
source = 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():
|
||||||
|
routes = registered_routes()
|
||||||
|
|
||||||
|
expected_routes = {
|
||||||
|
("/api/v1/status", "ANY"),
|
||||||
|
("/api/v1/health", "HTTP_GET"),
|
||||||
|
("/api/v1/capabilities", "HTTP_GET"),
|
||||||
|
("/api/v1/config", "HTTP_GET"),
|
||||||
|
("/api/v1/config/export", "HTTP_GET"),
|
||||||
|
("/api/v1/config/import", "HTTP_POST"),
|
||||||
|
("/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"),
|
||||||
|
("/config", "HTTP_GET"),
|
||||||
|
("/relay/set", "HTTP_POST"),
|
||||||
|
("/config/wifi", "HTTP_GET"),
|
||||||
|
("/config/wifi", "HTTP_POST"),
|
||||||
|
("/wifi/connect", "HTTP_POST"),
|
||||||
|
("/wifi/clear", "HTTP_POST"),
|
||||||
|
("/temps/scan", "HTTP_POST"),
|
||||||
|
("/temps/assign", "HTTP_POST"),
|
||||||
|
("/temps/clear", "HTTP_POST"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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():
|
||||||
|
payload = load_fixture("status_response.json")
|
||||||
|
|
||||||
|
assert_keys(payload, [
|
||||||
|
"type",
|
||||||
|
"timestamp",
|
||||||
|
"battery",
|
||||||
|
"temps",
|
||||||
|
"relays",
|
||||||
|
"vehicle",
|
||||||
|
"network",
|
||||||
|
"alarms",
|
||||||
|
"system",
|
||||||
|
"config",
|
||||||
|
])
|
||||||
|
assert payload["type"] == "status_response"
|
||||||
|
|
||||||
|
assert_keys(payload["battery"], [
|
||||||
|
"source",
|
||||||
|
"connected",
|
||||||
|
"soc",
|
||||||
|
"voltage",
|
||||||
|
"current",
|
||||||
|
"remaining_ah",
|
||||||
|
"capacity_ah",
|
||||||
|
"runtime_hours",
|
||||||
|
"temperature_f",
|
||||||
|
"cycle_count",
|
||||||
|
"cell_count",
|
||||||
|
"ntc_count",
|
||||||
|
"cell_voltages",
|
||||||
|
"cell_min_voltage",
|
||||||
|
"cell_max_voltage",
|
||||||
|
"cell_delta_mv",
|
||||||
|
"cells_valid",
|
||||||
|
])
|
||||||
|
assert_keys(payload["temps"][0], [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"weather",
|
||||||
|
"online",
|
||||||
|
"temperature_f",
|
||||||
|
])
|
||||||
|
assert_keys(payload["relays"][0], ["id", "name", "pin", "enabled", "state"])
|
||||||
|
assert_keys(payload["network"], [
|
||||||
|
"wifi_enabled",
|
||||||
|
"uart_connected",
|
||||||
|
"ap_enabled",
|
||||||
|
"ap_ip",
|
||||||
|
"sta_enabled",
|
||||||
|
"sta_connected",
|
||||||
|
"sta_ssid",
|
||||||
|
"sta_ip",
|
||||||
|
"saved_network_count",
|
||||||
|
"saved_networks",
|
||||||
|
])
|
||||||
|
assert_keys(payload["system"], [
|
||||||
|
"firmware_name",
|
||||||
|
"firmware_version",
|
||||||
|
"build_date",
|
||||||
|
"build_time",
|
||||||
|
"uptime_seconds",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_fixture_matches_current_config_response_shape():
|
||||||
|
payload = load_fixture("config_response.json")
|
||||||
|
|
||||||
|
assert_keys(payload, [
|
||||||
|
"device_name",
|
||||||
|
"relays",
|
||||||
|
"bms",
|
||||||
|
"temperature_sensor_count",
|
||||||
|
"temperature_sensors",
|
||||||
|
])
|
||||||
|
assert_keys(payload["relays"][0], ["id", "name", "pin", "enabled"])
|
||||||
|
assert_keys(payload["bms"], ["enabled", "name", "address", "address_type"])
|
||||||
|
assert_keys(payload["temperature_sensors"][0], [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"address",
|
||||||
|
"enabled",
|
||||||
|
"weather",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_health_and_capabilities_contracts_are_registered():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert '"health_response"' in source
|
||||||
|
assert '"capabilities_response"' in source
|
||||||
|
assert '"GET /api/v1/health"' in source
|
||||||
|
assert '"GET /api/v1/capabilities"' in source
|
||||||
|
assert '"config_backup_restore"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_backup_restore_contract_is_registered():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert 'API_V1("/config/export")' in source
|
||||||
|
assert 'API_V1("/config/import")' in source
|
||||||
|
assert '"config_export_response"' in source
|
||||||
|
assert '"password"' in source
|
||||||
|
assert '"invalid_config"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_relay_set_contract_fixtures_and_firmware_errors():
|
||||||
|
request = load_fixture("relay_set_request.json")
|
||||||
|
response = load_fixture("relay_set_response.json")
|
||||||
|
|
||||||
|
assert_keys(request, ["id", "state"])
|
||||||
|
assert_keys(response, ["type", "ok", "id", "state"])
|
||||||
|
assert response["type"] == "relay_response"
|
||||||
|
assert response["ok"] is True
|
||||||
|
|
||||||
|
source = firmware_source()
|
||||||
|
assert '"invalid_json"' in source
|
||||||
|
assert '"unknown_relay"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_temp_scan_assign_clear_contract_fixtures():
|
||||||
|
scan = load_fixture("temp_scan_response.json")
|
||||||
|
assign_request = load_fixture("temp_assign_request.json")
|
||||||
|
assign_response = load_fixture("temp_assign_response.json")
|
||||||
|
clear_request = load_fixture("temp_clear_request.json")
|
||||||
|
clear_response = load_fixture("temp_clear_response.json")
|
||||||
|
|
||||||
|
assert_keys(scan, ["type", "ok", "devices"])
|
||||||
|
assert scan["type"] == "temp_scan_response"
|
||||||
|
assert_keys(scan["devices"][0], ["index", "address"])
|
||||||
|
|
||||||
|
assert_keys(assign_request, ["id", "index"])
|
||||||
|
assert_keys(assign_response, ["device_name", "relays", "bms", "temperature_sensors"])
|
||||||
|
assert assign_response["temperature_sensors"][0]["address"]
|
||||||
|
assert assign_response["temperature_sensors"][0]["enabled"] is True
|
||||||
|
|
||||||
|
assert_keys(clear_request, ["id"])
|
||||||
|
assert_keys(clear_response, ["device_name", "relays", "bms", "temperature_sensors"])
|
||||||
|
assert clear_response["temperature_sensors"][0]["address"] == ""
|
||||||
|
assert clear_response["temperature_sensors"][0]["enabled"] is False
|
||||||
|
|
||||||
|
source = firmware_source()
|
||||||
|
assert '"unknown_temp_sensor"' in source
|
||||||
|
assert '"invalid_temp_selection"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_wifi_config_contract_fixtures():
|
||||||
|
request = load_fixture("wifi_config_request.json")
|
||||||
|
response = load_fixture("wifi_config_response.json")
|
||||||
|
|
||||||
|
assert_keys(request, ["networks"])
|
||||||
|
assert_keys(request["networks"][0], ["ssid", "password", "priority"])
|
||||||
|
assert_keys(response, ["type", "ok", "wifi"])
|
||||||
|
assert response["type"] == "wifi_config_response"
|
||||||
|
assert response["ok"] is True
|
||||||
|
assert_keys(response["wifi"], [
|
||||||
|
"ap_enabled",
|
||||||
|
"sta_enabled",
|
||||||
|
"network_count",
|
||||||
|
"active_ssid",
|
||||||
|
"sta_connected",
|
||||||
|
"ap_ip",
|
||||||
|
"sta_ip",
|
||||||
|
"networks",
|
||||||
|
])
|
||||||
|
assert_keys(response["wifi"]["networks"][0], [
|
||||||
|
"index",
|
||||||
|
"ssid",
|
||||||
|
"priority",
|
||||||
|
"password_set",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_error_fixture_and_known_error_codes_are_stable():
|
||||||
|
payload = load_fixture("error_response.json")
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert payload == {"ok": False, "error": "invalid_json"}
|
||||||
|
|
||||||
|
for error_code in [
|
||||||
|
"invalid_json",
|
||||||
|
"invalid_config",
|
||||||
|
"unknown_relay",
|
||||||
|
"unknown_temp_sensor",
|
||||||
|
"invalid_temp_selection",
|
||||||
|
"invalid_bms_selection",
|
||||||
|
"invalid_relay_route",
|
||||||
|
"missing_relay_action",
|
||||||
|
"invalid_relay_action",
|
||||||
|
]:
|
||||||
|
assert error_code in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_endpoint_supports_field_selection():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert 'server.hasArg("fields")' in source
|
||||||
|
assert 'server.arg("fields")' in source
|
||||||
|
assert 'findStatusFieldIndex' in source
|
||||||
|
assert '"invalid_field"' in source
|
||||||
|
assert 'Unknown field' in source
|
||||||
|
assert 'server.send(400, "application/json", errorOutput)' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_uses_partial_status_for_overview_polling():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert 'const STATUS_FIELDS_OVERVIEW="battery,temps,relays,vehicle,network,alarms,system";' in source
|
||||||
|
assert 'function statusUrl()' in source
|
||||||
|
assert 'api("/status?fields="+STATUS_FIELDS_OVERVIEW)' in source
|
||||||
|
assert 'fetch(statusUrl(),{cache:"no-store"})' in source
|
||||||
|
assert 'function mergeStatus(previous,next)' in source
|
||||||
|
assert 'api("/status?fields=config")' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_cargo_ap_uses_wpa2_password():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "Preferences apPrefs;" in source
|
||||||
|
assert "generateDefaultApPassword" in source
|
||||||
|
assert "validApPassword" in source
|
||||||
|
assert "loadApConfig();" in source
|
||||||
|
assert "startAccessPoint()" in source
|
||||||
|
assert "WiFi.softAP(apSsid.c_str(), apPassword.c_str())" in source
|
||||||
|
assert 'network["ap_auth"] = "wpa2";' in source
|
||||||
|
assert 'server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig);' in source
|
||||||
|
assert 'server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig);' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_cargo_ap_password_is_not_returned_by_get_config_ap():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
get_start = source.index("void handleGetApConfig()")
|
||||||
|
get_end = source.index("void handleUpdateApConfig()")
|
||||||
|
get_body = source[get_start:get_end]
|
||||||
|
|
||||||
|
assert 'response["password_set"]' in get_body
|
||||||
|
assert 'response["password"]' not in get_body
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_has_ap_settings_controls():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "Access Point" in source
|
||||||
|
assert 'id="apSsidInput"' in source
|
||||||
|
assert 'id="apPasswordInput"' in source
|
||||||
|
assert "function saveApConfig()" in source
|
||||||
|
assert 'api("/config/ap")' in source
|
||||||
|
assert "toggleApPassword" in source
|
||||||
|
assert "renderApConfig(data)" in source
|
||||||
|
assert "Dashboard auto-migration is planned but not implemented yet" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_config_page_is_grouped():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "configGroupTitle" in source
|
||||||
|
assert "Home / Starlink WiFi" in source
|
||||||
|
assert "Relay Outputs" in source
|
||||||
|
assert "Temperature Assignment" in source
|
||||||
|
assert "Maintenance" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_config_subtabs():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "function showConfigSubtab(name)" in source
|
||||||
|
assert "config-general" in source
|
||||||
|
assert "config-wifi" in source
|
||||||
|
assert "config-relays" in source
|
||||||
|
assert "config-temps" in source
|
||||||
|
assert "config-bms" in source
|
||||||
|
assert "config-maintenance" in source
|
||||||
|
assert "WiFi / AP" in source
|
||||||
|
assert "Maintenance" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_api_badge_labels_are_clear():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "API Connecting..." in source
|
||||||
|
assert "API Live" in source
|
||||||
|
assert "API Error" in source
|
||||||
|
assert 'textContent="Offline"' not in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_config_page_is_grouped():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "configGroupTitle" in source
|
||||||
|
assert "General" in source
|
||||||
|
assert "Home / Starlink WiFi" in source
|
||||||
|
assert "Relay Outputs" in source
|
||||||
|
assert "Temperature Assignment" in source
|
||||||
|
assert "BMS Config" in source
|
||||||
|
assert "Maintenance" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_config_subtabs():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "function showConfigSubtab(name)" in source
|
||||||
|
assert "config-general" in source
|
||||||
|
assert "config-wifi" in source
|
||||||
|
assert "config-relays" in source
|
||||||
|
assert "config-temps" in source
|
||||||
|
assert "config-bms" in source
|
||||||
|
assert "config-maintenance" in source
|
||||||
|
assert "WiFi / AP" in source
|
||||||
|
assert "subtabbtn active" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_cargo_oled_support_is_optional():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "#define OLED_ENABLED 0" in source
|
||||||
|
assert "#if OLED_ENABLED" in source
|
||||||
|
assert "void oledBegin()" in source
|
||||||
|
assert "void oledLoop()" in source
|
||||||
|
assert "void oledShowSetupPage()" in source
|
||||||
|
assert "OLED_I2C_ADDR = 0x3C" in source
|
||||||
|
assert "Adafruit_SSD1306" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_cargo_oled_shows_setup_and_service_pages():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert 'oledClear("Setup / AP")' in source
|
||||||
|
assert 'oledClear("Battery")' in source
|
||||||
|
assert 'oledClear("Relays")' in source
|
||||||
|
assert 'oledClear("Network")' in source
|
||||||
|
assert 'oledClear("Alarms")' in source
|
||||||
|
assert "oledShowSetupPage();" in source
|
||||||
|
assert "oledLoop();" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_gpio_pin_plan_assigns_oled_to_21_22():
|
||||||
|
config = (ROOT / "firmware" / "esp32" / "overland-controller" / "config.h").read_text()
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "#define OLED_I2C_SDA_PIN 21" in config
|
||||||
|
assert "#define OLED_I2C_SCL_PIN 22" in config
|
||||||
|
assert "#define OLED_SETUP_BUTTON_PIN 25" in config
|
||||||
|
assert "const int OLED_I2C_SDA = OLED_I2C_SDA_PIN;" in source
|
||||||
|
assert "const int OLED_I2C_SCL = OLED_I2C_SCL_PIN;" in source
|
||||||
|
assert "const int OLED_BUTTON_PIN = OLED_SETUP_BUTTON_PIN;" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_dashboard_uart_is_disabled_by_default():
|
||||||
|
config = (ROOT / "firmware" / "esp32" / "overland-controller" / "config.h").read_text()
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "#define DASHBOARD_UART_ENABLED 0" in config
|
||||||
|
assert "DASHBOARD_UART_TX_PIN 21" not in config
|
||||||
|
assert "DASHBOARD_UART_RX_PIN 22" not in config
|
||||||
|
assert "#if DASHBOARD_UART_ENABLED" in source
|
||||||
|
assert "pollDashboardUart();" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_temp_config_lookup_is_available_to_http_handlers():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "int findTempConfigIndexById(" in source
|
||||||
|
assert "configIndex = findTempConfigIndexById(id);" in source
|
||||||
|
assert "findTempConfigIndexForUart" not in source
|
||||||
|
|
||||||
|
helper_pos = source.index("int findTempConfigIndexById(")
|
||||||
|
first_uart_guard_pos = source.index("#if DASHBOARD_UART_ENABLED")
|
||||||
|
assert helper_pos < first_uart_guard_pos
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_preserves_config_input_while_polling():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "const configTouched={};" in source
|
||||||
|
assert "function markConfigFieldTouched(id)" in source
|
||||||
|
assert "function setConfigValue(id,value)" in source
|
||||||
|
assert "document.activeElement===el" in source
|
||||||
|
assert 'document.addEventListener("input"' in source
|
||||||
|
assert 'setConfigValue("apSsidInput"' in source
|
||||||
|
assert "deviceNameInput" in source
|
||||||
|
assert "bmsNameInput" in source
|
||||||
|
assert "tempEnabledCount" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_ap_config_can_be_reset_to_defaults():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "void resetApConfigToDefaults()" in source
|
||||||
|
assert 'saveApConfig("OverlandController", newPassword);' in source
|
||||||
|
assert "void handleResetApConfig()" in source
|
||||||
|
assert 'server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);' in source
|
||||||
|
assert 'api("/config/ap/reset")' in source
|
||||||
|
assert "Reset AP to Default" in source
|
||||||
|
assert "regenerated password" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_has_consistent_button_intent_styles():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "/* UI polish v2 */" in source
|
||||||
|
assert "button.save" in source
|
||||||
|
assert "button.danger" in source
|
||||||
|
assert "button.secondary" in source
|
||||||
|
assert "configGroupTitle" in source
|
||||||
|
assert "linear-gradient" in source
|
||||||
|
assert 'class="save" onclick="saveApConfig()"' in source
|
||||||
|
assert 'class="danger" onclick="resetApConfig()"' in source
|
||||||
|
assert 'class="save" onclick="saveWifi()"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_theme_v3():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "/* UI theme v3 */" in source
|
||||||
|
assert "--accent:#5f8fcf" in source
|
||||||
|
assert "@media(min-width:980px)" in source
|
||||||
|
assert "#configPage" in source
|
||||||
|
assert "grid-template-columns:190px 1fr" in source
|
||||||
|
assert "button.save" in source
|
||||||
|
assert "button.danger" in source
|
||||||
|
assert "button.secondary" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_main_pages_are_mutually_exclusive():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "/* Desktop page layout fix */" in source
|
||||||
|
assert ".page:not(.active){display:none!important}" in source
|
||||||
|
assert "#configPage.active" in source
|
||||||
|
assert "Main page navigation override" in source
|
||||||
|
assert 'overview:"overviewPage"' in source
|
||||||
|
assert 'battery:"batteryPage"' in source
|
||||||
|
assert 'config:"configPage"' in source
|
||||||
|
assert 'showConfigSubtab("general")' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_mobile_temperature_config_cards():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "/* Mobile temperature config layout */" in source
|
||||||
|
assert "tempConfigCard" in source
|
||||||
|
assert "tempConfigControls" in source
|
||||||
|
assert "Temp Slot " in source
|
||||||
|
assert "Clear Assignment" in source
|
||||||
|
assert "Weather Badge" in source
|
||||||
|
assert "@media(max-width:700px)" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_temp_config_uses_mobile_cards():
|
||||||
|
source = firmware_source()
|
||||||
|
start = source.index('const tempBox=$("tempConfigList");')
|
||||||
|
end = source.index("async function saveRelayConfig", start)
|
||||||
|
body = source[start:end]
|
||||||
|
|
||||||
|
assert "tempConfigCard" in body
|
||||||
|
assert "Temp Slot " in body
|
||||||
|
assert "Clear Assignment" in body
|
||||||
|
assert "Weather Badge" in body
|
||||||
|
assert "configrow tempConfigRow" not in body
|
||||||
|
assert "Address:" not in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_embedded_webui_formats_uptime():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "function formatUptime(totalSeconds)" in source
|
||||||
|
assert 'parts.push(days+"d")' in source
|
||||||
|
assert 'parts.push(hours+"h")' in source
|
||||||
|
assert 'parts.push(minutes+"m")' in source
|
||||||
|
assert 'parts.push(seconds+"s")' in source
|
||||||
|
assert 'formatUptime(s.uptime_seconds||0)' in source
|
||||||
|
assert '+" sec"' not in source
|
||||||
@@ -11,7 +11,7 @@ void setup() {
|
|||||||
Serial.print("Target: ");
|
Serial.print("Target: ");
|
||||||
Serial.println(TARGET_ADDRESS);
|
Serial.println(TARGET_ADDRESS);
|
||||||
|
|
||||||
NimBLEDevice::init("XterraESP32");
|
NimBLEDevice::init("OverlandController");
|
||||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||||
|
|
||||||
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);
|
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ void setup() {
|
|||||||
Serial.print("Target: ");
|
Serial.print("Target: ");
|
||||||
Serial.println(TARGET_ADDRESS);
|
Serial.println(TARGET_ADDRESS);
|
||||||
|
|
||||||
NimBLEDevice::init("XterraESP32");
|
NimBLEDevice::init("OverlandController");
|
||||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||||
|
|
||||||
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);
|
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);
|
||||||
|
|||||||
Reference in New Issue
Block a user