impl main func

This commit is contained in:
2025-10-05 00:14:46 +02:00
parent 7902518002
commit f176e0df26
5 changed files with 321 additions and 125 deletions
+14
View File
@@ -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"
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <SPI.h> #include <SPI.h>
//#define DEBUG_RFID // #define DEBUG_RFID
#ifdef DEBUG_RFID #ifdef DEBUG_RFID
#define LOG(msg) Serial.println(msg) #define LOG(msg) Serial.println(msg)
#else #else
+147 -58
View File
@@ -1,100 +1,151 @@
#include "hardware_serial.hpp" #include "hardware_serial.hpp"
#include <Arduino.h>
// #define DEBUG_PROTO
#ifdef DEBUG_PROTO
#define LOG(msg) Serial.println(msg)
#else
#define LOG(msg)
#endif
ProtoSerial::ProtoSerial() ProtoSerial::ProtoSerial()
: serial(nullptr), initialized(false), currentState(WAITING_FOR_LENGTH), : serial(nullptr), initialized(false), lastError{0}, callback(nullptr),
payloadLength(0), messageAvailable(false) { currentState(WAITING_FOR_SYNC_START), payloadLength(0), messageAvailable(false),
lastUpdate(0), lastByteTime(0) {
receivedMessage = INCOMING_MESSAGE_INIT; receivedMessage = INCOMING_MESSAGE_INIT;
} }
ProtoSerial::~ProtoSerial() { ProtoSerial::~ProtoSerial() {
if (serial) { // No delete needed; serial is caller-managed
delete serial;
}
} }
void ProtoSerial::begin(int rxPin, int txPin, long baud) { void ProtoSerial::begin(SerialType& serialPort) {
if (serial) { serial = &serialPort;
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
initialized = true; initialized = true;
currentState = WAITING_FOR_LENGTH; currentState = WAITING_FOR_SYNC_START;
payloadLength = 0; payloadLength = 0;
messageAvailable = false; messageAvailable = false;
lastError[0] = '\0';
lastUpdate = 0;
lastByteTime = 0;
snprintf(lastError, sizeof(lastError), "Initialized ProtoSerial");
LOG(lastError);
} }
bool ProtoSerial::sendMessage(const OutgoingMessage& message) { 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; return false;
} }
uint8_t buffer[OUTGOING_MESSAGE_SIZE]; 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)); pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encode the message // Encode the message
if (!pb_encode(&stream, OUTGOING_MESSAGE_FIELDS, &message)) { if (!pb_encode(&stream, OUTGOING_MESSAGE_FIELDS, &message)) {
// Encoding failed snprintf(lastError, sizeof(lastError), "Failed to encode message");
LOG(lastError);
return false; return false;
} }
uint16_t messageLength = stream.bytes_written; 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)); serial->write((uint8_t*)&messageLength, sizeof(messageLength));
// Send the actual payload
serial->write(buffer, messageLength); serial->write(buffer, messageLength);
uint8_t crc = calculate_crc8(buffer, messageLength);
// Ensure data is sent serial->write(crc);
serial->write(SYNC_END);
serial->flush(); serial->flush();
dumpHex(buffer, messageLength, "Sent");
snprintf(lastError, sizeof(lastError), "Sent message, length: %u, CRC: 0x%02X", messageLength, crc);
LOG(lastError);
return true; return true;
} }
void ProtoSerial::update() { void ProtoSerial::update() {
if (!initialized || !serial) { if (!initialized || !serial) {
snprintf(lastError, sizeof(lastError), "Serial not initialized");
LOG(lastError);
return; return;
} }
switch (currentState) { // Throttle updates to ~100Hz
case WAITING_FOR_LENGTH: { unsigned long now = millis();
if (serial->available() >= sizeof(payloadLength)) { if (now - lastUpdate < 10) return;
// Read the 2-byte length lastUpdate = now;
serial->readBytes((uint8_t*)&payloadLength, sizeof(payloadLength));
// Protect against absurdly large lengths // Timeout if no data received for 1 second
if (payloadLength > sizeof(payloadBuffer) || payloadLength == 0) { if (currentState != WAITING_FOR_SYNC_START && now - lastByteTime > 1000) {
// Invalid length, reset snprintf(lastError, sizeof(lastError), "Receive timeout");
payloadLength = 0; 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; currentState = WAITING_FOR_LENGTH;
} else { LOG("Received SYNC_START");
currentState = READING_PAYLOAD;
} }
break;
} }
break; case WAITING_FOR_LENGTH: {
} if (serial->available() >= sizeof(payloadLength)) {
serial->readBytes((uint8_t*)&payloadLength, sizeof(payloadLength));
case READING_PAYLOAD: { if (payloadLength > sizeof(payloadBuffer) || payloadLength == 0) {
if (serial->available() >= payloadLength) { snprintf(lastError, sizeof(lastError), "Invalid payload length: %u", payloadLength);
// Read the payload LOG(lastError);
serial->readBytes(payloadBuffer, payloadLength); currentState = WAITING_FOR_SYNC_START;
payloadLength = 0;
// Process the message } else {
processReceivedMessage(payloadBuffer, payloadLength); currentState = READING_PAYLOAD;
snprintf(lastError, sizeof(lastError), "Received length: %u", payloadLength);
// Reset for the next message LOG(lastError);
currentState = WAITING_FOR_LENGTH; }
payloadLength = 0; }
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() { void ProtoSerial::clearMessage() {
messageAvailable = false; messageAvailable = false;
receivedMessage = INCOMING_MESSAGE_INIT; 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) { void ProtoSerial::processReceivedMessage(uint8_t* buffer, uint16_t length) {
// Reset the message
receivedMessage = INCOMING_MESSAGE_INIT; receivedMessage = INCOMING_MESSAGE_INIT;
// Create a stream from the buffer
pb_istream_t stream = pb_istream_from_buffer(buffer, length); pb_istream_t stream = pb_istream_from_buffer(buffer, length);
// Decode the message
if (pb_decode(&stream, INCOMING_MESSAGE_FIELDS, &receivedMessage)) { 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; messageAvailable = true;
if (callback) {
callback(receivedMessage);
}
snprintf(lastError, sizeof(lastError), "Message decoded successfully");
LOG(lastError);
} else { } 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
} }
+106 -24
View File
@@ -1,12 +1,13 @@
#pragma once #pragma once
#ifdef ESP32 #ifdef ESP32
#include <HardwareSerial.h> #include <HardwareSerial.h>
typedef HardwareSerial SerialType; typedef HardwareSerial SerialType;
#define SERIAL_TYPE HardwareSerial #define SERIAL_TYPE HardwareSerial
#elif defined(ESP8266) #elif defined(ESP8266)
#include <SoftwareSerial.h> #include <HardwareSerial.h>
typedef SoftwareSerial SerialType; typedef HardwareSerial SerialType;
#define SERIAL_TYPE SoftwareSerial #define SERIAL_TYPE HardwareSerial
#else #else
#error "Unsupported platform. Only ESP32 and ESP8266 are supported." #error "Unsupported platform. Only ESP32 and ESP8266 are supported."
#endif #endif
@@ -28,8 +29,8 @@ typedef hardware_ControlToSensorMessage OutgoingMessage;
#define OUTGOING_MESSAGE_SIZE hardware_ControlToSensorMessage_size #define OUTGOING_MESSAGE_SIZE hardware_ControlToSensorMessage_size
#define INCOMING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields #define INCOMING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields
#define OUTGOING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields #define OUTGOING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields
#define INCOMING_MESSAGE_INIT hardware_SensorToControlMessage_init_default #define INCOMING_MESSAGE_INIT hardware_SensorToControlMessage_init_zero
#define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_default #define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero
#elif defined(HARDWARE_SERIAL_ROLE_SENSOR) #elif defined(HARDWARE_SERIAL_ROLE_SENSOR)
typedef hardware_ControlToSensorMessage IncomingMessage; typedef hardware_ControlToSensorMessage IncomingMessage;
typedef hardware_SensorToControlMessage OutgoingMessage; typedef hardware_SensorToControlMessage OutgoingMessage;
@@ -37,51 +38,132 @@ typedef hardware_SensorToControlMessage OutgoingMessage;
#define OUTGOING_MESSAGE_SIZE hardware_SensorToControlMessage_size #define OUTGOING_MESSAGE_SIZE hardware_SensorToControlMessage_size
#define INCOMING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields #define INCOMING_MESSAGE_FIELDS hardware_ControlToSensorMessage_fields
#define OUTGOING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields #define OUTGOING_MESSAGE_FIELDS hardware_SensorToControlMessage_fields
#define INCOMING_MESSAGE_INIT hardware_ControlToSensorMessage_init_default #define INCOMING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero
#define OUTGOING_MESSAGE_INIT hardware_SensorToControlMessage_init_default #define OUTGOING_MESSAGE_INIT hardware_ControlToSensorMessage_init_zero
#else #else
#error "Must define either HARDWARE_SERIAL_ROLE_CONTROL or HARDWARE_SERIAL_ROLE_SENSOR" #error "Must define either HARDWARE_SERIAL_ROLE_CONTROL or HARDWARE_SERIAL_ROLE_SENSOR"
#endif #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 { class ProtoSerial {
public: public:
using Callback = void (*)(const IncomingMessage&); ///< Callback for received messages.
/**
* @brief Constructor.
*/
ProtoSerial(); ProtoSerial();
/**
* @brief Destructor. No-op (serial is managed by caller).
*/
~ProtoSerial(); ~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); bool sendMessage(const OutgoingMessage& message);
// Check for incoming messages (non-blocking) /**
* @brief Checks for incoming messages (non-blocking).
*/
void update(); 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; 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; const IncomingMessage& getMessage() const;
// Clear the received message /**
* @brief Clears the received message.
*/
void clearMessage(); 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: private:
SerialType* serial; SerialType* serial; ///< Serial interface (pointer to caller-provided SerialType).
bool initialized; 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 // Receive state machine
enum RxState { enum RxState {
WAITING_FOR_LENGTH, WAITING_FOR_SYNC_START, ///< Waiting for start byte (0xAA).
READING_PAYLOAD WAITING_FOR_LENGTH, ///< Waiting for 2-byte length.
READING_PAYLOAD, ///< Reading payload bytes.
READING_CRC ///< Reading CRC-8 byte.
}; };
RxState currentState; RxState currentState; ///< Current state of receive state machine.
uint16_t payloadLength; uint16_t payloadLength; ///< Length of incoming payload.
uint8_t payloadBuffer[64]; // Max message size + some buffer uint8_t payloadBuffer[INCOMING_MESSAGE_SIZE + 10]; ///< Buffer for payload (plus overhead).
IncomingMessage receivedMessage; IncomingMessage receivedMessage; ///< Decoded incoming message.
bool messageAvailable; 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 // 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); 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);
};
+53 -42
View File
@@ -1,6 +1,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <hardware_led.hpp> #include <hardware_led.hpp>
#include <hardware_rfid.hpp> #include <hardware_rfid.hpp>
#include <hardware_serial.hpp>
#include "hardware.pb.h" #include "hardware.pb.h"
// Demo for HardwareLed // Demo for HardwareLed
@@ -9,14 +10,24 @@ HardwareLed<2> led; // Assuming NeoPixel on pin 2 (D2 on ESP8266)
// Demo for HardwareRfid // Demo for HardwareRfid
HardwareRfid rfid(4, 5); // SS=D2 (GPIO4), RST=D1 (GPIO5) HardwareRfid rfid(4, 5); // SS=D2 (GPIO4), RST=D1 (GPIO5)
// Demo for HardwareSerial
ProtoSerial serial;
hardware_LedConfig configs[4]; hardware_LedConfig configs[4];
unsigned long lastChange = 0; unsigned long lastChange = 0;
int currentConfig = 0; int currentConfig = 0;
void onRfidTag(const hardware_SensorToControlMessage& msg) { void onRfidTag(const hardware_SensorToControlMessage& msg) {
Serial.println("Callback onRfidTag triggered"); serial.sendMessage(msg);
Serial.print("RFID Tag detected: 0x"); // Serial.println("Callback onRfidTag triggered");
Serial.println(msg.payload.rfid_reading.card_id, HEX); // 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() { void setup() {
@@ -24,54 +35,54 @@ void setup() {
led.begin(); led.begin();
rfid.begin(); rfid.begin();
rfid.setCallback(onRfidTag); rfid.setCallback(onRfidTag);
serial.begin(Serial);
serial.setCallback(onSerialMessage);
// Static config // // Static config
configs[0] = {0}; // configs[0] = {0};
configs[0].brightness = 128; // configs[0].brightness = 128;
configs[0].duration_ms = 0; // configs[0].duration_ms = 0;
configs[0].which_animation_params = 3; // configs[0].which_animation_params = 3;
configs[0].animation_params.static_params.color = 0x00FF00; // Green // configs[0].animation_params.static_params.color = 0x00FF00; // Green
// Pulse config // // Pulse config
configs[1] = {0}; // configs[1] = {0};
configs[1].brightness = 128; // configs[1].brightness = 128;
configs[1].duration_ms = 0; // configs[1].duration_ms = 0;
configs[1].which_animation_params = 4; // configs[1].which_animation_params = 4;
configs[1].animation_params.pulse_params.color = 0xFF0000; // Red // configs[1].animation_params.pulse_params.color = 0xFF0000; // Red
configs[1].animation_params.pulse_params.speed_ms = 500; // configs[1].animation_params.pulse_params.speed_ms = 500;
// Fade config // // Fade config
configs[2] = {0}; // configs[2] = {0};
configs[2].brightness = 128; // configs[2].brightness = 128;
configs[2].duration_ms = 0; // configs[2].duration_ms = 0;
configs[2].which_animation_params = 5; // configs[2].which_animation_params = 5;
configs[2].animation_params.fade_params.colors_count = 3; // 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[0] = 0xFF0000; // Red
configs[2].animation_params.fade_params.colors[1] = 0x00FF00; // Green // 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.colors[2] = 0x0000FF; // Blue
configs[2].animation_params.fade_params.speed_ms = 1000; // configs[2].animation_params.fade_params.speed_ms = 1000;
// Flicker config // // Flicker config
configs[3] = {0}; // configs[3] = {0};
configs[3].brightness = 128; // configs[3].brightness = 128;
configs[3].duration_ms = 0; // configs[3].duration_ms = 0;
configs[3].which_animation_params = 6; // configs[3].which_animation_params = 6;
configs[3].animation_params.flicker_params.color = 0xFFFFFF; // White // configs[3].animation_params.flicker_params.color = 0xFFFFFF; // White
configs[3].animation_params.flicker_params.intensity = 50; // configs[3].animation_params.flicker_params.intensity = 50;
led.set(configs[0]); // led.set(configs[0]);
Serial.println("Starting with static green");
} }
void loop() { void loop() {
led.update(); led.update();
rfid.update(); rfid.update();
serial.update();
if (millis() - lastChange >= 10000) { // if (millis() - lastChange >= 10000) {
lastChange = millis(); // lastChange = millis();
currentConfig = (currentConfig + 1) % 4; // currentConfig = (currentConfig + 1) % 4;
led.set(configs[currentConfig]); // led.set(configs[currentConfig]);
Serial.print("Switched to config "); // }
Serial.println(currentConfig);
}
} }