Added UART as a communication interface option.

This commit is contained in:
2026-01-19 21:24:35 +02:00
parent 11b98166d1
commit 094b1a9620
10 changed files with 1219 additions and 1 deletions

View File

@@ -41,7 +41,8 @@ public:
// Message source identification
enum class MessageSource {
MQTT,
WEBSOCKET
WEBSOCKET,
UART
};
struct MessageContext {

View File

@@ -33,6 +33,7 @@ CommunicationRouter::CommunicationRouter(ConfigManager& configManager,
, _wsServer(webSocket, _clientManager)
, _commandHandler(configManager, otaManager)
, _httpHandler(server, configManager)
, _uartHandler()
, _settingsServer(server, configManager, networking) {}
CommunicationRouter::~CommunicationRouter() {}
@@ -106,13 +107,27 @@ void CommunicationRouter::begin() {
_settingsServer.begin();
LOG_INFO("✅ Settings Web Server initialized at /settings");
// Initialize UART Command Handler
LOG_INFO("Setting up UART Command Handler...");
_uartHandler.begin();
_uartHandler.setCallback([this](JsonDocument& message) {
onUartMessage(message);
});
LOG_INFO("✅ UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
LOG_INFO("Communication Router initialized with modular architecture");
LOG_INFO(" • MQTT: AsyncMqttClient");
LOG_INFO(" • WebSocket: Multi-client support");
LOG_INFO(" • HTTP REST API: /api endpoints");
LOG_INFO(" • UART: External device control");
LOG_INFO(" • Settings Page: /settings");
}
void CommunicationRouter::loop() {
// Process UART incoming data
_uartHandler.loop();
}
void CommunicationRouter::setPlayerReference(Player* player) {
_player = player;
_commandHandler.setPlayerReference(player);
@@ -327,6 +342,20 @@ void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocume
LOG_DEBUG("WebSocket message from client #%u processed", clientId);
}
void CommunicationRouter::onUartMessage(JsonDocument& message) {
// Extract command for logging
String cmd = message["cmd"] | "unknown";
LOG_INFO("🔌 UART message received: cmd=%s", cmd.c_str());
// Create message context for UART
CommandHandler::MessageContext context(CommandHandler::MessageSource::UART);
// Forward to command handler
_commandHandler.processCommand(message, context);
LOG_DEBUG("UART message processed");
}
void CommunicationRouter::sendResponse(const String& response, const CommandHandler::MessageContext& context) {
if (context.source == CommandHandler::MessageSource::MQTT) {
LOG_DEBUG("↗️ Sending response via MQTT: %s", response.c_str());
@@ -334,6 +363,9 @@ void CommunicationRouter::sendResponse(const String& response, const CommandHand
} else if (context.source == CommandHandler::MessageSource::WEBSOCKET) {
LOG_DEBUG("↗️ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
_wsServer.sendToClient(context.clientId, response);
} else if (context.source == CommandHandler::MessageSource::UART) {
LOG_DEBUG("↗️ Sending response via UART: %s", response.c_str());
_uartHandler.send(response);
} else {
LOG_ERROR("❌ Unknown message source for response routing!");
}

View File

@@ -39,6 +39,7 @@
#include "../CommandHandler/CommandHandler.hpp"
#include "../ResponseBuilder/ResponseBuilder.hpp"
#include "../HTTPRequestHandler/HTTPRequestHandler.hpp"
#include "../UARTCommandHandler/UARTCommandHandler.hpp"
#include "../../ClientManager/ClientManager.hpp"
#include "../../SettingsWebServer/SettingsWebServer.hpp"
@@ -63,6 +64,7 @@ public:
~CommunicationRouter();
void begin();
void loop(); // Must be called from main loop for UART processing
void setPlayerReference(Player* player);
void setFileManagerReference(FileManager* fm);
void setTimeKeeperReference(Timekeeper* tk);
@@ -78,6 +80,7 @@ public:
// Component accessors
MQTTAsyncClient& getMQTTClient() { return _mqttClient; }
UARTCommandHandler& getUARTHandler() { return _uartHandler; }
// Broadcast methods
void broadcastStatus(const String& statusMessage);
@@ -116,11 +119,13 @@ private:
WebSocketServer _wsServer;
CommandHandler _commandHandler;
HTTPRequestHandler _httpHandler;
UARTCommandHandler _uartHandler;
SettingsWebServer _settingsServer;
// Message handlers
void onMqttMessage(const String& topic, const String& payload);
void onWebSocketMessage(uint32_t clientId, const JsonDocument& message);
void onUartMessage(JsonDocument& message);
// Response routing
void sendResponse(const String& response, const CommandHandler::MessageContext& context);

View File

@@ -0,0 +1,131 @@
/*
* UARTCOMMANDHANDLER.CPP - UART Command Handler Implementation
*/
#include "UARTCommandHandler.hpp"
#include "../../Logging/Logging.hpp"
UARTCommandHandler::UARTCommandHandler(uint8_t txPin, uint8_t rxPin, uint32_t baudRate)
: _serial(Serial2)
, _txPin(txPin)
, _rxPin(rxPin)
, _baudRate(baudRate)
, _ready(false)
, _bufferIndex(0)
, _messageCount(0)
, _errorCount(0)
, _callback(nullptr)
{
resetBuffer();
}
UARTCommandHandler::~UARTCommandHandler() {
_serial.end();
}
void UARTCommandHandler::begin() {
LOG_INFO("Initializing UART Command Handler");
LOG_INFO(" TX Pin: GPIO%d", _txPin);
LOG_INFO(" RX Pin: GPIO%d", _rxPin);
LOG_INFO(" Baud Rate: %u", _baudRate);
// Initialize Serial2 with custom pins
_serial.begin(_baudRate, SERIAL_8N1, _rxPin, _txPin);
// Clear any garbage in the buffer
while (_serial.available()) {
_serial.read();
}
_ready = true;
LOG_INFO("UART Command Handler ready");
}
void UARTCommandHandler::loop() {
if (!_ready) return;
// Process all available bytes
while (_serial.available()) {
char c = _serial.read();
// Check for message delimiter (newline)
if (c == '\n' || c == '\r') {
if (_bufferIndex > 0) {
// Null-terminate and process
_buffer[_bufferIndex] = '\0';
processLine(_buffer);
resetBuffer();
}
// Skip empty lines
continue;
}
// Add character to buffer
if (_bufferIndex < BUFFER_SIZE - 1) {
_buffer[_bufferIndex++] = c;
} else {
// Buffer overflow - discard and reset
LOG_ERROR("UART buffer overflow, discarding message");
_errorCount++;
resetBuffer();
}
}
}
void UARTCommandHandler::setCallback(MessageCallback callback) {
_callback = callback;
}
void UARTCommandHandler::send(const String& response) {
if (!_ready) {
LOG_ERROR("UART not ready, cannot send response");
return;
}
_serial.print(response);
_serial.print('\n'); // Newline delimiter
_serial.flush(); // Ensure data is sent
LOG_DEBUG("UART TX: %s", response.c_str());
}
void UARTCommandHandler::processLine(const char* line) {
LOG_DEBUG("UART RX: %s", line);
// Skip empty lines or whitespace-only
if (strlen(line) == 0) return;
// Parse JSON
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, line);
if (error) {
LOG_ERROR("UART JSON parse error: %s", error.c_str());
_errorCount++;
// Send error response back
StaticJsonDocument<256> errorDoc;
errorDoc["status"] = "ERROR";
errorDoc["type"] = "parse_error";
errorDoc["payload"] = error.c_str();
String errorResponse;
serializeJson(errorDoc, errorResponse);
send(errorResponse);
return;
}
_messageCount++;
// Invoke callback if set
if (_callback) {
_callback(doc);
} else {
LOG_WARNING("UART message received but no callback set");
}
}
void UARTCommandHandler::resetBuffer() {
_bufferIndex = 0;
memset(_buffer, 0, BUFFER_SIZE);
}

View File

@@ -0,0 +1,122 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* UARTCOMMANDHANDLER.HPP - UART Command Interface for External Control Devices
* ═══════════════════════════════════════════════════════════════════════════════════
*
* 🔌 UART COMMAND HANDLER 🔌
*
* Enables command input from external devices (LCD panels, button controllers)
* via UART serial communication. Uses newline-delimited JSON protocol.
*
* Pin Configuration:
* • TX: GPIO12
* • RX: GPIO13
* • Baud: 115200 (configurable)
*
* Protocol:
* • Newline-delimited JSON messages
* • Same command format as MQTT/WebSocket
* • Responses sent back on same UART
*
* 📋 VERSION: 1.0
* 📅 DATE: 2025-01-19
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <functional>
class UARTCommandHandler {
public:
// Default pin configuration
static constexpr uint8_t DEFAULT_TX_PIN = 12;
static constexpr uint8_t DEFAULT_RX_PIN = 13;
static constexpr uint32_t DEFAULT_BAUD_RATE = 115200;
static constexpr size_t BUFFER_SIZE = 1024;
// Message callback type - called when a complete JSON message is received
using MessageCallback = std::function<void(JsonDocument& message)>;
/**
* @brief Construct UART handler with custom pins
* @param txPin GPIO pin for TX (default: 12)
* @param rxPin GPIO pin for RX (default: 13)
* @param baudRate Baud rate (default: 115200)
*/
explicit UARTCommandHandler(uint8_t txPin = DEFAULT_TX_PIN,
uint8_t rxPin = DEFAULT_RX_PIN,
uint32_t baudRate = DEFAULT_BAUD_RATE);
~UARTCommandHandler();
/**
* @brief Initialize the UART interface
*/
void begin();
/**
* @brief Process incoming UART data (call from loop or task)
* Non-blocking - processes available bytes and returns
*/
void loop();
/**
* @brief Set callback for received messages
* @param callback Function to call with parsed JSON
*/
void setCallback(MessageCallback callback);
/**
* @brief Send a response back over UART
* @param response JSON string to send (newline appended automatically)
*/
void send(const String& response);
/**
* @brief Check if UART is initialized and ready
*/
bool isReady() const { return _ready; }
/**
* @brief Get number of messages received since boot
*/
uint32_t getMessageCount() const { return _messageCount; }
/**
* @brief Get number of parse errors since boot
*/
uint32_t getErrorCount() const { return _errorCount; }
private:
HardwareSerial& _serial;
uint8_t _txPin;
uint8_t _rxPin;
uint32_t _baudRate;
bool _ready;
// Receive buffer
char _buffer[BUFFER_SIZE];
size_t _bufferIndex;
// Statistics
uint32_t _messageCount;
uint32_t _errorCount;
// Callback
MessageCallback _callback;
/**
* @brief Process a complete line from the buffer
* @param line Null-terminated string containing the message
*/
void processLine(const char* line);
/**
* @brief Reset the receive buffer
*/
void resetBuffer();
};