#pragma once #ifdef ESP32 #include typedef HardwareSerial SerialType; #define SERIAL_TYPE HardwareSerial #elif defined(ESP8266) #include typedef HardwareSerial SerialType; #define SERIAL_TYPE HardwareSerial #else #error "Unsupported platform. Only ESP32 and ESP8266 are supported." #endif #include #include #include "hardware.pb.h" // Define the role of this device // Uncomment one of the following defines to set the role //#define HARDWARE_SERIAL_ROLE_CONTROL #define HARDWARE_SERIAL_ROLE_SENSOR // Based on role, define the message types #if defined(HARDWARE_SERIAL_ROLE_CONTROL) typedef hardware_SensorToControlMessage IncomingMessage; typedef hardware_ControlToSensorMessage OutgoingMessage; #define INCOMING_MESSAGE_SIZE hardware_SensorToControlMessage_size #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_zero #define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero #elif defined(HARDWARE_SERIAL_ROLE_SENSOR) typedef hardware_ControlToSensorMessage IncomingMessage; typedef hardware_SensorToControlMessage OutgoingMessage; #define INCOMING_MESSAGE_SIZE hardware_ControlToSensorMessage_size #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_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(); /** * @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); /** * @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); /** * @brief Checks for incoming messages (non-blocking). */ void update(); /** * @brief Checks if a message is available. * @return True if a message is ready to be read. */ bool hasMessage() const; /** * @brief Gets the received message. * @return Reference to the received message (valid only if hasMessage() is true). */ const IncomingMessage& getMessage() const; /** * @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; ///< 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_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; ///< 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); };