248 lines
7.4 KiB
C++
248 lines
7.4 KiB
C++
// SkyLogic AeroAlign - ESP-NOW Master (Receiver) Implementation
|
|
//
|
|
// Receives sensor data from Slave nodes via ESP-NOW protocol.
|
|
// Automatically registers new nodes on first packet received (no manual pairing).
|
|
|
|
#include "espnow_master.h"
|
|
#include "config.h"
|
|
|
|
// Static instance pointer for callback
|
|
ESPNowMaster* ESPNowMaster::instance = nullptr;
|
|
|
|
ESPNowMaster::ESPNowMaster()
|
|
: total_packets_received(0), total_checksum_errors(0) {
|
|
// Initialize nodes array
|
|
memset(nodes, 0, sizeof(nodes));
|
|
|
|
// Set static instance pointer
|
|
instance = this;
|
|
}
|
|
|
|
bool ESPNowMaster::begin() {
|
|
#ifdef DEBUG_SERIAL_ENABLED
|
|
Serial.println("[ESP-NOW] Initializing Master receiver...");
|
|
#endif
|
|
|
|
// Initialize ESP-NOW
|
|
if (esp_now_init() != ESP_OK) {
|
|
last_error = "ESP-NOW initialization failed";
|
|
#ifdef DEBUG_SERIAL_ENABLED
|
|
Serial.printf("[ESP-NOW] ERROR: %s\n", last_error.c_str());
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// Register receive callback
|
|
esp_now_register_recv_cb(onDataRecv);
|
|
|
|
#ifdef DEBUG_SERIAL_ENABLED
|
|
Serial.println("[ESP-NOW] Master receiver initialized");
|
|
Serial.printf("[ESP-NOW] Master MAC: %s\n", WiFi.macAddress().c_str());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void ESPNowMaster::update() {
|
|
// Check for node timeouts
|
|
uint32_t now = millis();
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
if (nodes[i].node_id != 0 && nodes[i].is_connected) {
|
|
// Check if node timed out (no packet for ESPNOW_TIMEOUT_MS)
|
|
if (now - nodes[i].last_update_ms > ESPNOW_TIMEOUT_MS) {
|
|
nodes[i].is_connected = false;
|
|
|
|
#ifdef DEBUG_SERIAL_ENABLED
|
|
Serial.printf("[ESP-NOW] Node 0x%02X disconnected (timeout)\n", nodes[i].node_id);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SensorNode* ESPNowMaster::getNode(uint8_t node_id) {
|
|
// Find node by ID
|
|
for (int i = 0; i < 9; i++) {
|
|
if (nodes[i].node_id == node_id) {
|
|
return &nodes[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SensorNode* ESPNowMaster::getAllNodes(uint8_t &count) {
|
|
count = 0;
|
|
|
|
// Count registered nodes
|
|
for (int i = 0; i < 9; i++) {
|
|
if (nodes[i].node_id != 0) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
void ESPNowMaster::getStatistics(uint32_t &total_received, uint32_t &total_errors, float &loss_rate) {
|
|
total_received = total_packets_received;
|
|
total_errors = total_checksum_errors;
|
|
|
|
if (total_received > 0) {
|
|
loss_rate = (float)total_errors / (float)total_received;
|
|
} else {
|
|
loss_rate = 0.0;
|
|
}
|
|
}
|
|
|
|
bool ESPNowMaster::isNodeConnected(uint8_t node_id) {
|
|
SensorNode* node = getNode(node_id);
|
|
if (node) {
|
|
return node->is_connected;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String ESPNowMaster::getLastError() const {
|
|
return last_error;
|
|
}
|
|
|
|
// ========================================
|
|
// Private Methods
|
|
// ========================================
|
|
|
|
void ESPNowMaster::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
|
// Static callback - forward to instance
|
|
if (instance) {
|
|
instance->handleReceivedPacket(mac, data, len);
|
|
}
|
|
}
|
|
|
|
void ESPNowMaster::handleReceivedPacket(const uint8_t *mac, const uint8_t *data, int len) {
|
|
// Validate packet length
|
|
if (len != ESPNOW_PACKET_SIZE) {
|
|
#ifdef DEBUG_ESPNOW_PACKETS
|
|
Serial.printf("[ESP-NOW] Invalid packet size: %d (expected %d)\n", len, ESPNOW_PACKET_SIZE);
|
|
#endif
|
|
total_checksum_errors++;
|
|
return;
|
|
}
|
|
|
|
// Validate checksum
|
|
if (!validateChecksum(data, len)) {
|
|
#ifdef DEBUG_ESPNOW_PACKETS
|
|
Serial.printf("[ESP-NOW] Checksum error from %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
#endif
|
|
total_checksum_errors++;
|
|
return;
|
|
}
|
|
|
|
// Parse packet
|
|
ESPNowPacket packet;
|
|
memcpy(&packet, data, sizeof(ESPNowPacket));
|
|
|
|
// Validate node ID (0x02-0x09)
|
|
if (packet.node_id < 0x02 || packet.node_id > 0x09) {
|
|
#ifdef DEBUG_ESPNOW_PACKETS
|
|
Serial.printf("[ESP-NOW] Invalid node ID: 0x%02X\n", packet.node_id);
|
|
#endif
|
|
total_checksum_errors++;
|
|
return;
|
|
}
|
|
|
|
// Find or register node
|
|
SensorNode* node = getNode(packet.node_id);
|
|
if (!node) {
|
|
// New node - register it
|
|
registerNode(packet.node_id, mac);
|
|
node = getNode(packet.node_id);
|
|
}
|
|
|
|
if (node) {
|
|
// Update node data
|
|
node->device_type = static_cast<DeviceType>(packet.device_type);
|
|
node->pitch = packet.pitch;
|
|
node->roll = packet.roll;
|
|
node->yaw = packet.yaw;
|
|
node->front_weight_g = 0.0f;
|
|
node->rear_weight_g = 0.0f;
|
|
node->cog_position_mm = 0.0f;
|
|
if (node->device_type == DEVICE_TYPE_COG_SCALE || node->device_type == DEVICE_TYPE_HYBRID) {
|
|
node->front_weight_g = packet.pitch;
|
|
node->rear_weight_g = packet.roll;
|
|
node->cog_position_mm = packet.yaw;
|
|
}
|
|
node->battery_percent = packet.battery;
|
|
node->is_connected = true;
|
|
node->last_update_ms = millis();
|
|
node->packets_received++;
|
|
|
|
// Calculate RSSI (signal strength)
|
|
// Note: ESP-NOW doesn't provide direct RSSI, estimate from WiFi
|
|
node->rssi = WiFi.RSSI();
|
|
|
|
total_packets_received++;
|
|
|
|
#ifdef DEBUG_ESPNOW_PACKETS
|
|
Serial.printf("[ESP-NOW] RX from 0x%02X (%s): a=%.2f b=%.2f c=%.2f battery=%d%% RSSI=%ddBm\n",
|
|
packet.node_id, deviceTypeToString(node->device_type),
|
|
packet.pitch, packet.roll, packet.yaw, packet.battery, node->rssi);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool ESPNowMaster::validateChecksum(const uint8_t *data, int len) {
|
|
// Calculate expected checksum (XOR of bytes 0 to len-2)
|
|
uint8_t calculated = calculateChecksum(data, len - 1);
|
|
|
|
// Compare with received checksum (last byte)
|
|
uint8_t received = data[len - 1];
|
|
|
|
return (calculated == received);
|
|
}
|
|
|
|
uint8_t ESPNowMaster::calculateChecksum(const uint8_t *data, int len) {
|
|
uint8_t checksum = 0;
|
|
for (int i = 0; i < len; i++) {
|
|
checksum ^= data[i];
|
|
}
|
|
return checksum;
|
|
}
|
|
|
|
void ESPNowMaster::registerNode(uint8_t node_id, const uint8_t *mac) {
|
|
// Find first empty slot
|
|
for (int i = 0; i < 9; i++) {
|
|
if (nodes[i].node_id == 0) {
|
|
// Initialize new node
|
|
nodes[i].node_id = node_id;
|
|
nodes[i].device_type = DEVICE_TYPE_UNKNOWN;
|
|
memcpy(nodes[i].mac_address, mac, 6);
|
|
snprintf(nodes[i].label, sizeof(nodes[i].label), "Sensor %d", node_id - 1);
|
|
nodes[i].pitch = 0.0;
|
|
nodes[i].roll = 0.0;
|
|
nodes[i].yaw = 0.0;
|
|
nodes[i].front_weight_g = 0.0;
|
|
nodes[i].rear_weight_g = 0.0;
|
|
nodes[i].cog_position_mm = 0.0;
|
|
nodes[i].pitch_offset = 0.0;
|
|
nodes[i].roll_offset = 0.0;
|
|
nodes[i].yaw_offset = 0.0;
|
|
nodes[i].battery_percent = 0;
|
|
nodes[i].battery_voltage = 0.0;
|
|
nodes[i].rssi = 0;
|
|
nodes[i].is_connected = false;
|
|
nodes[i].last_update_ms = 0;
|
|
nodes[i].packets_received = 0;
|
|
nodes[i].packets_lost = 0;
|
|
|
|
#ifdef DEBUG_SERIAL_ENABLED
|
|
Serial.printf("[ESP-NOW] Registered new node 0x%02X (MAC: %02X:%02X:%02X:%02X:%02X:%02X)\n",
|
|
node_id, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|