Support ESPNow and improve documentation
This commit is contained in:
@@ -80,30 +80,36 @@
|
|||||||
|
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_RS232_BRIDGE
|
||||||
#include "helpers/bridges/RS232Bridge.h"
|
#include "helpers/bridges/RS232Bridge.h"
|
||||||
|
#define WITH_BRIDGE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
#ifdef WITH_ESPNOW_BRIDGE
|
||||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
#include "helpers/bridges/ESPNowBridge.h"
|
||||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
#define WITH_BRIDGE
|
||||||
|
#endif
|
||||||
|
|
||||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||||
|
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||||
|
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||||
|
|
||||||
struct RepeaterStats {
|
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||||
uint16_t batt_milli_volts;
|
|
||||||
uint16_t curr_tx_queue_len;
|
struct RepeaterStats {
|
||||||
int16_t noise_floor;
|
uint16_t batt_milli_volts;
|
||||||
int16_t last_rssi;
|
uint16_t curr_tx_queue_len;
|
||||||
uint32_t n_packets_recv;
|
int16_t noise_floor;
|
||||||
uint32_t n_packets_sent;
|
int16_t last_rssi;
|
||||||
uint32_t total_air_time_secs;
|
uint32_t n_packets_recv;
|
||||||
uint32_t total_up_time_secs;
|
uint32_t n_packets_sent;
|
||||||
uint32_t n_sent_flood, n_sent_direct;
|
uint32_t total_air_time_secs;
|
||||||
uint32_t n_recv_flood, n_recv_direct;
|
uint32_t total_up_time_secs;
|
||||||
uint16_t err_events; // was 'n_full_events'
|
uint32_t n_sent_flood, n_sent_direct;
|
||||||
int16_t last_snr; // x 4
|
uint32_t n_recv_flood, n_recv_direct;
|
||||||
uint16_t n_direct_dups, n_flood_dups;
|
uint16_t err_events; // was 'n_full_events'
|
||||||
uint32_t total_rx_air_time_secs;
|
int16_t last_snr; // x 4
|
||||||
};
|
uint16_t n_direct_dups, n_flood_dups;
|
||||||
|
uint32_t total_rx_air_time_secs;
|
||||||
|
};
|
||||||
|
|
||||||
struct ClientInfo {
|
struct ClientInfo {
|
||||||
mesh::Identity id;
|
mesh::Identity id;
|
||||||
@@ -118,7 +124,7 @@ struct ClientInfo {
|
|||||||
#define MAX_CLIENTS 32
|
#define MAX_CLIENTS 32
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
AbstractBridge* bridge;
|
AbstractBridge* bridge;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -308,7 +314,7 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void logTx(mesh::Packet* pkt, int len) override {
|
void logTx(mesh::Packet* pkt, int len) override {
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
bridge->onPacketTransmitted(pkt);
|
bridge->onPacketTransmitted(pkt);
|
||||||
#endif
|
#endif
|
||||||
if (_logging) {
|
if (_logging) {
|
||||||
@@ -576,8 +582,14 @@ public:
|
|||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||||
{
|
{
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
|
#if defined(WITH_RS232_BRIDGE)
|
||||||
bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc);
|
bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc);
|
||||||
|
#elif defined(WITH_ESPNOW_BRIDGE)
|
||||||
|
bridge = new ESPNowBridge(_mgr, &rtc);
|
||||||
|
#else
|
||||||
|
#error "You must choose either RS232 or ESPNow bridge"
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
memset(known_clients, 0, sizeof(known_clients));
|
memset(known_clients, 0, sizeof(known_clients));
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
@@ -779,7 +791,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
bridge->loop();
|
bridge->loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -831,7 +843,7 @@ void setup() {
|
|||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_BRIDGE
|
||||||
bridge->begin();
|
bridge->begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
184
src/helpers/bridges/ESPNowBridge.cpp
Normal file
184
src/helpers/bridges/ESPNowBridge.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#include "ESPNowBridge.h"
|
||||||
|
|
||||||
|
#include <RTClib.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
|
||||||
|
#ifdef WITH_ESPNOW_BRIDGE
|
||||||
|
|
||||||
|
// Static member to handle callbacks
|
||||||
|
ESPNowBridge *ESPNowBridge::_instance = nullptr;
|
||||||
|
|
||||||
|
// Static callback wrappers
|
||||||
|
void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int len) {
|
||||||
|
if (_instance) {
|
||||||
|
_instance->onDataRecv(mac, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
|
||||||
|
if (_instance) {
|
||||||
|
_instance->onDataSent(mac, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fletcher16 checksum calculation
|
||||||
|
static uint16_t fletcher16(const uint8_t *data, size_t len) {
|
||||||
|
uint16_t sum1 = 0;
|
||||||
|
uint16_t sum2 = 0;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
sum1 = (sum1 + *data++) % 255;
|
||||||
|
sum2 = (sum2 + sum1) % 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (sum2 << 8) | sum1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||||
|
: _mgr(mgr), _rtc(rtc), _rx_buffer_pos(0) {
|
||||||
|
_instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::begin() {
|
||||||
|
// Initialize WiFi in station mode
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
|
||||||
|
// Initialize ESP-NOW
|
||||||
|
if (esp_now_init() != ESP_OK) {
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: Error initializing ESP-NOW\n", getLogDateTime());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register callbacks
|
||||||
|
esp_now_register_recv_cb(recv_cb);
|
||||||
|
esp_now_register_send_cb(send_cb);
|
||||||
|
|
||||||
|
// Add broadcast peer
|
||||||
|
esp_now_peer_info_t peerInfo = {};
|
||||||
|
memset(&peerInfo, 0, sizeof(peerInfo));
|
||||||
|
memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address
|
||||||
|
peerInfo.channel = 0;
|
||||||
|
peerInfo.encrypt = false;
|
||||||
|
|
||||||
|
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::loop() {
|
||||||
|
// Nothing to do here - ESP-NOW is callback based
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) {
|
||||||
|
size_t keyLen = strlen(_secret);
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
data[i] ^= _secret[i % keyLen];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
||||||
|
// Ignore packets that are too small
|
||||||
|
if (len < 3) {
|
||||||
|
#if MESH_PACKET_LOGGING
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check packet header magic
|
||||||
|
if (data[0] != ESPNOW_HEADER_MAGIC) {
|
||||||
|
#if MESH_PACKET_LOGGING
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%02X\n", getLogDateTime(), data[0]);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a copy we can decrypt
|
||||||
|
uint8_t decrypted[MAX_ESPNOW_PACKET_SIZE];
|
||||||
|
memcpy(decrypted, data + 1, len - 1); // Skip magic byte
|
||||||
|
|
||||||
|
// Try to decrypt
|
||||||
|
xorCrypt(decrypted, len - 1);
|
||||||
|
|
||||||
|
// Validate checksum
|
||||||
|
uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1];
|
||||||
|
uint16_t calculated_checksum = fletcher16(decrypted + 2, len - 3);
|
||||||
|
|
||||||
|
if (received_checksum != calculated_checksum) {
|
||||||
|
// Failed to decrypt - likely from a different network
|
||||||
|
#if MESH_PACKET_LOGGING
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X calc=0x%04X\n", getLogDateTime(),
|
||||||
|
received_checksum, calculated_checksum);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MESH_PACKET_LOGGING
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: RX, len=%d\n", getLogDateTime(), len - 3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create mesh packet
|
||||||
|
mesh::Packet *pkt = _instance->_mgr->allocNew();
|
||||||
|
if (!pkt) return;
|
||||||
|
|
||||||
|
if (pkt->readFrom(decrypted + 2, len - 3)) {
|
||||||
|
_instance->onPacketReceived(pkt);
|
||||||
|
} else {
|
||||||
|
_instance->_mgr->free(pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||||
|
// Could add transmission error handling here if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
|
||||||
|
if (!_seen_packets.hasSeen(packet)) {
|
||||||
|
_mgr->queueInbound(packet, 0);
|
||||||
|
} else {
|
||||||
|
_mgr->free(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||||
|
if (!_seen_packets.hasSeen(packet)) {
|
||||||
|
uint8_t buffer[MAX_ESPNOW_PACKET_SIZE];
|
||||||
|
buffer[0] = ESPNOW_HEADER_MAGIC;
|
||||||
|
|
||||||
|
// Write packet to buffer starting after magic byte and checksum
|
||||||
|
uint16_t len = packet->writeTo(buffer + 3);
|
||||||
|
|
||||||
|
// Calculate and add checksum
|
||||||
|
uint16_t checksum = fletcher16(buffer + 3, len);
|
||||||
|
buffer[1] = (checksum >> 8) & 0xFF;
|
||||||
|
buffer[2] = checksum & 0xFF;
|
||||||
|
|
||||||
|
// Encrypt payload (not including magic byte)
|
||||||
|
xorCrypt(buffer + 1, len + 2);
|
||||||
|
|
||||||
|
// Broadcast using ESP-NOW
|
||||||
|
uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
esp_err_t result = esp_now_send(broadcastAddress, buffer, len + 3);
|
||||||
|
|
||||||
|
#if MESH_PACKET_LOGGING
|
||||||
|
if (result == ESP_OK) {
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), len);
|
||||||
|
} else {
|
||||||
|
Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ESPNowBridge::getLogDateTime() {
|
||||||
|
static char tmp[32];
|
||||||
|
uint32_t now = _rtc->getCurrentTime();
|
||||||
|
DateTime dt = DateTime(now);
|
||||||
|
sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(),
|
||||||
|
dt.year());
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
170
src/helpers/bridges/ESPNowBridge.h
Normal file
170
src/helpers/bridges/ESPNowBridge.h
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "MeshCore.h"
|
||||||
|
#include "esp_now.h"
|
||||||
|
#include "helpers/AbstractBridge.h"
|
||||||
|
#include "helpers/SimpleMeshTables.h"
|
||||||
|
|
||||||
|
#ifdef WITH_ESPNOW_BRIDGE
|
||||||
|
|
||||||
|
#ifndef WITH_ESPNOW_BRIDGE_SECRET
|
||||||
|
#error WITH_ESPNOW_BRIDGE_SECRET must be defined to use ESPNowBridge
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bridge implementation using ESP-NOW protocol for packet transport
|
||||||
|
*
|
||||||
|
* This bridge enables mesh packet transport over ESP-NOW, a connectionless communication
|
||||||
|
* protocol provided by Espressif that allows ESP32 devices to communicate directly
|
||||||
|
* without WiFi router infrastructure.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Broadcast-based communication (all bridges receive all packets)
|
||||||
|
* - Network isolation using XOR encryption with shared secret
|
||||||
|
* - Duplicate packet detection using SimpleMeshTables tracking
|
||||||
|
* - Maximum packet size of 250 bytes (ESP-NOW limitation)
|
||||||
|
*
|
||||||
|
* Packet Structure:
|
||||||
|
* [1 byte] Magic Header (0xAB) - Used to identify ESPNowBridge packets
|
||||||
|
* [2 bytes] Fletcher-16 checksum of encrypted payload (calculated over payload only)
|
||||||
|
* [n bytes] Encrypted payload containing the mesh packet
|
||||||
|
*
|
||||||
|
* The Fletcher-16 checksum is used to validate packet integrity and detect
|
||||||
|
* corrupted or tampered packets. It's calculated over the encrypted payload
|
||||||
|
* and provides a simple but effective way to verify packets are both
|
||||||
|
* uncorrupted and from the same network (since the checksum is calculated
|
||||||
|
* after encryption).
|
||||||
|
*
|
||||||
|
* Configuration:
|
||||||
|
* - Define WITH_ESPNOW_BRIDGE to enable this bridge
|
||||||
|
* - Define WITH_ESPNOW_BRIDGE_SECRET with a string to set the network encryption key
|
||||||
|
*
|
||||||
|
* Network Isolation:
|
||||||
|
* Multiple independent mesh networks can coexist by using different
|
||||||
|
* WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will
|
||||||
|
* fail the checksum validation and be discarded.
|
||||||
|
*/
|
||||||
|
class ESPNowBridge : public AbstractBridge {
|
||||||
|
private:
|
||||||
|
static ESPNowBridge *_instance;
|
||||||
|
static void recv_cb(const uint8_t *mac, const uint8_t *data, int len);
|
||||||
|
static void send_cb(const uint8_t *mac, esp_now_send_status_t status);
|
||||||
|
|
||||||
|
/** Packet manager for allocating and queuing mesh packets */
|
||||||
|
mesh::PacketManager *_mgr;
|
||||||
|
|
||||||
|
/** RTC clock for timestamping debug messages */
|
||||||
|
mesh::RTCClock *_rtc;
|
||||||
|
|
||||||
|
/** Tracks seen packets to prevent loops in broadcast communications */
|
||||||
|
SimpleMeshTables _seen_packets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum ESP-NOW packet size (250 bytes)
|
||||||
|
* This is a hardware limitation of ESP-NOW protocol:
|
||||||
|
* - ESP-NOW header: 20 bytes
|
||||||
|
* - Max payload: 250 bytes
|
||||||
|
* Source: ESP-NOW API documentation
|
||||||
|
*/
|
||||||
|
static const size_t MAX_ESPNOW_PACKET_SIZE = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic byte to identify ESPNowBridge packets (0xAB)
|
||||||
|
*/
|
||||||
|
static const uint8_t ESPNOW_HEADER_MAGIC = 0xAB;
|
||||||
|
|
||||||
|
/** Buffer for receiving ESP-NOW packets */
|
||||||
|
uint8_t _rx_buffer[MAX_ESPNOW_PACKET_SIZE];
|
||||||
|
|
||||||
|
/** Current position in receive buffer */
|
||||||
|
size_t _rx_buffer_pos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network encryption key from build define
|
||||||
|
* Must be defined with WITH_ESPNOW_BRIDGE_SECRET
|
||||||
|
* Used for XOR encryption to isolate different mesh networks
|
||||||
|
*/
|
||||||
|
const char *_secret = WITH_ESPNOW_BRIDGE_SECRET;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs XOR encryption/decryption of data
|
||||||
|
*
|
||||||
|
* Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation.
|
||||||
|
* The same operation is used for both encryption and decryption.
|
||||||
|
* While not cryptographically secure, it provides basic network isolation.
|
||||||
|
*
|
||||||
|
* @param data Pointer to data to encrypt/decrypt
|
||||||
|
* @param len Length of data in bytes
|
||||||
|
*/
|
||||||
|
void xorCrypt(uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ESP-NOW receive callback
|
||||||
|
* Called by ESP-NOW when a packet is received
|
||||||
|
*
|
||||||
|
* @param mac Source MAC address
|
||||||
|
* @param data Received data
|
||||||
|
* @param len Length of received data
|
||||||
|
*/
|
||||||
|
void onDataRecv(const uint8_t *mac, const uint8_t *data, int len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ESP-NOW send callback
|
||||||
|
* Called by ESP-NOW after a transmission attempt
|
||||||
|
*
|
||||||
|
* @param mac_addr Destination MAC address
|
||||||
|
* @param status Transmission status
|
||||||
|
*/
|
||||||
|
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructs an ESPNowBridge instance
|
||||||
|
*
|
||||||
|
* @param mgr PacketManager for allocating and queuing packets
|
||||||
|
* @param rtc RTCClock for timestamping debug messages
|
||||||
|
*/
|
||||||
|
ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the ESP-NOW bridge
|
||||||
|
*
|
||||||
|
* - Configures WiFi in station mode
|
||||||
|
* - Initializes ESP-NOW protocol
|
||||||
|
* - Registers callbacks
|
||||||
|
* - Sets up broadcast peer
|
||||||
|
*/
|
||||||
|
void begin() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main loop handler
|
||||||
|
* ESP-NOW is callback-based, so this is currently empty
|
||||||
|
*/
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a packet is received via ESP-NOW
|
||||||
|
* Queues the packet for mesh processing if not seen before
|
||||||
|
*
|
||||||
|
* @param packet The received mesh packet
|
||||||
|
*/
|
||||||
|
void onPacketReceived(mesh::Packet *packet) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a packet needs to be transmitted via ESP-NOW
|
||||||
|
* Encrypts and broadcasts the packet if not seen before
|
||||||
|
*
|
||||||
|
* @param packet The mesh packet to transmit
|
||||||
|
*/
|
||||||
|
void onPacketTransmitted(mesh::Packet *packet) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets formatted date/time string for logging
|
||||||
|
* Format: "HH:MM:SS - DD/MM/YYYY U"
|
||||||
|
*
|
||||||
|
* @return Formatted date/time string
|
||||||
|
*/
|
||||||
|
const char *getLogDateTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_RS232_BRIDGE
|
||||||
|
|
||||||
// Fletcher-16
|
// Static Fletcher-16 checksum calculation
|
||||||
// https://en.wikipedia.org/wiki/Fletcher%27s_checksum
|
// Based on: https://en.wikipedia.org/wiki/Fletcher%27s_checksum
|
||||||
|
// Used to verify data integrity of received packets
|
||||||
inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) {
|
inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) {
|
||||||
uint8_t sum1 = 0, sum2 = 0;
|
uint8_t sum1 = 0, sum2 = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -7,28 +7,87 @@
|
|||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_RS232_BRIDGE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A bridge implementation that uses a serial port to connect two mesh networks.
|
* @brief Bridge implementation using RS232/UART protocol for packet transport
|
||||||
|
*
|
||||||
|
* This bridge enables mesh packet transport over serial/UART connections,
|
||||||
|
* allowing nodes to communicate over wired serial links. It implements a simple
|
||||||
|
* packet framing protocol with checksums for reliable transfer.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Point-to-point communication over hardware UART
|
||||||
|
* - Fletcher-16 checksum for data integrity verification
|
||||||
|
* - Magic header for packet synchronization
|
||||||
|
* - Configurable RX/TX pins via build defines
|
||||||
|
* - Baud rate fixed at 115200
|
||||||
|
*
|
||||||
|
* Packet Structure:
|
||||||
|
* [2 bytes] Magic Header (0xCAFE) - Used to identify start of packet
|
||||||
|
* [2 bytes] Fletcher-16 checksum - Data integrity check
|
||||||
|
* [1 byte] Payload length
|
||||||
|
* [n bytes] Packet payload
|
||||||
|
*
|
||||||
|
* The Fletcher-16 checksum is used to validate packet integrity and detect
|
||||||
|
* corrupted or malformed packets. It provides error detection capabilities
|
||||||
|
* suitable for serial communication where noise or timing issues could
|
||||||
|
* corrupt data.
|
||||||
|
*
|
||||||
|
* Configuration:
|
||||||
|
* - Define WITH_RS232_BRIDGE to enable this bridge
|
||||||
|
* - Define WITH_RS232_BRIDGE_RX with the RX pin number
|
||||||
|
* - Define WITH_RS232_BRIDGE_TX with the TX pin number
|
||||||
|
*
|
||||||
|
* Platform Support:
|
||||||
|
* - ESP32 targets
|
||||||
|
* - NRF52 targets
|
||||||
|
* - RP2040 targets
|
||||||
|
* - STM32 targets
|
||||||
*/
|
*/
|
||||||
class RS232Bridge : public AbstractBridge {
|
class RS232Bridge : public AbstractBridge {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new Serial Bridge object
|
* @brief Constructs an RS232Bridge instance
|
||||||
*
|
*
|
||||||
* @param serial The serial port to use for the bridge.
|
* @param serial The hardware serial port to use
|
||||||
* @param mgr A pointer to the packet manager.
|
* @param mgr PacketManager for allocating and queuing packets
|
||||||
* @param rtc A pointer to the RTC clock.
|
* @param rtc RTCClock for timestamping debug messages
|
||||||
*/
|
*/
|
||||||
RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc);
|
RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RS232 bridge
|
||||||
|
*
|
||||||
|
* - Configures UART pins based on platform
|
||||||
|
* - Sets baud rate to 115200
|
||||||
|
*/
|
||||||
void begin() override;
|
void begin() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main loop handler
|
||||||
|
* Processes incoming serial data and builds packets
|
||||||
|
*/
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a packet needs to be transmitted over serial
|
||||||
|
* Formats and sends the packet with proper framing
|
||||||
|
*
|
||||||
|
* @param packet The mesh packet to transmit
|
||||||
|
*/
|
||||||
void onPacketTransmitted(mesh::Packet* packet) override;
|
void onPacketTransmitted(mesh::Packet* packet) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a complete packet has been received from serial
|
||||||
|
* Queues the packet for mesh processing if checksum is valid
|
||||||
|
*
|
||||||
|
* @param packet The received mesh packet
|
||||||
|
*/
|
||||||
void onPacketReceived(mesh::Packet* packet) override;
|
void onPacketReceived(mesh::Packet* packet) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/** Helper function to get formatted timestamp for logging */
|
||||||
const char* getLogDateTime();
|
const char* getLogDateTime();
|
||||||
/**
|
|
||||||
* @brief The 2-byte magic word used to signify the start of a packet.
|
/** Magic number to identify start of RS232 packets (0xCAFE) */
|
||||||
*/
|
|
||||||
static constexpr uint16_t SERIAL_PKT_MAGIC = 0xCAFE;
|
static constexpr uint16_t SERIAL_PKT_MAGIC = 0xCAFE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -64,29 +64,6 @@ lib_deps =
|
|||||||
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||||
${esp32_ota.lib_deps}
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
[env:LilyGo_TLora_V2_1_1_6_repeater_bridge]
|
|
||||||
extends = LilyGo_TLora_V2_1_1_6
|
|
||||||
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
|
||||||
+<helpers/ui/SSD1306Display.cpp>
|
|
||||||
+<helpers/bridges/RS232Bridge.cpp>
|
|
||||||
+<../examples/simple_repeater>
|
|
||||||
build_flags =
|
|
||||||
${LilyGo_TLora_V2_1_1_6.build_flags}
|
|
||||||
-D ADVERT_NAME='"TLora-V2.1-1.6 Bridge"'
|
|
||||||
-D ADVERT_LAT=0.0
|
|
||||||
-D ADVERT_LON=0.0
|
|
||||||
-D ADMIN_PASSWORD='"password"'
|
|
||||||
-D MAX_NEIGHBOURS=8
|
|
||||||
-D WITH_RS232_BRIDGE=Serial2
|
|
||||||
-D WITH_RS232_BRIDGE_RX=34
|
|
||||||
-D WITH_RS232_BRIDGE_TX=25
|
|
||||||
-D MESH_PACKET_LOGGING=1
|
|
||||||
; -D MESH_DEBUG=1
|
|
||||||
; -D CORE_DEBUG_LEVEL=3
|
|
||||||
lib_deps =
|
|
||||||
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
|
||||||
${esp32_ota.lib_deps}
|
|
||||||
|
|
||||||
[env:LilyGo_TLora_V2_1_1_6_terminal_chat]
|
[env:LilyGo_TLora_V2_1_1_6_terminal_chat]
|
||||||
extends = LilyGo_TLora_V2_1_1_6
|
extends = LilyGo_TLora_V2_1_1_6
|
||||||
build_flags =
|
build_flags =
|
||||||
@@ -179,3 +156,51 @@ build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
;
|
||||||
|
; Repeater Bridges
|
||||||
|
;
|
||||||
|
[env:LilyGo_TLora_V2_1_1_6_bridge_rs232]
|
||||||
|
extends = LilyGo_TLora_V2_1_1_6
|
||||||
|
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/bridges/RS232Bridge.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
build_flags =
|
||||||
|
${LilyGo_TLora_V2_1_1_6.build_flags}
|
||||||
|
-D ADVERT_NAME='"RS232 Bridge"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=8
|
||||||
|
-D WITH_RS232_BRIDGE=Serial2
|
||||||
|
-D WITH_RS232_BRIDGE_RX=34
|
||||||
|
-D WITH_RS232_BRIDGE_TX=25
|
||||||
|
-D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
; -D CORE_DEBUG_LEVEL=3
|
||||||
|
lib_deps =
|
||||||
|
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:LilyGo_TLora_V2_1_1_6_bridge_espnow]
|
||||||
|
extends = LilyGo_TLora_V2_1_1_6
|
||||||
|
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/bridges/ESPNowBridge.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
build_flags =
|
||||||
|
${LilyGo_TLora_V2_1_1_6.build_flags}
|
||||||
|
-D ADVERT_NAME='"ESPNow Bridge"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=8
|
||||||
|
-D WITH_ESPNOW_BRIDGE=1
|
||||||
|
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||||
|
-D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
; -D CORE_DEBUG_LEVEL=3
|
||||||
|
lib_deps =
|
||||||
|
${LilyGo_TLora_V2_1_1_6.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
Reference in New Issue
Block a user