70 Commits
Author SHA1 Message Date
nick f2966518da docs: prepare api contract for lilygo transition 2026-06-09 15:05:29 -06:00
nick 90c4490a3d docs: add pre-lilygo baseline checklist 2026-06-09 15:03:49 -06:00
nick f1683897bc docs: define cargo esp api contract 2026-06-09 15:02:44 -06:00
nick 19538781e8 docs: audit stale dashboard references and clarify DS18B20 support 2026-06-09 14:58:04 -06:00
nick cf722a7287 docs: document Waveshare 5B dashboard hardware 2026-06-09 00:54:14 -06:00
nick f5e2c5fd24 repo: archive legacy pico dashboard 2026-06-09 00:52:00 -06:00
nick 9cb06b86ff docs: clarify controller roles and load switching boundaries 2026-06-09 00:41:52 -06:00
nick 6acfa61ddb webui: format uptime duration 2026-06-08 12:28:17 -06:00
nick 8004ce86e1 release: bump firmware version to 0.5.0 2026-06-08 12:15:31 -06:00
nick fc4999b9d2 webui: render temperature settings as mobile cards 2026-06-08 11:07:48 -06:00
nick 989e0458aa test: relax mobile temp slot assertion 2026-06-08 10:53:46 -06:00
nick 094bfb33a4 webui: improve mobile temperature config layout 2026-06-08 10:40:11 -06:00
nick bd00b6e7ca webui: fix desktop config page layout 2026-06-08 09:47:07 -06:00
nick c49361488d webui: add dashboard-style theme polish 2026-06-08 09:35:46 -06:00
nick 8dfcc63688 webui: improve action button spacing 2026-06-08 09:33:09 -06:00
nick 69db0d7fe5 webui: polish settings layout and button styles 2026-06-08 09:30:37 -06:00
nick 0f7697e9dd webui: add AP reset recovery action 2026-06-08 09:22:10 -06:00
nick c43f121dc8 webui: preserve config edits during polling 2026-06-08 09:13:24 -06:00
nick 730da5482f firmware: expose temp lookup outside disabled UART block 2026-06-08 09:07:38 -06:00
nick 954bef4291 firmware: keep temp config lookup outside UART guard 2026-06-08 09:04:59 -06:00
nick 75c1d4d909 hardware: retire dashboard uart and assign oled pins 2026-06-08 09:03:24 -06:00
nick 46d52052e3 firmware: scaffold optional cargo OLED display 2026-06-08 08:45:24 -06:00
nick bc56514489 docs: remove vehicle-specific Xterra branding 2026-06-08 08:42:27 -06:00
nick d4dff6d49c webui: fix subtab css formatting 2026-06-08 08:38:11 -06:00
nick 567b84261e webui: organize config page with subtabs 2026-06-08 08:25:06 -06:00
nick dc70e4f8e5 test: remove leftover conflict marker 2026-06-08 08:16:34 -06:00
nick 9c2069dd28 Revert "webui: organize config page with subtabs"
This reverts commit 2f973a279c.
2026-06-08 08:15:33 -06:00
nick 0f7b5206b6 webui: clarify API connection badge 2026-06-08 08:09:33 -06:00
nick 2f973a279c webui: organize config page with subtabs 2026-06-08 08:01:54 -06:00
nick 40ce727986 webui: add AP settings and organize config page 2026-06-08 07:49:30 -06:00
nick b665bf1c82 wifi: add WPA2 AP setup API and pairing roadmap 2026-06-07 22:11:40 -06:00
nick 594edc3dd9 webui: add relay state badges and feedback 2026-06-07 19:37:01 -06:00
nick 4474b6d11a api: remove status cache validators 2026-06-07 19:33:37 -06:00
nick e7965f4b99 webui: use partial status polling 2026-06-07 19:13:39 -06:00
nick 512a1aa302 api: optimize status endpoint payload and caching 2026-06-07 19:04:30 -06:00
nick a721f6b539 Add ChatGPT handoff document 2026-06-07 10:40:46 -06:00
nick 6cc93667ab Add maintenance API endpoints 2026-06-07 10:31:41 -06:00
nick d87080d23f Add versioned HTTP API routes 2026-06-07 10:24:08 -06:00
nick ec39182bbd Add HTTP API contract fixtures 2026-06-07 10:15:33 -06:00
nick d9b851aa53 Clarify current HTTP API contract 2026-06-07 09:56:20 -06:00
nick f609f2ea43 Archive stale documentation 2026-06-07 09:52:24 -06:00
nick dc8340a1ec Align docs with ESP32-S3 dashboard architecture 2026-06-07 09:48:13 -06:00
nick fdf3d657b6 docs: add Codex handoff architecture docs 2026-06-07 09:33:49 -06:00
nick e1ea6a19e0 Add ESP32-S3 dashboard planning doc 2026-06-07 09:18:37 -06:00
nick a0cad804a2 Document ESP32-S3 dashboard architecture 2026-06-07 09:16:55 -06:00
nick 3a386437fa Restore two-button relay controls 2026-06-05 04:07:51 -06:00
nick 63f1d691ec Fix overview relay button handler 2026-06-05 04:03:54 -06:00
nick a2b747d741 Restore overview relay controls 2026-06-05 03:59:19 -06:00
nick 78f6a9e53e Fix temp slot enable checkbox logic 2026-06-05 03:48:21 -06:00
nick 4fe7491afa Fix dashboard temp visibility filter 2026-06-05 03:41:55 -06:00
nick a3b335e708 Hide inactive temp sensors on dashboard 2026-06-05 03:36:45 -06:00
nick 7ab75408b7 Separate temp scan results from runtime sensors 2026-06-05 03:28:50 -06:00
nick df9b49292d Show assigned temp probe addresses 2026-06-05 03:20:23 -06:00
nick f05f259523 Move temperature assignment above config 2026-06-05 03:18:30 -06:00
nick 3f02f6146e Hide assigned temp probes from scan results 2026-06-05 03:12:09 -06:00
nick 5d8afd2e4a Require assigned temp sensor addresses 2026-06-05 03:08:05 -06:00
nick 757b9acdd9 Harden temperature assignment clearing 2026-06-05 02:51:47 -06:00
nick 2f61c968a2 Fix temperature assignment clearing 2026-06-05 02:47:06 -06:00
nick 573f24e0a9 Fix weather temp checkbox persistence 2026-06-05 02:37:17 -06:00
nick a86a0b58c4 Add temperature assignment clear buttons 2026-06-05 02:31:39 -06:00
nick 47dd9a7b8b Enforce unique temperature probe assignments 2026-06-05 02:18:58 -06:00
nick 1ee73ad00d Improve temperature config checkbox layout 2026-06-05 02:06:25 -06:00
nick 261d8983df Add weather temperature sensor flag 2026-06-05 01:55:07 -06:00
nick 8a31048646 Register temperature probe API routes 2026-06-05 01:33:44 -06:00
nick 594fa3b9ce Format battery times as hours and minutes 2026-06-05 01:28:05 -06:00
nick af8de8fa1e Show charging time to full in WebUI 2026-06-05 01:11:29 -06:00
nick 175630cda2 Add project handoff and Pico MVP documentation 2026-06-05 00:41:21 -06:00
nick 0043e50533 Bump version to 0.4.0 and update documentation 2026-06-04 21:07:05 -06:00
nick 961ab3758b Add setup workflow controls to web UI 2026-06-04 20:54:48 -06:00
nick 9d506135a0 Add relay and temperature configuration to web UI 2026-06-04 20:44:31 -06:00
94 changed files with 5981 additions and 2383 deletions
+41
View File
@@ -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
View File
@@ -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
+69 -222
View File
@@ -1,260 +1,107 @@
Overland Controller
# Overland Controller
A modular ESP32 and Raspberry Pi Pico based monitoring and control system for overland vehicles, campers, trailers, van builds, and mobile power systems.
Overland Controller is a local-first vehicle power and monitoring system designed for overland, camping, and off-grid vehicle use.
The project uses a distributed architecture consisting of:
Current target vehicle:
* An ESP32 controller mounted near the battery and electrical system
* A Raspberry Pi Pico 2 W dashboard mounted in the vehicle cabin
* Local WiFi communication
* Offline-first operation
* Expandable sensor and relay support
* Open JSON APIs for future integrations
overland vehicle
The firmware itself is intended to stay vehicle-agnostic.
Project Goals
## Current Version
The goal of Overland Controller is to provide a self-hosted alternative to commercial vehicle monitoring systems.
0.5.0
The platform is designed to monitor and control:
## What It Does
* House battery systems
* Temperature sensors
* Auxiliary relays
* Vehicle telemetry
* Overland accessories
* Future automation systems
The ESP32 controller currently provides:
The system should be:
- Local WebUI
- HTTP API
- UART JSON protocol
- USB serial maintenance commands
- Relay control
- JBD/Xiaoxiang BLE BMS telemetry
- DS18B20 temperature sensor support
- Persistent configuration
- WiFi AP mode
- WiFi STA mode
- Multiple saved WiFi networks with priority
* Local-first
* Cloud independent
* Vehicle independent
* Easy to troubleshoot
* Easy to expand
## Current Hardware
Current controller:
Current Architecture
- ESP32 2-relay board
ESP32 Controller
Primary dashboard target:
The ESP32 acts as the primary control and monitoring node.
- Waveshare ESP32-S3-Touch-LCD-5
- WiFi client to the Cargo ESP32 access point
- HTTP API integration
Current responsibilities:
Optional future expansion:
* JBD/Xiaoxiang BMS monitoring
* DS18B20 temperature monitoring
* Relay control
* Configuration management
* Local web API
* WiFi Access Point
* Dashboard communications
* System status reporting
- Pi Zero for long-term logging, charts, and local integrations
## WebUI
Raspberry Pi Pico Dashboard
The embedded WebUI is served directly from the ESP32.
The Pico dashboard acts as the user interface.
Access through AP mode:
Planned responsibilities:
http://192.168.4.1
* Touchscreen dashboard
* Relay control
* Battery monitoring display
* Temperature monitoring display
* Vehicle status display
* Configuration interface
* Alarm notifications
Access through STA mode:
http://<controller-ip>
Current Features
The WebUI includes:
Battery Monitoring
- Overview tab
- Battery tab
- Config tab
Supported:
The Config tab is the preferred initial setup workflow.
* JBD / Xiaoxiang Bluetooth BMS
## Core Concepts
Available telemetry:
The ESP32 is the source of truth.
* State of charge
* Voltage
* Current
* Remaining capacity
* Total capacity
* Runtime estimates
* Temperature
* Cycle count
* Cell voltages
* Cell imbalance
* Cell count
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.
Temperature Monitoring
A future Pi Zero may be added for long-term logging or richer local integrations, but it must not become required for core control.
Supported:
## Documentation
* DS18B20 OneWire temperature sensors
Important docs:
Features:
- `docs/project-state.md`
- `docs/API.md`
- `docs/ARCHITECTURE.md`
- `docs/HARDWARE.md`
- `docs/dashboard-esp32s3.md`
- `docs/CONFIGURATION.md`
- `docs/UART_PROTOCOL.md`
- `docs/SERIAL_COMMANDS.md`
- `docs/PARITY_MATRIX.md`
- `docs/TEMP_SETUP.md`
- `docs/ROADMAP.md`
- `docs/CHANGELOG.md`
* Up to 8 sensors
* User-configurable names
* Online/offline detection
* Sensor health reporting
* JSON API reporting
## Safety
This project controls vehicle electrical loads.
Relay Control
Use:
Features:
- Proper fusing
- Proper wire gauge
- Proper terminals/ferrules
- Common ground
- Strain relief
- Testing before connecting expensive loads
* Multiple relay outputs
* User-configurable names
* API control
* Serial console control
* Dashboard control (planned)
Recommended usage:
* Fridge power relays
* Starlink power relays
* Lighting relays
* Inverter enable relays
* Accessory relays
The ESP32 relay outputs are intended to drive automotive relays rather than directly power large loads.
Configuration System
Configuration is stored locally and survives reboot.
Configurable items include:
Device
* Device name
Relays
* Relay name
* GPIO assignment
* Enabled state
Temperature Sensors
* Sensor name
* Sensor address
* Enabled state
BMS
* BMS name
* Bluetooth address
* Address type
Communication Interfaces
Serial Console
Used for:
* Initial setup
* Troubleshooting
* Configuration changes
* BLE discovery
* Diagnostic logging
See SERIAL_COMMANDS.md for details.
HTTP API
Provides:
* Status reporting
* Configuration management
* Relay control
* Dashboard integration
See API.md for details.
Current Development Status
Completed
* Configuration storage
* Relay control
* JSON status API
* BLE BMS integration
* BMS setup workflow
* DS18B20 framework
* Access Point mode
* Web server
* Dashboard communications framework
* Generic naming architecture
In Progress
* Dashboard software
* Temperature sensor assignment workflow
* Expanded configuration API
* Documentation cleanup
Planned
* Pico touchscreen dashboard
* OBD-II integration
* GPS integration
* MQTT support
* Home Assistant integration
* Historical data logging
* Graphing
* Alerting system
* OTA updates
* Multi-controller support
Design Philosophy
The project follows several core principles:
1. Local-first operation
2. No cloud dependency
3. Generic hardware naming
4. Open interfaces
5. Modular architecture
6. Expandable hardware support
7. Easy field troubleshooting
8. Vehicle-independent deployment
Documentation
* README.md — Project overview
* API.md — HTTP API documentation
* SERIAL_COMMANDS.md — Serial console commands
* HARDWARE.md — Hardware overview
* ARCHITECTURE.md — System architecture
* CHANGELOG.md — Project history
License
Private project currently under active development.
Do not connect vehicle 12V directly to ESP32 GPIO pins or dashboard GPIO pins.
+38
View File
@@ -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.
@@ -1,16 +1,17 @@
import json
class HttpClient:
def __init__(self, requests_module, base_url="http://192.168.4.1"):
self.requests = requests_module
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):
response = self.requests.get(f"{self.base_url}/status")
return response.json()
def set_relay(self, relay, enabled):
state = "on" if enabled else "off"
response = self.requests.get(f"{self.base_url}/relay/{relay}/{state}")
response = self.requests.post(
f"{self.base_url}/relay/set",
json={"id": relay, "state": enabled},
)
return response.json()
@@ -24,6 +24,8 @@ class AppState:
}
self.relays = {
"relay_1": False,
"relay_2": False,
"starlink": False,
"fridge": False,
}
@@ -51,8 +53,8 @@ class AppState:
self.network.update(message.get("network", {}))
def update_from_relay_response(self, message):
relay = message.get("relay")
enabled = message.get("enabled")
relay = message.get("relay", message.get("id"))
enabled = message.get("enabled", message.get("state"))
if relay in self.relays and enabled is not None:
self.relays[relay] = bool(enabled)
@@ -241,6 +241,7 @@ class FakeResponse:
class FakeRequests:
def __init__(self):
self.urls = []
self.posts = []
self.responses = []
def get(self, url):
@@ -251,6 +252,15 @@ class FakeRequests:
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():
from comms.http_client import HttpClient
@@ -264,7 +274,7 @@ def test_http_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["battery"]["soc"] == 82
@@ -275,9 +285,13 @@ def test_http_client_set_relay_on():
fake_requests = FakeRequests()
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}
@@ -287,9 +301,13 @@ def test_http_client_set_relay_off():
fake_requests = FakeRequests()
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}
@@ -317,7 +335,7 @@ def test_communication_service_http_fallback_status():
assert response["type"] == "status_response"
assert state.battery["soc"] == 66
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():
@@ -330,8 +348,8 @@ def test_communication_service_http_fallback_relay():
fake_requests.responses = [
{
"type": "relay_response",
"relay": "fridge",
"enabled": True,
"id": "relay_1",
"state": True,
"ok": True,
}
]
@@ -344,12 +362,16 @@ def test_communication_service_http_fallback_relay():
)
service.enable_http_fallback()
response = service.set_relay("fridge", True)
response = service.set_relay("relay_1", True)
assert response["type"] == "relay_response"
assert state.relays["fridge"] is True
assert state.relays["relay_1"] is True
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():
+806 -469
View File
File diff suppressed because it is too large Load Diff
+177 -212
View File
@@ -1,93 +1,107 @@
# 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:
- Read battery/BMS data
- Read temperature sensors
- Control relay outputs
- Read JBD/Xiaoxiang BMS data over BLE
- Read DS18B20 temperature sensors
- Control relay trigger outputs
- Store configuration
- Serve the HTTP API
- Communicate with the Pico dashboard over UART
- Maintain system status
- Handle alarms
- Serve HTTP API
- Serve local WebUI
- Run WiFi AP mode
- 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:
- Request status from ESP32
- Display battery data
- Display temperature data
- Display relay states
- Send relay commands
- Send configuration commands
- Provide setup workflows
- Operate without internet
- Native LVGL dashboard
- Connect to Cargo ESP32 AP as WiFi client
- Poll Cargo ESP32 HTTP API
- Send simple control commands through HTTP API
- Display battery, relay, temp, vehicle, and alert data
- Future CAN vehicle data
- Future transmission temperature sender display
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:
- Status requests
- Relay control
- Configuration updates
- Setup/configuration
- Relay names
- Temperature sensor names
- 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
- Troubleshooting
- Emergency configuration
- API testing
## Data Model
Avoid complex configuration on the touchscreen dashboard.
The same generic data model should be used across:
## Network
- HTTP API
- UART protocol
- Pico dashboard
Cargo ESP32 runs WPA2 AP mode continuously.
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
- 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
@@ -109,9 +123,9 @@ Temperature sensors:
temp_7
temp_8
Installation-specific names belong only in saved configuration.
Install-specific names belong in saved configuration only.
Examples of configuration names:
Examples:
Fridge
Starlink
@@ -119,33 +133,11 @@ Examples of configuration names:
Outside
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.
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:
Expected status groups:
battery
temps
@@ -156,176 +148,149 @@ ESP32 responds with the same shape as HTTP /status:
system
config
## Relay Control Flow
## Vehicle Data
Pico sends a generic relay command:
Use standard OBD-II over CAN first.
relay_1 on
relay_1 off
relay_2 on
relay_2 off
Planned standard PIDs:
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
scan_ble
scan_ble
select_bms
save_config
Phone/PWA
-> Home server HTTPS
-> MQTT/WebSocket/HTTPS bridge
-> Cargo ESP32 outbound connection
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
- 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
Target behavior:
## 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
- Home Assistant
- Grafana
- InfluxDB
- OBD-II
- CAN bus
- GPS
- Data logging
- OTA updates
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.
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
Used for recovery and direct setup
The current dashboard hardware target is the Waveshare ESP32-S3 Touch LCD 5B.
STA mode:
Selected dashboard hardware:
Connects to a configured WiFi network
Used for camp/home access from phone or laptop
- Waveshare ESP32-S3 Touch LCD 5B
- 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
Home WiFi
Shop WiFi
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.
### 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
2. Home WiFi
3. Shop WiFi
- Cargo ESP32 owns DS18B20 sensor reading.
- Cargo ESP32 exposes temperature state through the WebUI/API path.
- 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
Try configured STA networks
Connect to the first available network
Keep AP available even after STA connects
## Cargo ESP API Contract
Reason:
The Cargo ESP API contract is documented in `docs/cargo-api-contract.md`.
- Starlink may be off
- 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
The Cargo ESP32 is the source of truth for:
### 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
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.
The API should not require redesign when moving from 2 outputs to 6 outputs.
+64 -206
View File
@@ -1,208 +1,66 @@
# CHANGELOG.md
# Changelog
All notable changes to Overland Controller will be documented in this file.
The format loosely follows Keep a Changelog principles.
---
# [0.3.0] - 2026-06-04
## Major Changes
### Project Renaming
- Renamed project from Xterra Controller to Overland Controller
- Updated firmware naming
- Updated repository naming
- Updated documentation naming
- Began removal of vehicle-specific assumptions
### Generic Configuration Architecture
Removed hardcoded installation-specific values from firmware.
Firmware now uses generic identifiers:
Relays:
text id="4xvc8l" relay_1 relay_2
Temperature sensors:
text id="xq2e8v" temp_1 temp_2 temp_3 temp_4 temp_5 temp_6 temp_7 temp_8
Installation-specific names now belong in saved configuration.
### JSON Status API Improvements
Reworked status response format.
Changed from:
json id="k8l5yx" { "temps": { "outside": 72.0 } }
To:
json id="k3g0ey" { "temps": [ { "id": "temp_1", "name": "Outside", "temperature_f": 72.0 } ] }
Benefits:
- Dynamic sensor support
- Generic architecture
- Easier dashboard integration
- Better future scalability
### Relay Status Improvements
Relay reporting converted to structured arrays.
Example:
json id="wh1f5k" { "id": "relay_1", "name": "Aux Power", "state": false }
### BMS Integration
Implemented:
- JBD BLE communication
- Xiaoxiang compatibility
- Cell voltage reporting
- SOC reporting
- Current reporting
- Capacity reporting
- Temperature reporting
- Cycle count reporting
### BLE Setup Workflow
Added:
- Setup mode
- BLE device scanning
- BMS selection
- Persistent BMS storage
Known limitation:
- Some BMS devices advertise infrequently and may require multiple scans before appearing.
### Documentation
Added:
- README.md
- API.md
- SERIAL_COMMANDS.md
- HARDWARE.md
- ARCHITECTURE.md
- CHANGELOG.md
---
# [0.2.x]
## Initial Controller Development
Implemented:
- ESP32 firmware framework
- Relay control
- Configuration storage
- Access Point mode
- Web server
- UART communications
- JSON status reporting
---
# [0.1.x]
## Project Foundation
Initial proof-of-concept work.
Implemented:
- Repository structure
- ESP32 firmware skeleton
- Basic relay testing
- Initial dashboard communications concept
---
# Upcoming
## Planned for 0.4.x
### Dashboard Development
- Pico 2 W integration
- Dashboard communications
- Touchscreen support
- Relay control interface
### Temperature System
- Sensor assignment workflow
- Sensor discovery workflow
- Improved diagnostics
### Configuration System
- Expanded configuration API
- Additional runtime configuration
- Improved validation
### Documentation
- Installation guide
- Wiring guide
- Development guide
---
## Planned for 0.5.x
### Vehicle Integration
Potential support for:
- OBD-II
- ELM327
- CAN Bus
### Data Logging
Potential support for:
- Historical battery data
- Historical temperature data
- Event logging
### Integrations
Potential support for:
- MQTT
- Home Assistant
- Grafana
- InfluxDB
---
## Long-Term Roadmap
Potential future capabilities:
- GPS support
- Route logging
- Multi-controller support
- OTA updates
- Alerting system
- Mobile-friendly dashboard
- Remote monitoring
The roadmap is intentionally flexible and may change as the project evolves.
## 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
### Added
- Embedded tabbed WebUI.
- Overview, Battery, and Config tabs.
- Estimated runtime display.
- Battery detail page with BMS status, cell voltages, cell delta, capacity, remaining Ah, cycles, and battery temperature.
- WebUI setup workflow.
- Device name configuration from WebUI.
- Relay name configuration from WebUI.
- Temperature sensor name and enabled-count configuration from WebUI.
- Temperature probe scan and assignment workflow from WebUI.
- BMS enable/disable controls from WebUI.
- BMS reconnect action from WebUI/API.
- System restart action from WebUI/API.
- WiFi AP + STA mode.
- Multiple saved STA WiFi networks.
- WiFi priority and runtime reconnect behavior.
- HTTP/UART/USB serial command parity documentation.
- DS18B20 configurable temperature probe support.
- Preferred relay command endpoint: `POST /api/v1/relay/set`.
### Changed
- ESP32 remains the controller/source of truth.
- WebUI is now the preferred initial configuration workflow.
- Waveshare ESP32-S3 is the primary physical dashboard target.
- 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.
### Fixed
- Restored default NimBLE connection behavior after custom timeout caused BMS connection failures.
- Trimmed WiFi credentials from setup inputs.
- Improved WebUI organization by moving setup/configuration controls to the Config tab.
### Known Notes
- BMS connection can still temporarily block the ESP32 while attempting BLE connection if the BMS is out of range.
- WebUI BMS disable/enable controls are intended to make bench testing easier.
- DS18B20 multi-probe final install should use a proper terminal/distribution board rather than temporary Wago wiring.
+199
View File
@@ -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
View File
@@ -9,7 +9,7 @@ Configuration survives reboot.
- Keep firmware generic
- Store installation-specific names in config
- 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
## Stored Configuration
@@ -38,7 +38,7 @@ Serial command:
HTTP endpoint:
POST /config/device
POST /api/v1/config/device
## Relay Configuration
@@ -67,7 +67,7 @@ Serial commands:
HTTP endpoint:
POST /config/relay
POST /api/v1/config/relay
## Temperature Sensor Configuration
@@ -102,7 +102,7 @@ Serial commands:
HTTP endpoint:
POST /config/temp
POST /api/v1/config/temp
## BMS Configuration
@@ -127,7 +127,7 @@ Serial commands:
HTTP endpoint:
POST /config/bms
POST /api/v1/config/bms
## BMS Setup Mode
@@ -157,7 +157,7 @@ Serial command:
HTTP endpoint:
POST /config/factory-reset
POST /api/v1/config/factory-reset
After factory reset:
@@ -166,6 +166,19 @@ After factory reset:
- Temperature names return to generic defaults
- 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 is stored using ESP32 non-volatile storage.
+167 -209
View File
@@ -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 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.
The cargo ESP32 is the controller and source of truth.
Responsibilities:
- Battery monitoring
- Temperature monitoring
- Relay control
- Configuration storage
- Local API server
- Dashboard communications
- BMS BLE
- Relays
- DS18B20 temps
- WebUI
- HTTP API
- 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
- Display battery information
- Display temperatures
- Control relays
- Configuration interface
- Alarm notifications
Do not use OBD-II pin 16 for permanent dashboard power.
---
## 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
- Voltage
- Current
- Capacity
- Temperature
- Cycle count
- Cell voltages
- Cell imbalance
OBD pin 5 -> board/system ground
---
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
- Freezer compartment
- Cabin temperature
- Exterior temperature
- Electronics enclosure
- Battery compartment
## Transmission Temperature Sender
---
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 |
|---------|---------|
| relay_1 | 16 |
| relay_2 | 17 |
Implementation:
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
- Inverter enable
- Lighting circuits
- Water pump
- Air compressor
- Lights
- Spare output
---
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
- Appropriate wire gauge
- Proper grounding
- Automotive-rated connectors
The selected dashboard hardware target is the Waveshare ESP32-S3 Touch LCD 5B.
---
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:
text WiFi HTTP API MQTT
---
# 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
- Do not migrate active firmware assumptions until the board is physically available.
- Bench test relay boot behavior before connecting to WUPP or vehicle loads.
- 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.
+15 -10
View File
@@ -12,14 +12,18 @@ Control surfaces:
| Feature | USB Serial | UART JSON | HTTP API | Notes |
|---|---:|---:|---:|---|
| Status | Yes | Yes | Yes | `/status` equals `status_request` |
| Config view | Yes | Yes | Yes | `/config` equals `config_request` |
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /relay/set` |
| Device name config | Yes | Yes | Yes | `devicename`, `config_device`, `/config/device` |
| Relay config | Yes | Yes | Yes | `relayname`, `config_relay`, `/config/relay` |
| Temperature config | Yes | Yes | Yes | `tempname`, `config_temp`, `/config/temp` |
| BMS config | Yes | Yes | Yes | `bmsname`/`bmsaddr`, `config_bms`, `/config/bms` |
| Save config | Yes | Yes | Yes | `save`, `save_config`, `/config/save` |
| Health | No | No | Yes | `/api/v1/health` is lightweight HTTP liveness |
| Capabilities | No | No | Yes | `/api/v1/capabilities` supports client discovery |
| Status | Yes | Yes | Yes | `/api/v1/status` equals `status_request` |
| Config view | Yes | Yes | Yes | `/api/v1/config` equals `config_request` |
| Config export | No | No | Yes | `/api/v1/config/export` includes WiFi passwords |
| Config import | No | No | Yes | `/api/v1/config/import` persists restored config |
| Relay control | Yes | Yes | Yes | Preferred HTTP endpoint is `POST /api/v1/relay/set` |
| 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 |
| BMS setup enter | Yes | Yes | Yes | Setup pauses normal BMS reconnect behavior |
| 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:
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/off
@@ -107,3 +111,4 @@ Future additions must be implemented on:
before parity is considered maintained.
- Temperature sensor config/status parity now includes the `weather` boolean flag for outside-air display selection.
+23 -4
View File
@@ -12,15 +12,17 @@ This folder contains project documentation for Overland Controller.
## Project Tracking
- `CHATGPT_HANDOFF.md` - Concise assistant handoff with current status, goals, constraints, and validation notes
- `project-state.md` - Current project state
- `roadmap.md` - Roadmap and future plans
- `hardware-status.md` - Hardware status and bring-up notes
- `decisions.md` - Architecture and implementation decisions
## Technical References
- `protocol.md` - Dashboard/controller protocol
- `wiring.md` - Wiring notes and plans
- `dashboard-esp32s3.md` - Current Waveshare ESP32-S3 dashboard plan
- `CONFIGURATION.md` - Configuration model and setup concepts
- `TEMP_SETUP.md` - DS18B20 temperature sensor setup
- `UART_PROTOCOL.md` - Optional UART diagnostics protocol
## 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.
## Parity
- `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
View File
@@ -1,8 +1,10 @@
# 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
@@ -41,7 +43,7 @@ Temperature sensors:
## Status Request
Pico sends:
Client sends:
{"type":"status_request"}
@@ -49,7 +51,7 @@ ESP32 responds with the same shape as HTTP /status.
## Relay Control
Pico sends:
Client sends:
{"type":"set_relay","relay":"relay_1","enabled":true}
@@ -59,7 +61,7 @@ ESP32 responds:
## Config Request
Pico sends:
Client sends:
{"type":"config_request"}
@@ -69,37 +71,37 @@ ESP32 responds:
## Device Name Update
Pico sends:
Client sends:
{"type":"config_device","device_name":"Overland Controller"}
## Relay Config Update
Pico sends:
Client sends:
{"type":"config_relay","id":"relay_1","name":"Aux Power","enabled":true}
## Temperature Sensor Config Update
Pico sends:
Client sends:
{"type":"config_temp","id":"temp_1","name":"Cabin","address":"","enabled":true}
## 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"}
## Save Config
Pico sends:
Client sends:
{"type":"save_config"}
## Factory Reset
Pico sends:
Client sends:
{"type":"factory_reset"}
@@ -156,14 +158,14 @@ The UART protocol should eventually support the same management capabilities as
## Notes
- UART is the preferred dashboard/control path.
- HTTP is useful for setup, debugging, and future web UI.
- HTTP is the preferred dashboard/control path.
- UART is useful for diagnostics and alternate clients.
- 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
Pico sends:
Client sends:
{"type":"wifi_request"}
@@ -173,19 +175,19 @@ ESP32 responds:
## WiFi Config Update
Pico sends:
Client sends:
{"type":"config_wifi","ssid":"Starlink","password":"password_here"}
## WiFi Connect
Pico sends:
Client sends:
{"type":"wifi_connect"}
## WiFi Clear
Pico sends:
Client sends:
{"type":"wifi_clear"}
@@ -239,7 +241,7 @@ Lower priority numbers are tried first.
## Scan Temperature Sensors
Pico sends:
Client sends:
{"type":"scan_temps"}
@@ -249,7 +251,7 @@ ESP32 responds:
## Assign Temperature Sensor
Pico sends:
Client sends:
{"type":"assign_temp","id":"temp_1","index":1}
@@ -259,10 +261,33 @@ Alternative:
## Clear Temperature Sensor
Pico sends:
Client sends:
{"type":"clear_temp","id":"temp_1"}
Alternative:
{"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
+2 -2
View File
@@ -1,4 +1,4 @@
# Xterra Overland Dashboard - Codex Handoff
# Overland Controller - Codex Handoff
## Repository
@@ -27,7 +27,7 @@ Before making any code changes:
# 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:
@@ -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
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
@@ -1,10 +1,10 @@
# ESP32 ↔ Pico Communication Protocol
# Legacy UART Communication Protocol
## 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.
@@ -14,7 +14,7 @@ RS-485/MAX3485 is fallback-only and is not currently planned.
* Baud: 115200
* Format: 8N1
* Framing: newline-delimited JSON
* Direction: Pico dashboard ↔ ESP32 cargo controller
* Direction: UART client ↔ ESP32 cargo controller
## Message Rules
@@ -28,7 +28,7 @@ Example:
Responses should also be one JSON object followed by a newline.
## Pico → ESP32 Messages
## Client → ESP32 Messages
### Status Request
@@ -68,7 +68,7 @@ Expected response:
}
```
## ESP32 → Pico Messages
## ESP32 → Client Messages
### Status Response
@@ -1,16 +1,22 @@
# 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
- Raspberry Pi Pico 2 W dashboard
- CAT5/CAT6 cable between rear controller and dash
- 12V-to-5V buck converter in the dash enclosure
- `docs/HARDWARE.md`
- `docs/dashboard-esp32s3.md`
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
- JBD/Xiaoxiang BMS over BLE
- External automotive relays for high-current loads
- External fused automotive relays or contactors for major loads
---
@@ -547,3 +553,111 @@ Suggested fuse allocation:
Spare optional
External automotive relays remain a good upgrade path for higher-current loads.
---
## Current Relay Switching Decision
For the current Starlink Mini and fridge use case, the built-in ESP32 relay board relays are the planned switching method.
Use positive-side switching:
Battery +
|
Fuse
|
Relay COM
|
Relay NO
|
Load +
Load -
|
Ground bus
External automotive relays are reserved for future higher-current loads.
## DS18B20 Final Install Direction
Temporary Wago wiring proved one probe, but final install should use a proper terminal/distribution board.
Recommended:
3.3V bus
DATA bus
GND bus
With:
4.7k resistor between DATA and 3.3V
Only three wires need to return to the ESP32:
3.3V
GPIO4
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
+1 -1
View File
@@ -2,7 +2,7 @@
## 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:
+1 -1
View File
@@ -4,7 +4,7 @@
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.
+185
View File
@@ -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.
+108
View File
@@ -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
+205
View File
@@ -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
View File
@@ -20,38 +20,45 @@ Reason:
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:
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:
- Simple wiring
- Deterministic
- No WiFi dependency
- Works offline
- Easier to debug electrically
- Matches the WebUI and future integration contract
- Avoids a dedicated dashboard data cable
- Supports the Waveshare ESP32-S3 dashboard target
- Keeps the dashboard a simple client
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
The same status/config model should be used across:
- UART
- HTTP
- Simulator
- Future MQTT/Home Assistant integrations
- Optional UART diagnostics
Reason:
@@ -177,3 +184,13 @@ Cloud dashboards and remote access are not current priorities.
Reason:
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.
+54
View File
@@ -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
+199 -332
View File
@@ -1,398 +1,265 @@
# Project State
Last Updated: June 2026
Last updated: 2026-06-07
## Project Name
## Version
Overland Controller
Current firmware version:
## Current Status
0.5.0
Phase 1 is functional.
## Current Architecture
The ESP32 controller can:
The project is now an ESP32-based overland controller with a lightweight embedded WebUI.
- Host a local WiFi access point
- Serve a web interface
- Expose an HTTP API
- Read JBD/Xiaoxiang BLE BMS data
- Persist configuration
- Factory reset configuration
- Control relay outputs
- Report relay status
- Report BMS status
- Report temperature sensor status
- Communicate over UART
- Support BLE setup mode for BMS discovery
Core controller:
The Pico dashboard hardware has not yet been brought online.
ESP32 relay board
---
Current dashboard plan:
## Hardware Owned
Waveshare ESP32-S3-Touch-LCD-5 over WiFi/HTTP
### ESP32 Controller
Optional future expansion:
Owned and operational.
Pi Zero for long-term logging, charts, and local integrations
Current hardware:
## ESP32 Responsibilities
- ESP32 relay board
- 2 relay outputs
The ESP32 is the source of truth for:
### Dashboard
- Relay control
- JBD/Xiaoxiang BLE BMS telemetry
- DS18B20 temperature sensors
- Persistent configuration
- HTTP API
- Embedded WebUI
- WiFi AP recovery network
- WiFi STA client mode
- Optional UART JSON protocol for diagnostics or alternate clients
Owned but not yet integrated.
## Current Working Features
Current hardware:
- BMS live data from Greta/JBD BMS
- Relay control
- Embedded phone-friendly WebUI
- AP mode at `192.168.4.1`
- STA mode on saved WiFi networks
- Multiple saved WiFi networks with priority
- WiFi reconnect/failover behavior
- DS18B20 scan/assign/config workflow
- One DS18B20 probe verified working
- Generic relay IDs
- Generic temperature IDs
- Config-driven relay names
- Config-driven temperature names
- WebUI setup workflow
- HTTP API
- UART JSON protocol
- USB serial maintenance commands
- Raspberry Pi Pico 2 W
## WebUI Tabs
Potential display:
### Overview
- 3.5" touchscreen expansion board
Shows:
### Vehicle
Current target vehicle:
- Nissan Xterra
The firmware itself should remain vehicle-agnostic.
---
## BMS Status
Working.
Current battery:
- JBD / Xiaoxiang BLE BMS
Current telemetry:
- SOC
- Battery SOC
- Voltage
- Current
- Estimated runtime / charging time-to-full
- Temperature sensors
- Relay controls
- Network status
- Alarm summary
### Battery
Shows:
- BMS online/offline
- Source
- Remaining Ah
- Capacity
- Temperature
- Capacity Ah
- Battery temperature
- Cycle count
- Cell voltages
- Cell count
- Cell delta
- Individual cell voltages
- BMS enable/disable controls
Configuration is stored in persistent settings.
### Config
---
Supports:
## Relay Status
Working.
Current relay outputs:
relay_1
relay_2
Current installation-specific names:
Starlink
Fridge
These names are configuration only.
No relay names are intended to be hardcoded.
---
## Temperature Sensor Status
Partially complete.
Framework exists.
Current status:
- Configuration exists
- Status reporting exists
- Naming exists
Not yet completed:
- Physical DS18B20 integration
- Sensor discovery workflow
- Sensor assignment workflow
Planned support:
Up to 8 DS18B20 sensors.
---
## Configuration Status
Persistent configuration works.
Stored settings include:
- Device name
- Relay names
- Relay enabled state
- Temperature sensor names
- Temperature sensor enabled state
- BMS name
- BMS address
- BMS address type
Factory reset is functional.
---
## HTTP API Status
Working.
Implemented:
- Status endpoint
- Config endpoint
- Relay control
- Configuration updates
- Save configuration
- Device name editing
- BMS name/address/address type editing
- BMS reconnect
- Temperature probe scan
- Temperature probe assignment
- Relay renaming
- Temperature sensor enabled count
- Temperature sensor renaming
- WiFi network configuration
- Restart
- Factory reset
Documentation:
## Hardware Direction
See:
Current rear enclosure target:
docs/api.md
- ESP32 relay board
- Compact fuse block
- Ground bus
- CAT6 keystone
- DS18B20 terminal/distribution board
- Built-in relay outputs for Starlink Mini and fridge
---
Relay strategy:
## UART Status
ESP32 relay board output
-> external fused automotive relay or contactor trigger
-> fused load circuit
Partially complete.
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.
Current UART capabilities:
## Temperature Sensor Direction
- Status requests
- Dashboard communications
- Basic command support
DS18B20 bus:
Missing goal:
ESP32 GPIO4 = data
ESP32 3.3V = VCC
ESP32 GND = ground
UART should eventually expose every operation available through HTTP and serial.
Required:
Target:
One 4.7k pull-up resistor between data and 3.3V
UART becomes the primary dashboard integration interface.
Final install should use a terminal/distribution board.
---
## Simulator Status
Working.
Simulator provides:
- Fake ESP32 controller
- Fake Pico dashboard
- Test data generation
- Protocol validation
Useful for development before hardware is connected.
---
## Documentation Status
Primary documents:
README.md
HARDWARE.md
docs/project-state.md
docs/architecture.md
docs/api.md
docs/protocol.md
docs/roadmap.md
docs/decisions.md
docs/SERIAL_COMMANDS.md
Older planning/design documents may be archived later.
---
## Highest Priority Next Tasks
### Priority 1
UART parity.
Goal:
Anything possible through serial or HTTP should also be possible through UART.
Required additions:
- config_request
- config_device
- config_relay
- config_temp
- config_bms
- save_config
- factory_reset
- enter_bms_setup
- scan_ble
- select_bms
- exit_bms_setup
### Priority 2
DS18B20 support.
Needed:
- Scan sensors
- Assign sensors
- Save assignments
### Priority 3
Pico hardware bring-up.
Needed:
- UART communication
- Display status screen
- Relay control screen
---
## Long-Term Vision
A vehicle-agnostic overland monitoring and control platform.
Core principles:
- Local-first
- No cloud required
- Generic hardware model
- Generic API model
- Generic UART model
- Dashboard optional
- Vehicle independent
Target use cases:
- Overland vehicles
- Campers
- Trailers
- RV electrical systems
- Mobile command systems
- Off-grid battery monitoring
---
## Current Direction Update
The project direction is now:
ESP32:
Controller
Source of truth
BMS telemetry
Temperature sensors
Relay control
Configuration storage
HTTP API
Lightweight embedded web dashboard
WiFi AP recovery network
Pico 2 W:
Instant-on physical dashboard
UART client
Touchscreen UI
Local control surface
Optional future Pi Zero 2 W:
Advanced web app
Historical logging
Graphs
MQTT/Home Assistant bridge
Advanced integrations
The base system should remain ESP32 + Pico.
A Pi Zero may be added later as an optional expansion module, not a required component.
---
## Networking Direction
## Network Direction
Current:
ESP32 AP mode
http://192.168.4.1
- AP always available
- STA attempts saved networks by priority
- AP remains available even when STA is connected
Planned:
Examples:
ESP32 AP + STA mode
Priority 1: Starlink
Priority 2: Home WiFi
Expected future behavior:
## Next Recommended Work
AP always remains enabled
ESP32 attempts to join configured WiFi networks
Multiple saved networks are tried in priority order
1. Verify WebUI setup workflow after fresh flash.
2. Clean up DS18B20 physical distribution hardware.
3. Test 2-4 temperature probes after distribution board is installed.
4. Start Waveshare ESP32-S3 dashboard MVP over WiFi/HTTP.
5. Add config backup/restore.
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.
Example network priority:
Recent notes:
1. Starlink
2. Home WiFi
3. Shop WiFi
- 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.
This allows:
- Direct connection when camping without Starlink
- Phone access over Starlink at camp
- Phone/laptop access over home WiFi in the driveway
- Recovery access even when STA setup fails
## Dashboard Architecture Update
---
Primary dashboard architecture is now:
## Recent Design Decisions
Cargo ESP32 Controller
→ WiFi AP/STA
→ HTTP API
→ WebUI
The onboard ESP32 relays may be acceptable for low-current loads such as:
Dashboard Client:
→ Waveshare 5" ESP32-S3 Display
→ Connects to cargo ESP32 over WiFi
→ Consumes existing HTTP API
→ Planned LVGL-based dashboard UI
Starlink Mini
12V fridge
small accessories
There is no planned Pico dashboard path. Existing Pico/UART files are legacy artifacts unless explicitly revived later.
Reason:
UART JSON remains optional for diagnostics or alternate clients. New dashboard work should target the Waveshare ESP32-S3 display and the HTTP API.
- Starlink Mini current draw is modest
- Typical 12V fridge current is modest
- Smaller enclosure is a priority
External automotive relays remain recommended for higher-current loads or future expansion.
## Current communications and GPIO decision
The current enclosure concept includes:
Active dashboard communication is WiFi/HTTP using `/api/v1`.
- ESP32 relay board
- Small fuse block
- Ground bus
- CAT6 keystone
- CAT5/CAT6 run to dash
- 12V feed to dash
- Buck converter in dash enclosure
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.
The dash power plan is:
Current Cargo ESP32 GPIO plan:
Rear fused 12V
-> CAT5/CAT6 blue pair
-> dash buck converter
-> Pico VSYS
- 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.
+53
View File
@@ -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 -363
View File
@@ -1,406 +1,161 @@
# 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
Goals:
- 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
- LVGL project scaffold
- WiFi client to Cargo ESP32 AP
- Poll /status
- Display battery, relays, temps, and alarms
- Send relay commands
- Handle offline/stale/failed-command states
Initial dashboard screens:
UI direction:
- Main overview
- Battery screen
- Temperature screen
- Relay control screen
- Settings screen
- Dark theme
- Large readable text
- Circular SOC gauge
- Card-based layout
- 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
- Configure relay names
- Configure temperature names
- Configure BMS
- Run BLE scan workflow
- Save config
- Factory reset
- Install dedicated sender
- Read through exposed ADC or ADS1115 over I2C
- Convert resistance to temp with lookup interpolation
- Add alert thresholds
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
Possible inputs:
- Ignition signal
- Vehicle battery voltage
- OBD-II
- CAN bus
- microSD logging
- GPS
- OTA updates
- Dashboard alert sounds
- More vehicle PIDs
- Historical charts
Potential data:
- Ignition state
- Engine RPM
- Coolant temperature
- Vehicle speed
- Fuel level
- Diagnostic trouble codes
- Location
- Trip data
## AP Security, OLED Setup, and Dashboard Pairing
## 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
- Log temperature history
- Log relay events
- Log alarm events
- Show historical charts
- Add 128x64 SSD1306 OLED support on the Cargo ESP32.
- Show AP SSID/password/IP during first boot and setup mode.
- Show critical operational state during normal operation:
- SOC
- 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
- SD card
- External service
- Future InfluxDB integration
- Dashboard ESP32-S3 connects as a client to the Cargo ESP AP.
- Cargo ESP remains the network/configuration authority.
- When AP credentials are changed from the phone WebUI, Cargo stages the new credentials.
- 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
- Home Assistant
- Grafana
- InfluxDB
- Node-RED
## Cargo OLED firmware foundation
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
- Stable API
- Stable UART protocol
- Installation guide
- Wiring guide
- Build guide
- Example configurations
- Versioned releases
Current Cargo ESP32 GPIO plan:
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:
- OTA firmware updates
- Multi-controller support
- Expansion modules
- Water tank monitoring
- Inverter monitoring
- Solar charge controller monitoring
- Starlink diagnostics
- 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.
- BNO085/BNO086 tilt sensor
- CAN sniffing beyond standard OBD-II polling
- Fault-code support
- Standalone dashboard mode
- Additional cargo outputs
- Custom PCB
- Smart high-side switching
- WUPP-style fused distribution module
- MQTT/Home Assistant/Grafana/InfluxDB integrations
+64
View File
@@ -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),
"Temperature " + String(i + 1),
"",
i < appConfig.tempSensorCount
i < appConfig.tempSensorCount,
false
};
}
@@ -90,6 +91,10 @@ void loadConfig() {
(prefix + "enabled").c_str(),
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);
@@ -124,6 +129,7 @@ void saveConfig() {
preferences.putString((prefix + "name").c_str(), appConfig.tempSensors[i].name);
preferences.putString((prefix + "addr").c_str(), appConfig.tempSensors[i].address);
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);
@@ -17,6 +17,7 @@ struct TempSensorConfig {
String name;
String address;
bool enabled;
bool weather;
};
struct BmsConfig {
+8 -3
View File
@@ -12,9 +12,14 @@
// Ignition Sense
#define IGNITION_PIN 34
// UART over CAT5 to Pico dashboard
#define DASHBOARD_UART_TX_PIN 21
#define DASHBOARD_UART_RX_PIN 22
// Cargo OLED service/setup display
#define OLED_I2C_SDA_PIN 21
#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
// RS-485/MAX3485 is fallback only and not currently planned.
File diff suppressed because it is too large Load Diff
+76 -19
View File
@@ -15,6 +15,9 @@ DeviceAddress sensorAddresses[MAX_TEMP_SENSORS];
String sensorAddressStrings[MAX_TEMP_SENSORS];
int sensorCount = 0;
int tempScanResultIndexes[MAX_TEMP_SENSORS];
int tempScanResultCount = 0;
float cToF(float c) {
return (c * 9.0 / 5.0) + 32.0;
}
@@ -52,6 +55,26 @@ bool addressMatches(DeviceAddress address, const String& expected) {
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() {
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
sensors.tempsF[i] = -127.0;
@@ -68,29 +91,48 @@ int scanTempSensors() {
sensorCount = MAX_TEMP_SENSORS;
}
tempScanResultCount = 0;
for (int i = 0; i < MAX_TEMP_SENSORS; i++) {
sensorAddressStrings[i] = "";
tempScanResultIndexes[i] = -1;
}
for (int i = 0; i < sensorCount; i++) {
if (ds18b20.getAddress(sensorAddresses[i], i)) {
sensorAddressStrings[i] = addressToString(sensorAddresses[i]);
if (!ds18b20.getAddress(sensorAddresses[i], i)) {
continue;
}
String addressString = addressToString(sensorAddresses[i]);
sensorAddressStrings[i] = addressString;
if (isTempAddressAssigned(addressString)) {
continue;
}
tempScanResultIndexes[tempScanResultCount] = i;
tempScanResultCount++;
}
return sensorCount;
return tempScanResultCount;
}
int getTempScanResultCount() {
return sensorCount;
return tempScanResultCount;
}
String getTempScanResultAddress(int index) {
if (index < 0 || index >= sensorCount) {
if (index < 0 || index >= tempScanResultCount) {
return "";
}
return sensorAddressStrings[index];
int physicalIndex = tempScanResultIndexes[index];
if (physicalIndex < 0 || physicalIndex >= sensorCount) {
return "";
}
return sensorAddressStrings[physicalIndex];
}
void printSensorAddresses() {
@@ -128,17 +170,8 @@ void updateSensors() {
String configuredAddress = appConfig.tempSensors[configIndex].address;
if (configuredAddress.length() == 0) {
// Backward-compatible behavior:
// if no address is assigned, use physical scan order.
if (configIndex < sensorCount) {
float tempC = ds18b20.getTempC(sensorAddresses[configIndex]);
if (validTempC(tempC)) {
sensors.tempsF[configIndex] = cToF(tempC);
sensors.online[configIndex] = true;
}
}
sensors.tempsF[configIndex] = -127.0;
sensors.online[configIndex] = false;
continue;
}
@@ -164,11 +197,32 @@ bool assignTempSensorByIndex(int configIndex, int scanIndex) {
return false;
}
if (scanIndex < 0 || scanIndex >= sensorCount) {
if (scanIndex < 0 || scanIndex >= tempScanResultCount) {
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;
return true;
@@ -181,6 +235,9 @@ bool clearTempSensor(int configIndex) {
appConfig.tempSensors[configIndex].address = "";
appConfig.tempSensors[configIndex].enabled = false;
appConfig.tempSensors[configIndex].weather = false;
sensors.tempsF[configIndex] = -127.0;
sensors.online[configIndex] = false;
return true;
}
@@ -1,4 +1,4 @@
#pragma once
#define FIRMWARE_VERSION "0.3.0"
#define FIRMWARE_VERSION "0.5.0"
#define FIRMWARE_NAME "overland-controller"
+5
View File
@@ -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
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
UART over CAT5
@@ -1,5 +1,7 @@
# 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 17 Relay 2 (Fridge)
@@ -1,5 +1,7 @@
# Pico Pinout (Planned)
Legacy note: no Pico dashboard is currently planned. This file is retained only as historical/reference material.
## Display
3.5 inch SPI capacitive touchscreen
@@ -1,5 +1,7 @@
# 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
Blue +5V
+3 -6
View File
@@ -2,15 +2,13 @@
## Ordered
- Raspberry Pi Pico 2 W
- ESP32 2-Channel Relay Board
- 3.5" SPI Capacitive Touchscreen
## Required
- 4x DS18B20 Waterproof Sensors
- 12V→5V Buck Converter
- CAT5 Cable
- Waveshare ESP32-S3-Touch-LCD-5 dashboard
- Fused ACC switched 12V dashboard power
- Bosch Relay Sockets
- 30A/40A Bosch Relays
- Automotive Fuse Block
@@ -18,8 +16,7 @@
## Future / Optional Fallback
- 2x MAX3485 RS-485 Modules, only if UART over CAT5 proves unreliable
- Pi Zero 2 W
- Pi Zero for long-term logging and local integrations
- GPS Module
- ELM327 OBD-II Interface
- CAN Interface
View File
View File
+1 -20
View File
@@ -3,26 +3,7 @@ import sys
COMMANDS = [
[sys.executable, "-m", "py_compile",
"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"],
[sys.executable, "-m", "pytest", "tests/test_http_api_contract.py"],
]
+1
View File
@@ -1 +1,2 @@
flask
pytest
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Xterra Dashboard Simulator</title>
<title>Overland Controller Simulator</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
@@ -23,7 +23,7 @@
<main>
<header>
<h1>Xterra Dashboard</h1>
<h1>Overland Controller</h1>
<div id="statusLine">UART: -- | WiFi: --</div>
</header>
+33
View File
@@ -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
View File
@@ -0,0 +1,4 @@
{
"ok": false,
"error": "invalid_json"
}
+4
View File
@@ -0,0 +1,4 @@
{
"id": "relay_1",
"state": true
}
+6
View File
@@ -0,0 +1,6 @@
{
"type": "relay_response",
"ok": true,
"id": "relay_1",
"state": true
}
+113
View File
@@ -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
}
]
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"id": "temp_1",
"index": 1
}
+20
View File
@@ -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
}
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"id": "temp_1"
}
+20
View File
@@ -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
View File
@@ -0,0 +1,10 @@
{
"type": "temp_scan_response",
"ok": true,
"devices": [
{
"index": 1,
"address": "28ff001122334455"
}
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"networks": [
{
"ssid": "Starlink",
"password": "example-password",
"priority": 1
}
]
}
+21
View File
@@ -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
}
]
}
}
+566
View File
@@ -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.println(TARGET_ADDRESS);
NimBLEDevice::init("XterraESP32");
NimBLEDevice::init("OverlandController");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);
@@ -44,7 +44,7 @@ void setup() {
Serial.print("Target: ");
Serial.println(TARGET_ADDRESS);
NimBLEDevice::init("XterraESP32");
NimBLEDevice::init("OverlandController");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEAddress address(std::string(TARGET_ADDRESS), BLE_ADDR_PUBLIC);