init
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
#include "hardware_rtc.hpp"
|
||||
#include <logger.hpp>
|
||||
|
||||
|
||||
HardwareRTC::HardwareRTC(TwoWire& wire): wire(wire), initialized(false) {}
|
||||
|
||||
HardwareRTC::~HardwareRTC() {}
|
||||
|
||||
void HardwareRTC::begin() {
|
||||
if(wire.getClock() == 0) {
|
||||
wire.begin();
|
||||
wire.setClock(100000); // Set to 100kHz
|
||||
}
|
||||
initialized = true;
|
||||
LOG_DEBUG("RTC initialized");
|
||||
setSystemTime(0);
|
||||
}
|
||||
|
||||
bool HardwareRTC::setTime(time_t unixTimestamp) {
|
||||
if (!initialized) {
|
||||
LOG_WARN("RTC not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use time_t, which on ESP32 is 64-bit and safe for this conversion
|
||||
time_t t = unixTimestamp;
|
||||
struct tm timeStruct;
|
||||
gmtime_r(&t, &timeStruct);
|
||||
|
||||
// Check if the year is within the DS1307's valid range (2000-2099)
|
||||
if (timeStruct.tm_year < 100 || timeStruct.tm_year > 199) {
|
||||
// Year is out of the 2000-2099 range that the DS1307 can store.
|
||||
LOG_ERROR("RTC setTime: Year %d is out of range (2000-2099)", timeStruct.tm_year + 1900);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buffer[7];
|
||||
buffer[0] = decToBcd(timeStruct.tm_sec);
|
||||
buffer[1] = decToBcd(timeStruct.tm_min);
|
||||
buffer[2] = decToBcd(timeStruct.tm_hour);
|
||||
buffer[3] = decToBcd(timeStruct.tm_wday + 1);
|
||||
buffer[4] = decToBcd(timeStruct.tm_mday);
|
||||
buffer[5] = decToBcd(timeStruct.tm_mon + 1);
|
||||
// The DS1307 only stores years 00-99. We handle this by taking the year since 2000.
|
||||
buffer[6] = decToBcd(timeStruct.tm_year - 100);
|
||||
|
||||
writeRegisters(buffer, 7);
|
||||
|
||||
// set system time to match RTC
|
||||
setSystemTime(0);
|
||||
|
||||
LOG_DEBUG("RTC setTime: %s", toDateString(unixTimestamp).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
time_t HardwareRTC::getTime() {
|
||||
if (!initialized) return 0;
|
||||
|
||||
uint8_t buffer[7];
|
||||
readRegisters(buffer, 7);
|
||||
|
||||
struct tm timeStruct = {0}; // Important: Initialize the struct to zero
|
||||
timeStruct.tm_sec = bcdToDec(buffer[0] & 0x7F);
|
||||
timeStruct.tm_min = bcdToDec(buffer[1]);
|
||||
timeStruct.tm_hour = bcdToDec(buffer[2] & 0x3F);
|
||||
timeStruct.tm_wday = bcdToDec(buffer[3]) - 1;
|
||||
timeStruct.tm_mday = bcdToDec(buffer[4]);
|
||||
timeStruct.tm_mon = bcdToDec(buffer[5]) - 1;
|
||||
// Assume all 2-digit years from the RTC are in the 21st century (2000-2099)
|
||||
timeStruct.tm_year = bcdToDec(buffer[6]) + 100; // Years since 1900
|
||||
|
||||
// mktime converts a local time struct to a time_t.
|
||||
// Since the RTC stores time without timezone info, we treat it as UTC.
|
||||
// timegm is the correct function for this, but mktime is often used
|
||||
// on embedded systems with the timezone set to UTC.
|
||||
time_t t = mktime(&timeStruct);
|
||||
|
||||
LOG_DEBUG("RTC getTime: %s", toDateString(t).c_str());
|
||||
|
||||
// Cast the 64-bit time_t to uint64_t for the return type
|
||||
return static_cast<time_t>(t);
|
||||
}
|
||||
|
||||
void HardwareRTC::setSystemTime(int timezoneOffsetHours) {
|
||||
if (!initialized) {
|
||||
LOG_WARN("RTC not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uint64_t unixTime = getTime();
|
||||
if (unixTime == 0) return;
|
||||
|
||||
unixTime += timezoneOffsetHours * 3600;
|
||||
|
||||
LOG_DEBUG("RTC setSystemTime: %s", toDateString(unixTime).c_str());
|
||||
|
||||
struct timeval tv;
|
||||
// The tv_sec field is of type time_t, which is 64-bit on ESP32
|
||||
tv.tv_sec = unixTime;
|
||||
tv.tv_usec = 0;
|
||||
settimeofday(&tv, NULL);
|
||||
}
|
||||
|
||||
|
||||
bool HardwareRTC::isRunning() {
|
||||
if (!initialized) return false;
|
||||
|
||||
uint8_t buffer[1];
|
||||
readRegisters(buffer, 1);
|
||||
return !(buffer[0] & 0x80); // CH bit is 0 when running
|
||||
}
|
||||
|
||||
void HardwareRTC::setTimezone(const char* timezone) {
|
||||
setenv("TZ", timezone, 1);
|
||||
tzset();
|
||||
}
|
||||
|
||||
void HardwareRTC::update()
|
||||
{
|
||||
static uint32_t nextSync = 0;
|
||||
time_t now = millis();
|
||||
if (now >= nextSync) {
|
||||
if(!isRunning()) {
|
||||
LOG_WARN("RTC is not running, skipping update");
|
||||
return;
|
||||
}
|
||||
setSystemTime(0);
|
||||
nextSync = now + (15UL * 60UL * 1000UL); // every 15 minutes
|
||||
LOG_DEBUG("RTC update: System time synchronized with RTC");
|
||||
}
|
||||
}
|
||||
|
||||
String HardwareRTC::toDateString(time_t timestamp) {
|
||||
struct tm* timeinfo = gmtime(×tamp);
|
||||
char buffer[40];
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
uint8_t HardwareRTC::bcdToDec(uint8_t bcd) {
|
||||
return ((bcd >> 4) * 10) + (bcd & 0x0F);
|
||||
}
|
||||
|
||||
uint8_t HardwareRTC::decToBcd(uint8_t dec) {
|
||||
return ((dec / 10) << 4) | (dec % 10);
|
||||
}
|
||||
|
||||
void HardwareRTC::readRegisters(uint8_t* buffer, uint8_t length) {
|
||||
Wire.beginTransmission(DS1307_ADDRESS);
|
||||
Wire.write(0x00); // start at register 0
|
||||
Wire.endTransmission();
|
||||
|
||||
Wire.requestFrom(DS1307_ADDRESS, length);
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
buffer[i] = Wire.read();
|
||||
}
|
||||
}
|
||||
|
||||
void HardwareRTC::writeRegisters(uint8_t* buffer, uint8_t length) {
|
||||
Wire.beginTransmission(DS1307_ADDRESS);
|
||||
Wire.write(0x00); // start at register 0
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
Wire.write(buffer[i]);
|
||||
}
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wire.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <logger.hpp>
|
||||
|
||||
#define DS1307_ADDRESS 0x68
|
||||
#define EEPROM_ADDRESS 0x50
|
||||
|
||||
class HardwareRTC {
|
||||
public:
|
||||
HardwareRTC(TwoWire& wire = Wire);
|
||||
~HardwareRTC();
|
||||
|
||||
void begin();
|
||||
|
||||
// Set RTC time from a 64-bit Unix timestamp
|
||||
bool setTime(time_t unixTimestamp);
|
||||
|
||||
// Get current time as a 64-bit Unix timestamp
|
||||
time_t getTime();
|
||||
|
||||
static time_t getSystemTime() {
|
||||
struct timeval tv;
|
||||
if (gettimeofday(&tv, nullptr) != 0) {
|
||||
LOG_ERROR("Failed to get system time for log entry");
|
||||
return 0; // Fallback-Wert
|
||||
} else {
|
||||
return tv.tv_sec; // Unix-Timestamp in Sekunden
|
||||
}
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec;
|
||||
}
|
||||
// Helper method to convert time_t to date string
|
||||
static String toDateString(time_t timestamp);
|
||||
|
||||
|
||||
void setSystemTime(int timezoneOffsetHours = 0);
|
||||
bool isRunning();
|
||||
void setTimezone(const char* timezone);
|
||||
void update(); // Placeholder for future use
|
||||
|
||||
private:
|
||||
bool initialized;
|
||||
TwoWire& wire;
|
||||
|
||||
uint8_t bcdToDec(uint8_t bcd);
|
||||
uint8_t decToBcd(uint8_t dec);
|
||||
|
||||
void readRegisters(uint8_t* buffer, uint8_t length);
|
||||
void writeRegisters(uint8_t* buffer, uint8_t length);
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "hardware_serial.hpp"
|
||||
#include <Arduino.h>
|
||||
#include <logger.hpp>
|
||||
|
||||
//#define DEBUG_PROTO
|
||||
#ifdef DEBUG_PROTO
|
||||
#define LOG(msg) LOG_DEBUG(msg)
|
||||
#else
|
||||
#define LOG(msg)
|
||||
#endif
|
||||
|
||||
ProtoSerial::ProtoSerial()
|
||||
: 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() {
|
||||
// No delete needed; serial is caller-managed
|
||||
}
|
||||
|
||||
void ProtoSerial::begin(SerialType& serialPort) {
|
||||
serial = &serialPort;
|
||||
initialized = true;
|
||||
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 || !serial->availableForWrite()) {
|
||||
snprintf(lastError, sizeof(lastError), "Serial not initialized or not ready");
|
||||
LOG(lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buffer[OUTGOING_MESSAGE_SIZE];
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
||||
|
||||
// Encode the message
|
||||
if (!pb_encode(&stream, OUTGOING_MESSAGE_FIELDS, &message)) {
|
||||
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 framing: sync start, length, payload, CRC, sync end
|
||||
serial->write(SYNC_START);
|
||||
serial->write((uint8_t*)&messageLength, sizeof(messageLength));
|
||||
serial->write(buffer, messageLength);
|
||||
uint8_t crc = calculate_crc8(buffer, messageLength);
|
||||
serial->write(crc);
|
||||
serial->write(SYNC_END);
|
||||
serial->flush();
|
||||
|
||||
#ifdef DEBUG_PROTO
|
||||
dumpHex(buffer, messageLength, "Sent");
|
||||
snprintf(lastError, sizeof(lastError), "Sent message, length: %u, CRC: 0x%02X", messageLength, crc);
|
||||
LOG(lastError);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProtoSerial::update() {
|
||||
if (!initialized || !serial) {
|
||||
snprintf(lastError, sizeof(lastError), "Serial not initialized");
|
||||
LOG(lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle updates to ~100Hz
|
||||
unsigned long now = millis();
|
||||
if (now - lastUpdate < 10) return;
|
||||
lastUpdate = now;
|
||||
|
||||
// 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;
|
||||
LOG("Received SYNC_START");
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ProtoSerial::hasMessage() const {
|
||||
return messageAvailable;
|
||||
}
|
||||
|
||||
const IncomingMessage& ProtoSerial::getMessage() const {
|
||||
return receivedMessage;
|
||||
}
|
||||
|
||||
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) {
|
||||
receivedMessage = INCOMING_MESSAGE_INIT;
|
||||
pb_istream_t stream = pb_istream_from_buffer(buffer, length);
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
ProtoSerial pserial;
|
||||
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP32
|
||||
#include <HardwareSerial.h>
|
||||
typedef HardwareSerial SerialType;
|
||||
#define SERIAL_TYPE HardwareSerial
|
||||
#elif defined(ESP8266)
|
||||
#include <HardwareSerial.h>
|
||||
typedef HardwareSerial SerialType;
|
||||
#define SERIAL_TYPE HardwareSerial
|
||||
#else
|
||||
#error "Unsupported platform. Only ESP32 and ESP8266 are supported."
|
||||
#endif
|
||||
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#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);
|
||||
};
|
||||
|
||||
extern ProtoSerial pserial;
|
||||
Reference in New Issue
Block a user