This commit is contained in:
2025-10-06 18:27:50 +02:00
commit 3e191a4f60
213 changed files with 22261 additions and 0 deletions
+168
View File
@@ -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(&timestamp);
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();
}
+53
View File
@@ -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);
};
+222
View File
@@ -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;
+171
View File
@@ -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;