Files
2026-06-03 02:21:21 -06:00

273 lines
6.7 KiB
Arduino

#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include "config.h"
#include "protocol.h"
#include "relays.h"
#include "sensors.h"
WebServer server(80);
HardwareSerial DashboardSerial(2);
String uartLineBuffer;
float batterySOC = 82.0;
float batteryVoltage = 13.2;
float batteryCurrent = -6.4;
float batteryRemainingAh = 82.0;
float batteryRuntimeHours = 12.0;
float batteryTemp = 76.0;
void buildStatusDocument(JsonDocument& doc) {
doc["type"] = MSG_STATUS_RESPONSE;
doc["timestamp"] = millis();
JsonObject battery = doc.createNestedObject("battery");
battery["soc"] = batterySOC;
battery["voltage"] = batteryVoltage;
battery["current"] = batteryCurrent;
battery["remaining_ah"] = batteryRemainingAh;
battery["runtime_hours"] = batteryRuntimeHours;
battery["temperature_f"] = batteryTemp;
JsonObject temps = doc.createNestedObject("temps");
if (sensors.fridgeZone1Online) {
temps["fridge_zone_1"] = sensors.fridgeZone1;
} else {
temps["fridge_zone_1"] = nullptr;
}
if (sensors.fridgeZone2Online) {
temps["fridge_zone_2"] = sensors.fridgeZone2;
} else {
temps["fridge_zone_2"] = nullptr;
}
if (sensors.rearSeatOnline) {
temps["rear_seat"] = sensors.rearSeat;
} else {
temps["rear_seat"] = nullptr;
}
if (sensors.outsideAirOnline) {
temps["outside"] = sensors.outsideAir;
} else {
temps["outside"] = nullptr;
}
JsonObject sensorHealth = doc.createNestedObject("sensor_health");
sensorHealth["fridge_zone_1"] = sensors.fridgeZone1Online;
sensorHealth["fridge_zone_2"] = sensors.fridgeZone2Online;
sensorHealth["rear_seat"] = sensors.rearSeatOnline;
sensorHealth["outside"] = sensors.outsideAirOnline;
JsonObject relayObj = doc.createNestedObject("relays");
relayObj["starlink"] = relays.starlink;
relayObj["fridge"] = relays.fridge;
JsonObject vehicle = doc.createNestedObject("vehicle");
vehicle["ignition_on"] = digitalRead(IGNITION_PIN);
JsonObject network = doc.createNestedObject("network");
network["wifi_enabled"] = true;
network["uart_connected"] = true;
}
void sendStatus(Stream& output, bool pretty = false) {
DynamicJsonDocument doc(2048);
buildStatusDocument(doc);
if (pretty) {
serializeJsonPretty(doc, output);
} else {
serializeJson(doc, output);
}
output.println();
}
void sendError(Stream& output, const char* message) {
DynamicJsonDocument doc(256);
doc["type"] = MSG_ERROR;
doc["message"] = message;
serializeJson(doc, output);
output.println();
}
bool setRelayByName(const char* relayName, bool enabled) {
if (strcmp(relayName, "starlink") == 0) {
relays.starlink = enabled;
} else if (strcmp(relayName, "fridge") == 0) {
relays.fridge = enabled;
} else {
return false;
}
updateRelayOutputs();
return true;
}
void sendRelayResponse(Stream& output, const char* relayName, bool enabled) {
DynamicJsonDocument doc(256);
doc["type"] = MSG_RELAY_RESPONSE;
doc["relay"] = relayName;
doc["enabled"] = enabled;
doc["ok"] = true;
serializeJson(doc, output);
output.println();
}
void handleUartMessage(const String& line) {
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, line);
if (error) {
sendError(DashboardSerial, "invalid_json");
return;
}
const char* type = doc["type"] | "";
if (strcmp(type, MSG_STATUS_REQUEST) == 0) {
sendStatus(DashboardSerial);
return;
}
if (strcmp(type, MSG_SET_RELAY) == 0) {
const char* relayName = doc["relay"] | "";
bool enabled = doc["enabled"] | false;
if (!setRelayByName(relayName, enabled)) {
sendError(DashboardSerial, "unknown_relay");
return;
}
sendRelayResponse(DashboardSerial, relayName, enabled);
return;
}
sendError(DashboardSerial, "unknown_message_type");
}
void pollDashboardUart() {
while (DashboardSerial.available()) {
char c = DashboardSerial.read();
if (c == '\n') {
uartLineBuffer.trim();
if (uartLineBuffer.length() > 0) {
handleUartMessage(uartLineBuffer);
}
uartLineBuffer = "";
} else if (c != '\r') {
uartLineBuffer += c;
if (uartLineBuffer.length() > 512) {
uartLineBuffer = "";
sendError(DashboardSerial, "message_too_long");
}
}
}
}
void handleStatus() {
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "application/json", "");
sendStatus(server.client(), true);
}
void handleRelayHttp(const char* relayName, bool enabled) {
if (!setRelayByName(relayName, enabled)) {
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
return;
}
DynamicJsonDocument doc(256);
doc["type"] = MSG_RELAY_RESPONSE;
doc["relay"] = relayName;
doc["enabled"] = enabled;
doc["ok"] = true;
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
}
void handleStarlinkOn() {
handleRelayHttp("starlink", true);
}
void handleStarlinkOff() {
handleRelayHttp("starlink", false);
}
void handleFridgeOn() {
handleRelayHttp("fridge", true);
}
void handleFridgeOff() {
handleRelayHttp("fridge", false);
}
void setup() {
Serial.begin(115200);
DashboardSerial.begin(
DASHBOARD_UART_BAUD,
SERIAL_8N1,
DASHBOARD_UART_RX_PIN,
DASHBOARD_UART_TX_PIN
);
Serial.println();
Serial.println("==================================");
Serial.println("Xterra Controller Booting");
Serial.println("==================================");
pinMode(IGNITION_PIN, INPUT);
initRelays();
initSensors();
WiFi.mode(WIFI_AP);
bool apResult = WiFi.softAP("XterraController");
if (apResult) {
Serial.println("AP Started");
Serial.print("IP: ");
Serial.println(WiFi.softAPIP());
}
server.on("/status", handleStatus);
server.on("/relay/starlink/on", handleStarlinkOn);
server.on("/relay/starlink/off", handleStarlinkOff);
server.on("/relay/fridge/on", handleFridgeOn);
server.on("/relay/fridge/off", handleFridgeOff);
server.begin();
Serial.println("Web Server Started");
Serial.println("Dashboard UART Started");
}
void loop() {
server.handleClient();
pollDashboardUart();
static unsigned long lastSensorUpdate = 0;
if (millis() - lastSensorUpdate > 5000) {
updateSensors();
lastSensorUpdate = millis();
Serial.println("Sensor Update");
}
}