Normalize primary communication around UART
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
run:
|
||||
python3 simulator/app.py
|
||||
|
||||
venv:
|
||||
python3 -m venv .venv
|
||||
|
||||
install:
|
||||
. .venv/bin/activate && pip install -r simulator/requirements.txt
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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"])
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user