Normalize primary communication around UART

This commit is contained in:
root
2026-06-03 02:08:19 -06:00
parent 08a8275a29
commit b494a98de4
15 changed files with 64 additions and 54 deletions
+8
View File
@@ -0,0 +1,8 @@
run:
python3 simulator/app.py
venv:
python3 -m venv .venv
install:
. .venv/bin/activate && pip install -r simulator/requirements.txt
+1 -1
View File
@@ -17,4 +17,4 @@ A custom monitoring and control system for a Nissan Xterra using:
3. ESP32 communications
4. Temperature sensors
5. JBD/Xiaoxiang BMS integration
6. Logging and dashboards
6. Future integrations / optional logging
+3 -3
View File
@@ -4,7 +4,7 @@
The ESP32 acts as the system controller and provides status and control endpoints.
Primary communication is RS-485.
Primary communication is UART over CAT5. WiFi HTTP API is the backup communication path.
WiFi HTTP API is provided for:
@@ -48,7 +48,7 @@ Example:
"network": {
"wifi_enabled": false,
"rs485_connected": true
"uart_connected": true
}
}
```
@@ -167,7 +167,7 @@ Example:
```json
{
"wifi_enabled": false,
"rs485_connected": true,
"uart_connected": true,
"starlink_enabled": false
}
```
+3 -3
View File
@@ -19,7 +19,7 @@ The system provides:
- Raspberry Pi Pico 2 W
- 3.5" SPI Capacitive Touchscreen
- RS-485 communications
- UART-over-CAT5 communications
- WiFi backup communications
### Responsibilities
@@ -36,7 +36,7 @@ The system provides:
### Hardware
- ESP32 Relay Controller
- RS-485 Interface
- UART-over-CAT5 interface
- House Battery Interface
- DS18B20 Temperature Sensor Bus
- Bosch Relay Drivers
@@ -52,7 +52,7 @@ The system provides:
## Communications
Primary:
- RS-485
- UART over CAT5
Backup:
- WiFi HTTP API
+2 -2
View File
@@ -52,14 +52,14 @@ Future:
## System Screen
Displays:
- RS-485 Status
- UART Status
- WiFi Status
- Message Counts
- Latency
- Packet Loss
Testing:
- RS-485 Disconnect
- UART Disconnect
- Sensor Fault Simulation
- Ignition Simulation
+2 -2
View File
@@ -8,7 +8,7 @@ Features:
- Dashboard UI
- Alarm System
- Sensor Simulation
- RS-485 Simulation
- UART Communication Simulation
- Protocol Layer
## Phase 2 — Pico Dashboard
@@ -26,7 +26,7 @@ Status: Pending
Features:
- Relay Control
- RS-485 Communications
- UART Communications
- Configuration Storage
## Phase 4 — DS18B20 Sensors
+5 -4
View File
@@ -12,7 +12,8 @@
// Ignition Sense
#define IGNITION_PIN 34
// Future RS485
#define RS485_TX_PIN 21
#define RS485_RX_PIN 22
#define RS485_DE_PIN 23
// UART over CAT5 to Pico dashboard
#define UART_TX_PIN 21
#define UART_RX_PIN 22
// RS-485/MAX3485 fallback only; not currently planned
@@ -50,7 +50,7 @@ void handleStatus() {
JsonObject network = doc.createNestedObject("network");
network["wifi_enabled"] = true;
network["rs485_connected"] = true;
network["uart_connected"] = true;
String output;
serializeJsonPretty(doc, output);
+2 -2
View File
@@ -8,7 +8,6 @@
## Required
- 2x MAX3485 RS-485 Modules
- 4x DS18B20 Waterproof Sensors
- 12V→5V Buck Converter
- CAT5 Cable
@@ -17,8 +16,9 @@
- Automotive Fuse Block
- Assorted ATC Fuses
## Future
## Future / Optional Fallback
- 2x MAX3485 RS-485 Modules, only if UART over CAT5 proves unreliable
- Pi Zero 2 W
- GPS Module
- ELM327 OBD-II Interface
+4 -3
View File
@@ -5,9 +5,10 @@ GPIO 17 Relay 2 (Fridge)
GPIO 4 DS18B20 Bus
GPIO 21 RS-485 TX
GPIO 22 RS-485 RX
GPIO 23 RS-485 DE/RE
GPIO 21 UART TX to Pico over CAT5
GPIO 22 UART RX from Pico over CAT5
RS-485/MAX3485 is fallback only and not currently planned.
GPIO 34 Ignition Sense
+8 -8
View File
@@ -96,16 +96,16 @@ def comms():
return jsonify(pico.get_comms())
@app.route("/comms/rs485/disconnect", methods=["POST"])
def disconnect_rs485():
pico.disconnect_rs485()
return jsonify({"success": True, "rs485_connected": False})
@app.route("/comms/uart/disconnect", methods=["POST"])
def disconnect_uart():
pico.disconnect_uart()
return jsonify({"success": True, "uart_connected": False})
@app.route("/comms/rs485/restore", methods=["POST"])
def restore_rs485():
pico.restore_rs485()
return jsonify({"success": True, "rs485_connected": True})
@app.route("/comms/uart/restore", methods=["POST"])
def restore_uart():
pico.restore_uart()
return jsonify({"success": True, "uart_connected": True})
@app.route("/comms/latency", methods=["POST"])
+1 -1
View File
@@ -198,7 +198,7 @@ class ESP32Simulator:
"network": {
"wifi_enabled": starlink_on or wifi_override_active,
"wifi_override_active": wifi_override_active,
"rs485_connected": True,
"uart_connected": True,
"starlink_enabled": starlink_on
},
+9 -9
View File
@@ -7,14 +7,14 @@ from protocol import (
toggle_ignition_request,
toggle_sensor_fault_request,
)
from transport import RS485Transport
from transport import UARTTransport
class PicoSimulator:
def __init__(self, controller):
self.transport = RS485Transport(controller)
self.transport = UARTTransport(controller)
self.last_status = None
self.primary_link = "rs485"
self.primary_link = "uart"
self.backup_link_available = True
self.messages_sent = 0
self.messages_received = 0
@@ -40,7 +40,7 @@ class PicoSimulator:
if response.get("type") == "error":
if self.last_status:
self.last_status["network"]["rs485_connected"] = False
self.last_status["network"]["uart_connected"] = False
self.last_status["network"]["communication_lost"] = True
self.add_comms_to_status(self.last_status)
return self.last_status
@@ -53,7 +53,7 @@ class PicoSimulator:
"sensor_health": {},
"relays": {},
"network": {
"rs485_connected": False,
"uart_connected": False,
"communication_lost": True,
"wifi_enabled": False,
"wifi_override_active": False,
@@ -63,7 +63,7 @@ class PicoSimulator:
}
self.last_status = response["data"]
self.last_status["network"]["rs485_connected"] = self.transport.connected
self.last_status["network"]["uart_connected"] = self.transport.connected
self.last_status["network"]["communication_lost"] = False
self.add_comms_to_status(self.last_status)
return self.last_status
@@ -87,10 +87,10 @@ class PicoSimulator:
def toggle_sensor_fault(self, sensor):
return self.send_message(toggle_sensor_fault_request(sensor))
def disconnect_rs485(self):
def disconnect_uart(self):
self.transport.disconnect()
def restore_rs485(self):
def restore_uart(self):
self.transport.restore()
def set_latency(self, latency_ms):
@@ -103,7 +103,7 @@ class PicoSimulator:
return {
"primary": self.primary_link,
"backup_available": self.backup_link_available,
"rs485_connected": self.transport.connected,
"uart_connected": self.transport.connected,
"messages_sent": self.messages_sent,
"messages_received": self.messages_received,
"last_message_time": self.last_message_time,
+12 -12
View File
@@ -24,7 +24,7 @@
<main>
<header>
<h1>Xterra Dashboard</h1>
<div id="statusLine">RS-485: -- | WiFi: --</div>
<div id="statusLine">UART: -- | WiFi: --</div>
</header>
<section id="dashboard" class="screen active">
@@ -82,7 +82,7 @@
<section id="system" class="screen">
<h2>System</h2>
<div class="detail-list">
<div>RS-485 <strong id="sysRs485">--</strong></div>
<div>UART <strong id="sysUart">--</strong></div>
<div>WiFi <strong id="sysWifi">--</strong></div>
<div>WiFi Override <strong id="sysWifiOverride">--</strong></div>
<div>Ignition <strong id="sysIgnition">--</strong></div>
@@ -101,8 +101,8 @@
<div>Packet Loss <strong id="commsPacketLoss">--</strong></div>
</div>
<button onclick="disconnectRs485()">Disconnect RS-485</button>
<button onclick="restoreRs485()">Restore RS-485</button>
<button onclick="disconnectUart()">Disconnect UART</button>
<button onclick="restoreUart()">Restore UART</button>
<div class="settings-list">
<label>
@@ -235,7 +235,7 @@ function checkAlarms(data) {
const warnings = [];
if (data.network.communication_lost) {
warnings.push('Communication lost: RS-485 disconnected');
warnings.push('Communication lost: UART disconnected');
}
for (const [name, healthy] of Object.entries(data.sensor_health || {})) {
@@ -347,7 +347,7 @@ async function loadStatus() {
relayState = data.relays;
document.getElementById('statusLine').textContent =
`RS-485: ${data.network.rs485_connected ? 'Connected' : 'Lost'} | WiFi: ${data.network.wifi_enabled ? 'Enabled' : 'Disabled'}`;
`UART: ${data.network.uart_connected ? 'Connected' : 'Lost'} | WiFi: ${data.network.wifi_enabled ? 'Enabled' : 'Disabled'}`;
if (data.network.communication_lost) {
document.body.classList.add('comms-lost');
@@ -393,12 +393,12 @@ async function loadStatus() {
document.getElementById('starlinkBtn').textContent = `Starlink: ${onOff(data.relays.starlink)}`;
document.getElementById('fridgeBtn').textContent = `Fridge: ${onOff(data.relays.fridge)}`;
document.getElementById('sysRs485').textContent = data.network.rs485_connected ? 'Connected' : 'Disconnected';
document.getElementById('sysUart').textContent = data.network.uart_connected ? 'Connected' : 'Disconnected';
document.getElementById('sysWifi').textContent = data.network.wifi_enabled ? 'Enabled' : 'Disabled';
document.getElementById('sysWifiOverride').textContent = data.network.wifi_override_active ? 'Active' : 'Inactive';
document.getElementById('sysIgnition').textContent = data.vehicle?.ignition_on ? 'ON' : 'OFF';
document.getElementById('commsPrimary').textContent = data.network.rs485_connected ? 'RS-485' : 'RS-485 LOST';
document.getElementById('commsPrimary').textContent = data.network.uart_connected ? 'UART' : 'UART LOST';
document.getElementById('commsBackup').textContent = 'WiFi';
document.getElementById('commsSent').textContent = data.network.messages_sent ?? '--';
document.getElementById('commsReceived').textContent = data.network.messages_received ?? '--';
@@ -466,8 +466,8 @@ async function toggleIgnition() {
loadStatus();
}
async function disconnectRs485() {
await fetch('/comms/rs485/disconnect', {
async function disconnectUart() {
await fetch('/comms/uart/disconnect', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({})
@@ -476,8 +476,8 @@ async function disconnectRs485() {
loadStatus();
}
async function restoreRs485() {
await fetch('/comms/rs485/restore', {
async function restoreUart() {
await fetch('/comms/uart/restore', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({})
+3 -3
View File
@@ -2,7 +2,7 @@ import random
import time
class RS485Transport:
class UARTTransport:
def __init__(self, controller):
self.controller = controller
self.connected = True
@@ -14,7 +14,7 @@ class RS485Transport:
return {
"type": "error",
"success": False,
"error": "RS-485 disconnected"
"error": "UART disconnected"
}
if self.latency_ms > 0:
@@ -25,7 +25,7 @@ class RS485Transport:
return {
"type": "error",
"success": False,
"error": "RS-485 packet lost"
"error": "UART packet lost"
}
return self.controller.handle_message(message)