From f176e0df26423c20e5abfda46bbfbb97060cab2f Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Sun, 5 Oct 2025 00:14:46 +0200 Subject: [PATCH] impl main func --- .vscode/settings.json | 14 +++ lib/hardware/hardware_rfid.cpp | 2 +- lib/hardware/hardware_serial.cpp | 205 ++++++++++++++++++++++--------- lib/hardware/hardware_serial.hpp | 130 ++++++++++++++++---- src/main.cpp | 95 +++++++------- 5 files changed, 321 insertions(+), 125 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1e8e1bc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "files.associations": { + "array": "cpp", + "deque": "cpp", + "list": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "string_view": "cpp", + "initializer_list": "cpp", + "ranges": "cpp" + } +} diff --git a/lib/hardware/hardware_rfid.cpp b/lib/hardware/hardware_rfid.cpp index 01ed88e..3a3a6e7 100644 --- a/lib/hardware/hardware_rfid.cpp +++ b/lib/hardware/hardware_rfid.cpp @@ -2,7 +2,7 @@ #include #include -//#define DEBUG_RFID +// #define DEBUG_RFID #ifdef DEBUG_RFID #define LOG(msg) Serial.println(msg) #else diff --git a/lib/hardware/hardware_serial.cpp b/lib/hardware/hardware_serial.cpp index 78e79fc..688a400 100644 --- a/lib/hardware/hardware_serial.cpp +++ b/lib/hardware/hardware_serial.cpp @@ -1,100 +1,151 @@ #include "hardware_serial.hpp" +#include + +// #define DEBUG_PROTO +#ifdef DEBUG_PROTO +#define LOG(msg) Serial.println(msg) +#else +#define LOG(msg) +#endif ProtoSerial::ProtoSerial() - : serial(nullptr), initialized(false), currentState(WAITING_FOR_LENGTH), - payloadLength(0), messageAvailable(false) { + : serial(nullptr), initialized(false), lastError{0}, callback(nullptr), + currentState(WAITING_FOR_SYNC_START), payloadLength(0), messageAvailable(false), + lastUpdate(0), lastByteTime(0) { receivedMessage = INCOMING_MESSAGE_INIT; } ProtoSerial::~ProtoSerial() { - if (serial) { - delete serial; - } + // No delete needed; serial is caller-managed } -void ProtoSerial::begin(int rxPin, int txPin, long baud) { - if (serial) { - delete serial; - } -#ifdef ESP32 - serial = new HardwareSerial(1); // Use UART 1 - serial->begin(baud, SERIAL_8N1, rxPin, txPin); -#elif defined(ESP8266) - serial = new SoftwareSerial(rxPin, txPin); - serial->begin(baud); -#endif +void ProtoSerial::begin(SerialType& serialPort) { + serial = &serialPort; initialized = true; - currentState = WAITING_FOR_LENGTH; + currentState = WAITING_FOR_SYNC_START; payloadLength = 0; messageAvailable = false; + lastError[0] = '\0'; + lastUpdate = 0; + lastByteTime = 0; + snprintf(lastError, sizeof(lastError), "Initialized ProtoSerial"); + LOG(lastError); } bool ProtoSerial::sendMessage(const OutgoingMessage& message) { - if (!initialized || !serial) { + if (!initialized || !serial || !serial->availableForWrite()) { + snprintf(lastError, sizeof(lastError), "Serial not initialized or not ready"); + LOG(lastError); return false; } uint8_t buffer[OUTGOING_MESSAGE_SIZE]; - - // Create a stream that writes to our buffer pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); // Encode the message if (!pb_encode(&stream, OUTGOING_MESSAGE_FIELDS, &message)) { - // Encoding failed + snprintf(lastError, sizeof(lastError), "Failed to encode message"); + LOG(lastError); return false; } uint16_t messageLength = stream.bytes_written; + if (messageLength > sizeof(buffer)) { + snprintf(lastError, sizeof(lastError), "Encoded message too large: %u", messageLength); + LOG(lastError); + return false; + } - // Send the length first (as two bytes, little-endian) + // Send framing: sync start, length, payload, CRC, sync end + serial->write(SYNC_START); serial->write((uint8_t*)&messageLength, sizeof(messageLength)); - - // Send the actual payload serial->write(buffer, messageLength); - - // Ensure data is sent + uint8_t crc = calculate_crc8(buffer, messageLength); + serial->write(crc); + serial->write(SYNC_END); serial->flush(); + dumpHex(buffer, messageLength, "Sent"); + snprintf(lastError, sizeof(lastError), "Sent message, length: %u, CRC: 0x%02X", messageLength, crc); + LOG(lastError); return true; } void ProtoSerial::update() { if (!initialized || !serial) { + snprintf(lastError, sizeof(lastError), "Serial not initialized"); + LOG(lastError); return; } - switch (currentState) { - case WAITING_FOR_LENGTH: { - if (serial->available() >= sizeof(payloadLength)) { - // Read the 2-byte length - serial->readBytes((uint8_t*)&payloadLength, sizeof(payloadLength)); + // Throttle updates to ~100Hz + unsigned long now = millis(); + if (now - lastUpdate < 10) return; + lastUpdate = now; - // Protect against absurdly large lengths - if (payloadLength > sizeof(payloadBuffer) || payloadLength == 0) { - // Invalid length, reset - payloadLength = 0; + // Timeout if no data received for 1 second + if (currentState != WAITING_FOR_SYNC_START && now - lastByteTime > 1000) { + snprintf(lastError, sizeof(lastError), "Receive timeout"); + LOG(lastError); + currentState = WAITING_FOR_SYNC_START; + payloadLength = 0; + } + + while (serial->available()) { + lastByteTime = now; + switch (currentState) { + case WAITING_FOR_SYNC_START: { + uint8_t byte = serial->read(); + if (byte == SYNC_START) { currentState = WAITING_FOR_LENGTH; - } else { - currentState = READING_PAYLOAD; + LOG("Received SYNC_START"); } + break; } - break; - } - - case READING_PAYLOAD: { - if (serial->available() >= payloadLength) { - // Read the payload - serial->readBytes(payloadBuffer, payloadLength); - - // Process the message - processReceivedMessage(payloadBuffer, payloadLength); - - // Reset for the next message - currentState = WAITING_FOR_LENGTH; - payloadLength = 0; + case WAITING_FOR_LENGTH: { + if (serial->available() >= sizeof(payloadLength)) { + serial->readBytes((uint8_t*)&payloadLength, sizeof(payloadLength)); + if (payloadLength > sizeof(payloadBuffer) || payloadLength == 0) { + snprintf(lastError, sizeof(lastError), "Invalid payload length: %u", payloadLength); + LOG(lastError); + currentState = WAITING_FOR_SYNC_START; + payloadLength = 0; + } else { + currentState = READING_PAYLOAD; + snprintf(lastError, sizeof(lastError), "Received length: %u", payloadLength); + LOG(lastError); + } + } + break; + } + case READING_PAYLOAD: { + if (serial->available() >= payloadLength) { + serial->readBytes(payloadBuffer, payloadLength); + currentState = READING_CRC; + } + break; + } + case READING_CRC: { + if (serial->available() >= 1) { + uint8_t received_crc = serial->read(); + uint8_t expected_crc = calculate_crc8(payloadBuffer, payloadLength); + if (received_crc != expected_crc) { + snprintf(lastError, sizeof(lastError), "CRC mismatch: received 0x%02X, expected 0x%02X", received_crc, expected_crc); + LOG(lastError); + } else if (serial->available() >= 1 && serial->read() != SYNC_END) { + snprintf(lastError, sizeof(lastError), "Missing SYNC_END"); + LOG(lastError); + } else { + dumpHex(payloadBuffer, payloadLength, "Received"); + processReceivedMessage(payloadBuffer, payloadLength); + snprintf(lastError, sizeof(lastError), "Received valid message, length: %u", payloadLength); + LOG(lastError); + } + currentState = WAITING_FOR_SYNC_START; + payloadLength = 0; + } + break; } - break; } } } @@ -110,19 +161,57 @@ const IncomingMessage& ProtoSerial::getMessage() const { void ProtoSerial::clearMessage() { messageAvailable = false; receivedMessage = INCOMING_MESSAGE_INIT; + snprintf(lastError, sizeof(lastError), "Message cleared"); + LOG(lastError); +} + +void ProtoSerial::setCallback(Callback cb) { + callback = cb; + snprintf(lastError, sizeof(lastError), "Callback set"); + LOG(lastError); } void ProtoSerial::processReceivedMessage(uint8_t* buffer, uint16_t length) { - // Reset the message receivedMessage = INCOMING_MESSAGE_INIT; - - // Create a stream from the buffer pb_istream_t stream = pb_istream_from_buffer(buffer, length); - - // Decode the message if (pb_decode(&stream, INCOMING_MESSAGE_FIELDS, &receivedMessage)) { + if (receivedMessage.which_payload == 0) { + snprintf(lastError, sizeof(lastError), "Invalid message: which_payload not set"); + LOG(lastError); + return; + } messageAvailable = true; + if (callback) { + callback(receivedMessage); + } + snprintf(lastError, sizeof(lastError), "Message decoded successfully"); + LOG(lastError); } else { - // Decoding failed, do not set messageAvailable + snprintf(lastError, sizeof(lastError), "Failed to decode message"); + LOG(lastError); } +} + +uint8_t ProtoSerial::calculate_crc8(const uint8_t* data, uint16_t len) { + uint8_t crc = 0x00; + for (uint16_t i = 0; i < len; i++) { + crc ^= data[i]; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80) crc = (crc << 1) ^ 0x31; // CRC-8 polynomial + else crc <<= 1; + } + } + return crc; +} + +void ProtoSerial::dumpHex(const uint8_t* data, uint16_t len, const char* label) { +#ifdef DEBUG_PROTO + Serial.print(label); + Serial.print(": "); + for (uint16_t i = 0; i < len; i++) { + if (i % 16 == 0) Serial.println(); + Serial.printf("%02X ", data[i]); + } + Serial.println(); +#endif } \ No newline at end of file diff --git a/lib/hardware/hardware_serial.hpp b/lib/hardware/hardware_serial.hpp index 7d627f5..c9f8f5d 100644 --- a/lib/hardware/hardware_serial.hpp +++ b/lib/hardware/hardware_serial.hpp @@ -1,12 +1,13 @@ #pragma once + #ifdef ESP32 #include typedef HardwareSerial SerialType; #define SERIAL_TYPE HardwareSerial #elif defined(ESP8266) -#include -typedef SoftwareSerial SerialType; -#define SERIAL_TYPE SoftwareSerial +#include +typedef HardwareSerial SerialType; +#define SERIAL_TYPE HardwareSerial #else #error "Unsupported platform. Only ESP32 and ESP8266 are supported." #endif @@ -28,8 +29,8 @@ typedef hardware_ControlToSensorMessage OutgoingMessage; #define OUTGOING_MESSAGE_SIZE hardware_ControlToSensorMessage_size #define INCOMING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields #define OUTGOING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields -#define INCOMING_MESSAGE_INIT hardware_SensorToControlMessage_init_default -#define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_default +#define INCOMING_MESSAGE_INIT hardware_SensorToControlMessage_init_zero +#define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero #elif defined(HARDWARE_SERIAL_ROLE_SENSOR) typedef hardware_ControlToSensorMessage IncomingMessage; typedef hardware_SensorToControlMessage OutgoingMessage; @@ -37,51 +38,132 @@ typedef hardware_SensorToControlMessage OutgoingMessage; #define OUTGOING_MESSAGE_SIZE hardware_SensorToControlMessage_size #define INCOMING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields #define OUTGOING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields -#define INCOMING_MESSAGE_INIT hardware_ControlToSensorMessage_init_default -#define OUTGOING_MESSAGE_INIT hardware_SensorToControlMessage_init_default +#define INCOMING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero +#define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero #else #error "Must define either HARDWARE_SERIAL_ROLE_CONTROL or HARDWARE_SERIAL_ROLE_SENSOR" #endif +/** + * @class ProtoSerial + * @brief Manages serial communication with Protocol Buffers for ESP32/ESP8266. + * Supports length-prefixed messages with CRC-8 checksum and sync bytes. + * Configured for SENSOR or CONTROL role via HARDWARE_SERIAL_ROLE_* macros. + * On ESP8266, uses UART0 (Serial, RX=GPIO3, TX=GPIO1). On ESP32, uses caller-provided UART. + */ class ProtoSerial { public: + using Callback = void (*)(const IncomingMessage&); ///< Callback for received messages. + + /** + * @brief Constructor. + */ ProtoSerial(); + + /** + * @brief Destructor. No-op (serial is managed by caller). + */ ~ProtoSerial(); - // Initialize with RX and TX pins - void begin(int rxPin, int txPin, long baud = 9600); + /** + * @brief Initializes serial communication with a caller-provided serial port. + * @param serialPort Reference to a configured SerialType (e.g., Serial for ESP8266, HardwareSerial for ESP32). + */ + void begin(SerialType& serialPort); - // Send a message + /** + * @brief Sends a Protocol Buffers message with sync bytes and CRC-8. + * @param message The message to send (OutgoingMessage type). + * @return True if sent successfully, false otherwise. + */ bool sendMessage(const OutgoingMessage& message); - // Check for incoming messages (non-blocking) + /** + * @brief Checks for incoming messages (non-blocking). + */ void update(); - // Check if a message has been received + /** + * @brief Checks if a message is available. + * @return True if a message is ready to be read. + */ bool hasMessage() const; - // Get the received message (only valid if hasMessage() returns true) + /** + * @brief Gets the received message. + * @return Reference to the received message (valid only if hasMessage() is true). + */ const IncomingMessage& getMessage() const; - // Clear the received message + /** + * @brief Clears the received message. + */ void clearMessage(); + /** + * @brief Sets the callback for received messages. + * @param cb Callback function to handle incoming messages. + */ + void setCallback(Callback cb); + + /** + * @brief Checks if the serial interface is initialized. + * @return True if initialized. + */ + bool isInitialized() const { return initialized; } + + /** + * @brief Gets the last error message (for debugging). + * @return Last error string or empty if none. + */ + const char* getLastError() const { return lastError; } + private: - SerialType* serial; - bool initialized; + SerialType* serial; ///< Serial interface (pointer to caller-provided SerialType). + bool initialized; ///< Tracks initialization state. + char lastError[64]; ///< Last error message (fixed-size to avoid heap issues). + Callback callback; ///< Callback for received messages. + + // Framing constants + static const uint8_t SYNC_START = 0xAA; ///< Start byte for message framing. + static const uint8_t SYNC_END = 0xBB; ///< End byte for message framing. // Receive state machine enum RxState { - WAITING_FOR_LENGTH, - READING_PAYLOAD + WAITING_FOR_SYNC_START, ///< Waiting for start byte (0xAA). + WAITING_FOR_LENGTH, ///< Waiting for 2-byte length. + READING_PAYLOAD, ///< Reading payload bytes. + READING_CRC ///< Reading CRC-8 byte. }; - RxState currentState; - uint16_t payloadLength; - uint8_t payloadBuffer[64]; // Max message size + some buffer - IncomingMessage receivedMessage; - bool messageAvailable; + RxState currentState; ///< Current state of receive state machine. + uint16_t payloadLength; ///< Length of incoming payload. + uint8_t payloadBuffer[INCOMING_MESSAGE_SIZE + 10]; ///< Buffer for payload (plus overhead). + IncomingMessage receivedMessage; ///< Decoded incoming message. + bool messageAvailable; ///< True if a message is ready. + unsigned long lastUpdate; ///< Timestamp of last update (for throttling). + unsigned long lastByteTime; ///< Timestamp of last byte received (for timeout). // Helper methods + /** + * @brief Processes a received payload and decodes it. + * @param buffer Payload buffer. + * @param length Payload length. + */ void processReceivedMessage(uint8_t* buffer, uint16_t length); -}; + /** + * @brief Calculates CRC-8 for a buffer. + * @param data Buffer to compute CRC over. + * @param len Length of buffer. + * @return CRC-8 value. + */ + uint8_t calculate_crc8(const uint8_t* data, uint16_t len); + + /** + * @brief Logs a hex dump of a buffer (debug only). + * @param data Buffer to dump. + * @param len Length of buffer. + * @param label Label for the dump. + */ + void dumpHex(const uint8_t* data, uint16_t len, const char* label); +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 5796d08..3b2414d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "hardware.pb.h" // Demo for HardwareLed @@ -9,14 +10,24 @@ HardwareLed<2> led; // Assuming NeoPixel on pin 2 (D2 on ESP8266) // Demo for HardwareRfid HardwareRfid rfid(4, 5); // SS=D2 (GPIO4), RST=D1 (GPIO5) +// Demo for HardwareSerial +ProtoSerial serial; + hardware_LedConfig configs[4]; unsigned long lastChange = 0; int currentConfig = 0; void onRfidTag(const hardware_SensorToControlMessage& msg) { - Serial.println("Callback onRfidTag triggered"); - Serial.print("RFID Tag detected: 0x"); - Serial.println(msg.payload.rfid_reading.card_id, HEX); + serial.sendMessage(msg); + // Serial.println("Callback onRfidTag triggered"); + // Serial.print("RFID Tag detected: 0x"); + // Serial.println(msg.payload.rfid_reading.card_id, HEX); +} + +void onSerialMessage(const IncomingMessage& msg) { + if (msg.which_payload == hardware_ControlToSensorMessage_led_config_tag) { + led.set(msg.payload.led_config); + } } void setup() { @@ -24,54 +35,54 @@ void setup() { led.begin(); rfid.begin(); rfid.setCallback(onRfidTag); + serial.begin(Serial); + serial.setCallback(onSerialMessage); - // Static config - configs[0] = {0}; - configs[0].brightness = 128; - configs[0].duration_ms = 0; - configs[0].which_animation_params = 3; - configs[0].animation_params.static_params.color = 0x00FF00; // Green + // // Static config + // configs[0] = {0}; + // configs[0].brightness = 128; + // configs[0].duration_ms = 0; + // configs[0].which_animation_params = 3; + // configs[0].animation_params.static_params.color = 0x00FF00; // Green - // Pulse config - configs[1] = {0}; - configs[1].brightness = 128; - configs[1].duration_ms = 0; - configs[1].which_animation_params = 4; - configs[1].animation_params.pulse_params.color = 0xFF0000; // Red - configs[1].animation_params.pulse_params.speed_ms = 500; + // // Pulse config + // configs[1] = {0}; + // configs[1].brightness = 128; + // configs[1].duration_ms = 0; + // configs[1].which_animation_params = 4; + // configs[1].animation_params.pulse_params.color = 0xFF0000; // Red + // configs[1].animation_params.pulse_params.speed_ms = 500; - // Fade config - configs[2] = {0}; - configs[2].brightness = 128; - configs[2].duration_ms = 0; - configs[2].which_animation_params = 5; - configs[2].animation_params.fade_params.colors_count = 3; - configs[2].animation_params.fade_params.colors[0] = 0xFF0000; // Red - configs[2].animation_params.fade_params.colors[1] = 0x00FF00; // Green - configs[2].animation_params.fade_params.colors[2] = 0x0000FF; // Blue - configs[2].animation_params.fade_params.speed_ms = 1000; + // // Fade config + // configs[2] = {0}; + // configs[2].brightness = 128; + // configs[2].duration_ms = 0; + // configs[2].which_animation_params = 5; + // configs[2].animation_params.fade_params.colors_count = 3; + // configs[2].animation_params.fade_params.colors[0] = 0xFF0000; // Red + // configs[2].animation_params.fade_params.colors[1] = 0x00FF00; // Green + // configs[2].animation_params.fade_params.colors[2] = 0x0000FF; // Blue + // configs[2].animation_params.fade_params.speed_ms = 1000; - // Flicker config - configs[3] = {0}; - configs[3].brightness = 128; - configs[3].duration_ms = 0; - configs[3].which_animation_params = 6; - configs[3].animation_params.flicker_params.color = 0xFFFFFF; // White - configs[3].animation_params.flicker_params.intensity = 50; + // // Flicker config + // configs[3] = {0}; + // configs[3].brightness = 128; + // configs[3].duration_ms = 0; + // configs[3].which_animation_params = 6; + // configs[3].animation_params.flicker_params.color = 0xFFFFFF; // White + // configs[3].animation_params.flicker_params.intensity = 50; - led.set(configs[0]); - Serial.println("Starting with static green"); + // led.set(configs[0]); } void loop() { led.update(); rfid.update(); + serial.update(); - if (millis() - lastChange >= 10000) { - lastChange = millis(); - currentConfig = (currentConfig + 1) % 4; - led.set(configs[currentConfig]); - Serial.print("Switched to config "); - Serial.println(currentConfig); - } + // if (millis() - lastChange >= 10000) { + // lastChange = millis(); + // currentConfig = (currentConfig + 1) % 4; + // led.set(configs[currentConfig]); + // } } \ No newline at end of file