Work on receiver and sender
This commit is contained in:
@@ -162,3 +162,19 @@ a843eb9 Keep v0.3.1
|
|||||||
- src/VictronBLE.cpp
|
- src/VictronBLE.cpp
|
||||||
- src/VictronBLE.h
|
- src/VictronBLE.h
|
||||||
|
|
||||||
|
|
||||||
|
### Session: 2026-02-15 19:18
|
||||||
|
**Commits:**
|
||||||
|
```
|
||||||
|
8a2402c Repeater and Test code for ESP Now
|
||||||
|
```
|
||||||
|
**Modified files:**
|
||||||
|
- .claude/CLAUDE.md
|
||||||
|
- examples/FakeRepeater/platformio.ini
|
||||||
|
- examples/FakeRepeater/src/main.cpp
|
||||||
|
- examples/Receiver/platformio.ini
|
||||||
|
- examples/Receiver/src/main.cpp
|
||||||
|
- examples/Repeater/platformio.ini
|
||||||
|
- examples/Repeater/src/main.cpp
|
||||||
|
- library.json
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ monitor_speed = 115200
|
|||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
build_flags =
|
build_flags =
|
||||||
-Os
|
-Os
|
||||||
|
-D USE_M5STICK
|
||||||
lib_deps =
|
lib_deps =
|
||||||
M5StickC
|
M5StickC
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <esp_now.h>
|
#include <esp_now.h>
|
||||||
|
|
||||||
|
#ifdef USE_M5STICK
|
||||||
|
#include <M5StickC.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// ESPNow packet structure - must match Repeater
|
// ESPNow packet structure - must match Repeater
|
||||||
struct __attribute__((packed)) SolarChargerPacket {
|
struct __attribute__((packed)) SolarChargerPacket {
|
||||||
uint8_t chargeState;
|
uint8_t chargeState;
|
||||||
@@ -27,6 +31,26 @@ struct __attribute__((packed)) SolarChargerPacket {
|
|||||||
|
|
||||||
static uint32_t recvCount = 0;
|
static uint32_t recvCount = 0;
|
||||||
|
|
||||||
|
#ifdef USE_M5STICK
|
||||||
|
// Display: cache latest packet per device for screen rotation
|
||||||
|
static const int MAX_DISPLAY_DEVICES = 4;
|
||||||
|
static SolarChargerPacket displayPackets[MAX_DISPLAY_DEVICES];
|
||||||
|
static bool displayValid[MAX_DISPLAY_DEVICES] = {};
|
||||||
|
static int displayCount = 0;
|
||||||
|
static int displayPage = 0; // Which device to show
|
||||||
|
static bool displayDirty = true;
|
||||||
|
static unsigned long lastPageSwitch = 0;
|
||||||
|
static const unsigned long PAGE_SWITCH_MS = 5000; // Rotate pages every 5s
|
||||||
|
|
||||||
|
static int findOrAddDisplay(const char* name) {
|
||||||
|
for (int i = 0; i < displayCount; i++) {
|
||||||
|
if (strncmp(displayPackets[i].deviceName, name, 16) == 0) return i;
|
||||||
|
}
|
||||||
|
if (displayCount < MAX_DISPLAY_DEVICES) return displayCount++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char* chargeStateName(uint8_t state) {
|
static const char* chargeStateName(uint8_t state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 0: return "Off";
|
case 0: return "Off";
|
||||||
@@ -76,6 +100,15 @@ void onDataRecv(const uint8_t* senderMac, const uint8_t* data, int len) {
|
|||||||
pkt->rssi,
|
pkt->rssi,
|
||||||
senderMac[0], senderMac[1], senderMac[2],
|
senderMac[0], senderMac[1], senderMac[2],
|
||||||
senderMac[3], senderMac[4], senderMac[5]);
|
senderMac[3], senderMac[4], senderMac[5]);
|
||||||
|
|
||||||
|
#ifdef USE_M5STICK
|
||||||
|
int idx = findOrAddDisplay(name);
|
||||||
|
if (idx >= 0) {
|
||||||
|
displayPackets[idx] = *pkt;
|
||||||
|
displayValid[idx] = true;
|
||||||
|
displayDirty = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* VictronBLE Repeater Example
|
* VictronBLE Repeater Example
|
||||||
*
|
*
|
||||||
* Collects Solar Charger data via BLE and forwards every packet
|
* Collects Solar Charger data via BLE and transmits the latest
|
||||||
* over ESPNow broadcast. Place this ESP32 near Victron devices and
|
* readings over ESPNow broadcast every 30 seconds. Place this ESP32
|
||||||
* use a separate Receiver ESP32 at a distance.
|
* near Victron devices and use a separate Receiver ESP32 at a distance.
|
||||||
*
|
*
|
||||||
* ESPNow range is typically much greater than BLE (~200m+ line of sight).
|
* ESPNow range is typically much greater than BLE (~200m+ line of sight).
|
||||||
*
|
*
|
||||||
@@ -34,14 +34,37 @@ struct __attribute__((packed)) SolarChargerPacket {
|
|||||||
// Broadcast address
|
// Broadcast address
|
||||||
static const uint8_t BROADCAST_ADDR[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
static const uint8_t BROADCAST_ADDR[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
|
||||||
|
static const unsigned long SEND_INTERVAL_MS = 30000; // 30 seconds
|
||||||
|
|
||||||
static uint32_t sendCount = 0;
|
static uint32_t sendCount = 0;
|
||||||
static uint32_t sendFailCount = 0;
|
static uint32_t sendFailCount = 0;
|
||||||
|
static uint32_t blePacketCount = 0;
|
||||||
|
|
||||||
|
// Cache latest packet per device
|
||||||
|
static const int MAX_DEVICES = 4;
|
||||||
|
static SolarChargerPacket cachedPackets[MAX_DEVICES];
|
||||||
|
static bool cachedValid[MAX_DEVICES] = {};
|
||||||
|
static int cachedCount = 0;
|
||||||
|
static unsigned long lastSendTime = 0;
|
||||||
|
|
||||||
VictronBLE victron;
|
VictronBLE victron;
|
||||||
|
|
||||||
|
// Find cached slot by device name, or allocate a new one
|
||||||
|
static int findOrAddCached(const char* name) {
|
||||||
|
for (int i = 0; i < cachedCount; i++) {
|
||||||
|
if (strncmp(cachedPackets[i].deviceName, name, sizeof(cachedPackets[i].deviceName)) == 0)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
if (cachedCount < MAX_DEVICES) return cachedCount++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
class RepeaterCallback : public VictronDeviceCallback {
|
class RepeaterCallback : public VictronDeviceCallback {
|
||||||
public:
|
public:
|
||||||
void onSolarChargerData(const SolarChargerData& data) override {
|
void onSolarChargerData(const SolarChargerData& data) override {
|
||||||
|
blePacketCount++;
|
||||||
|
|
||||||
|
// Build packet
|
||||||
SolarChargerPacket pkt;
|
SolarChargerPacket pkt;
|
||||||
pkt.chargeState = static_cast<uint8_t>(data.chargeState);
|
pkt.chargeState = static_cast<uint8_t>(data.chargeState);
|
||||||
pkt.batteryVoltage = data.batteryVoltage;
|
pkt.batteryVoltage = data.batteryVoltage;
|
||||||
@@ -51,25 +74,14 @@ public:
|
|||||||
pkt.yieldToday = data.yieldToday;
|
pkt.yieldToday = data.yieldToday;
|
||||||
pkt.loadCurrent = data.loadCurrent;
|
pkt.loadCurrent = data.loadCurrent;
|
||||||
pkt.rssi = data.rssi;
|
pkt.rssi = data.rssi;
|
||||||
|
|
||||||
// Copy device name, truncate to fit
|
|
||||||
memset(pkt.deviceName, 0, sizeof(pkt.deviceName));
|
memset(pkt.deviceName, 0, sizeof(pkt.deviceName));
|
||||||
strncpy(pkt.deviceName, data.deviceName.c_str(), sizeof(pkt.deviceName) - 1);
|
strncpy(pkt.deviceName, data.deviceName.c_str(), sizeof(pkt.deviceName) - 1);
|
||||||
|
|
||||||
esp_err_t result = esp_now_send(BROADCAST_ADDR,
|
// Cache it
|
||||||
reinterpret_cast<const uint8_t*>(&pkt),
|
int idx = findOrAddCached(pkt.deviceName);
|
||||||
sizeof(pkt));
|
if (idx >= 0) {
|
||||||
|
cachedPackets[idx] = pkt;
|
||||||
sendCount++;
|
cachedValid[idx] = true;
|
||||||
if (result != ESP_OK) {
|
|
||||||
sendFailCount++;
|
|
||||||
Serial.println("ESPNow send failed: " + String(esp_err_to_name(result)));
|
|
||||||
} else {
|
|
||||||
Serial.println("[TX] " + String(pkt.deviceName) +
|
|
||||||
" Batt:" + String(pkt.batteryVoltage, 2) + "V" +
|
|
||||||
" PV:" + String(pkt.panelPower, 0) + "W" +
|
|
||||||
" (sent:" + String(sendCount) +
|
|
||||||
" fail:" + String(sendFailCount) + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -138,5 +150,40 @@ void setup() {
|
|||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
victron.loop();
|
victron.loop();
|
||||||
|
|
||||||
|
// Send cached packets every 30 seconds
|
||||||
|
unsigned long now = millis();
|
||||||
|
if (now - lastSendTime >= SEND_INTERVAL_MS) {
|
||||||
|
lastSendTime = now;
|
||||||
|
|
||||||
|
int sent = 0;
|
||||||
|
for (int i = 0; i < cachedCount; i++) {
|
||||||
|
if (!cachedValid[i]) continue;
|
||||||
|
|
||||||
|
esp_err_t result = esp_now_send(BROADCAST_ADDR,
|
||||||
|
reinterpret_cast<const uint8_t*>(&cachedPackets[i]),
|
||||||
|
sizeof(SolarChargerPacket));
|
||||||
|
|
||||||
|
if (result == ESP_OK) {
|
||||||
|
sendCount++;
|
||||||
|
sent++;
|
||||||
|
Serial.printf("[ESPNow] Sent %s: %.2fV %.1fA PV:%.1fV %.0fW State:%d\n",
|
||||||
|
cachedPackets[i].deviceName,
|
||||||
|
cachedPackets[i].batteryVoltage,
|
||||||
|
cachedPackets[i].batteryCurrent,
|
||||||
|
cachedPackets[i].panelVoltage,
|
||||||
|
cachedPackets[i].panelPower,
|
||||||
|
cachedPackets[i].chargeState);
|
||||||
|
} else {
|
||||||
|
sendFailCount++;
|
||||||
|
Serial.printf("[ESPNow] FAIL sending %s (err=%d)\n",
|
||||||
|
cachedPackets[i].deviceName, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[Stats] BLE pkts:%lu ESPNow sent:%lu fail:%lu devices:%d\n",
|
||||||
|
blePacketCount, sendCount, sendFailCount, cachedCount);
|
||||||
|
}
|
||||||
|
|
||||||
delay(100);
|
delay(100);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user