Single callback version - vastly simplified.
This commit is contained in:
@@ -17,7 +17,6 @@ struct __attribute__((packed)) SolarChargerPacket {
|
||||
uint8_t chargeState;
|
||||
float batteryVoltage; // V
|
||||
float batteryCurrent; // A
|
||||
float panelVoltage; // V
|
||||
float panelPower; // W
|
||||
uint16_t yieldToday; // Wh
|
||||
float loadCurrent; // A
|
||||
@@ -74,7 +73,6 @@ void loop() {
|
||||
pkt.chargeState = (sendCount % 4) + 3; // Cycle through Bulk(3), Absorption(4), Float(5), Storage(6)
|
||||
pkt.batteryVoltage = 51.0f + (sendCount % 20) * 0.15f;
|
||||
pkt.batteryCurrent = 2.0f + (sendCount % 10) * 0.5f;
|
||||
pkt.panelVoltage = 65.0f + (sendCount % 15) * 0.8f;
|
||||
pkt.panelPower = pkt.batteryCurrent * pkt.batteryVoltage;
|
||||
pkt.yieldToday = 100 + sendCount * 10;
|
||||
pkt.loadCurrent = 0;
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
*
|
||||
* Demonstrates change-detection logging for Solar Charger data.
|
||||
* Only logs to serial when a value changes (ignoring RSSI), or once
|
||||
* per minute if nothing has changed. This keeps serial output quiet
|
||||
* and is useful for long-running monitoring / data logging.
|
||||
* per minute if nothing has changed.
|
||||
*
|
||||
* Setup:
|
||||
* 1. Get your device encryption keys from the VictronConnect app
|
||||
@@ -16,13 +15,11 @@
|
||||
|
||||
VictronBLE victron;
|
||||
|
||||
// Tracks last-logged values per device for change detection
|
||||
struct SolarChargerSnapshot {
|
||||
bool valid = false;
|
||||
SolarChargerState chargeState;
|
||||
uint8_t chargeState;
|
||||
float batteryVoltage;
|
||||
float batteryCurrent;
|
||||
float panelVoltage;
|
||||
float panelPower;
|
||||
uint16_t yieldToday;
|
||||
float loadCurrent;
|
||||
@@ -30,26 +27,26 @@ struct SolarChargerSnapshot {
|
||||
uint32_t packetsSinceLastLog = 0;
|
||||
};
|
||||
|
||||
// Store a snapshot per device (index by MAC string)
|
||||
static const int MAX_DEVICES = 4;
|
||||
static String deviceMACs[MAX_DEVICES];
|
||||
static char deviceMACs[MAX_DEVICES][VICTRON_MAC_LEN];
|
||||
static SolarChargerSnapshot snapshots[MAX_DEVICES];
|
||||
static int deviceCount = 0;
|
||||
|
||||
static const unsigned long LOG_INTERVAL_MS = 60000; // 1 minute
|
||||
static const unsigned long LOG_INTERVAL_MS = 60000;
|
||||
|
||||
static int findOrAddDevice(const String& mac) {
|
||||
static int findOrAddDevice(const char* mac) {
|
||||
for (int i = 0; i < deviceCount; i++) {
|
||||
if (deviceMACs[i] == mac) return i;
|
||||
if (strcmp(deviceMACs[i], mac) == 0) return i;
|
||||
}
|
||||
if (deviceCount < MAX_DEVICES) {
|
||||
deviceMACs[deviceCount] = mac;
|
||||
strncpy(deviceMACs[deviceCount], mac, VICTRON_MAC_LEN - 1);
|
||||
deviceMACs[deviceCount][VICTRON_MAC_LEN - 1] = '\0';
|
||||
return deviceCount++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static String chargeStateName(SolarChargerState state) {
|
||||
static const char* chargeStateName(uint8_t state) {
|
||||
switch (state) {
|
||||
case CHARGER_OFF: return "Off";
|
||||
case CHARGER_LOW_POWER: return "Low Power";
|
||||
@@ -66,66 +63,58 @@ static String chargeStateName(SolarChargerState state) {
|
||||
}
|
||||
}
|
||||
|
||||
static void logData(const SolarChargerData& data, const char* reason, uint32_t packets) {
|
||||
Serial.println("[" + data.deviceName + "] " + reason +
|
||||
" pkts:" + String(packets) +
|
||||
" | State:" + chargeStateName(data.chargeState) +
|
||||
" Batt:" + String(data.batteryVoltage, 2) + "V" +
|
||||
" " + String(data.batteryCurrent, 2) + "A" +
|
||||
" PV:" + String(data.panelVoltage, 1) + "V" +
|
||||
" " + String(data.panelPower, 0) + "W" +
|
||||
" Yield:" + String(data.yieldToday) + "Wh" +
|
||||
(data.loadCurrent > 0 ? " Load:" + String(data.loadCurrent, 2) + "A" : ""));
|
||||
static void logData(const VictronDevice* dev, const VictronSolarData& s,
|
||||
const char* reason, uint32_t packets) {
|
||||
Serial.printf("[%s] %s pkts:%lu | State:%s Batt:%.2fV %.2fA PV:%.0fW Yield:%uWh",
|
||||
dev->name, reason, packets,
|
||||
chargeStateName(s.chargeState),
|
||||
s.batteryVoltage, s.batteryCurrent,
|
||||
s.panelPower, s.yieldToday);
|
||||
if (s.loadCurrent > 0)
|
||||
Serial.printf(" Load:%.2fA", s.loadCurrent);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
class LoggerCallback : public VictronDeviceCallback {
|
||||
public:
|
||||
void onSolarChargerData(const SolarChargerData& data) override {
|
||||
int idx = findOrAddDevice(data.macAddress);
|
||||
if (idx < 0) return;
|
||||
void onVictronData(const VictronDevice* dev) {
|
||||
if (dev->deviceType != DEVICE_TYPE_SOLAR_CHARGER) return;
|
||||
const auto& s = dev->solar;
|
||||
|
||||
SolarChargerSnapshot& prev = snapshots[idx];
|
||||
unsigned long now = millis();
|
||||
prev.packetsSinceLastLog++;
|
||||
int idx = findOrAddDevice(dev->mac);
|
||||
if (idx < 0) return;
|
||||
|
||||
if (!prev.valid) {
|
||||
// First reading - always log
|
||||
logData(data, "INIT", prev.packetsSinceLastLog);
|
||||
SolarChargerSnapshot& prev = snapshots[idx];
|
||||
unsigned long now = millis();
|
||||
prev.packetsSinceLastLog++;
|
||||
|
||||
if (!prev.valid) {
|
||||
logData(dev, s, "INIT", prev.packetsSinceLastLog);
|
||||
} else {
|
||||
bool changed = (prev.chargeState != s.chargeState) ||
|
||||
(prev.batteryVoltage != s.batteryVoltage) ||
|
||||
(prev.batteryCurrent != s.batteryCurrent) ||
|
||||
(prev.panelPower != s.panelPower) ||
|
||||
(prev.yieldToday != s.yieldToday) ||
|
||||
(prev.loadCurrent != s.loadCurrent);
|
||||
|
||||
if (changed) {
|
||||
logData(dev, s, "CHG", prev.packetsSinceLastLog);
|
||||
} else if (now - prev.lastLogTime >= LOG_INTERVAL_MS) {
|
||||
logData(dev, s, "HEARTBEAT", prev.packetsSinceLastLog);
|
||||
} else {
|
||||
// Check for changes (everything except RSSI)
|
||||
bool changed = false;
|
||||
if (prev.chargeState != data.chargeState) changed = true;
|
||||
if (prev.batteryVoltage != data.batteryVoltage) changed = true;
|
||||
if (prev.batteryCurrent != data.batteryCurrent) changed = true;
|
||||
if (prev.panelVoltage != data.panelVoltage) changed = true;
|
||||
if (prev.panelPower != data.panelPower) changed = true;
|
||||
if (prev.yieldToday != data.yieldToday) changed = true;
|
||||
if (prev.loadCurrent != data.loadCurrent) changed = true;
|
||||
|
||||
if (changed) {
|
||||
logData(data, "CHG", prev.packetsSinceLastLog);
|
||||
} else if (now - prev.lastLogTime >= LOG_INTERVAL_MS) {
|
||||
logData(data, "HEARTBEAT", prev.packetsSinceLastLog);
|
||||
} else {
|
||||
return; // Nothing to log
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Update snapshot
|
||||
prev.packetsSinceLastLog = 0;
|
||||
prev.valid = true;
|
||||
prev.chargeState = data.chargeState;
|
||||
prev.batteryVoltage = data.batteryVoltage;
|
||||
prev.batteryCurrent = data.batteryCurrent;
|
||||
prev.panelVoltage = data.panelVoltage;
|
||||
prev.panelPower = data.panelPower;
|
||||
prev.yieldToday = data.yieldToday;
|
||||
prev.loadCurrent = data.loadCurrent;
|
||||
prev.lastLogTime = now;
|
||||
}
|
||||
};
|
||||
|
||||
LoggerCallback callback;
|
||||
prev.packetsSinceLastLog = 0;
|
||||
prev.valid = true;
|
||||
prev.chargeState = s.chargeState;
|
||||
prev.batteryVoltage = s.batteryVoltage;
|
||||
prev.batteryCurrent = s.batteryCurrent;
|
||||
prev.panelPower = s.panelPower;
|
||||
prev.yieldToday = s.yieldToday;
|
||||
prev.loadCurrent = s.loadCurrent;
|
||||
prev.lastLogTime = now;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
@@ -135,14 +124,12 @@ void setup() {
|
||||
|
||||
if (!victron.begin(5)) {
|
||||
Serial.println("ERROR: Failed to initialize VictronBLE!");
|
||||
Serial.println(victron.getLastError());
|
||||
while (1) delay(1000);
|
||||
}
|
||||
|
||||
victron.setDebug(false);
|
||||
victron.setCallback(&callback);
|
||||
victron.setCallback(onVictronData);
|
||||
|
||||
// Add your devices here
|
||||
victron.addDevice(
|
||||
"Rainbow48V",
|
||||
"E4:05:42:34:14:F3",
|
||||
@@ -157,7 +144,7 @@ void setup() {
|
||||
DEVICE_TYPE_SOLAR_CHARGER
|
||||
);
|
||||
|
||||
Serial.println("Configured " + String(victron.getDeviceCount()) + " devices");
|
||||
Serial.printf("Configured %d devices\n", (int)victron.getDeviceCount());
|
||||
Serial.println("Logging on change, or every 60s heartbeat\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,154 +1,129 @@
|
||||
/**
|
||||
* VictronBLE Example
|
||||
* VictronBLE Multi-Device Example
|
||||
*
|
||||
* This example demonstrates how to use the VictronBLE library to read data
|
||||
* from multiple Victron devices simultaneously.
|
||||
*
|
||||
* Hardware Requirements:
|
||||
* - ESP32 board
|
||||
* - Victron devices with BLE (SmartSolar, SmartShunt, etc.)
|
||||
* Demonstrates reading data from multiple Victron device types via BLE.
|
||||
*
|
||||
* Setup:
|
||||
* 1. Get your device encryption keys from the VictronConnect app:
|
||||
* - Open VictronConnect
|
||||
* - Connect to your device
|
||||
* - Go to Settings > Product Info
|
||||
* - Enable "Instant readout via Bluetooth"
|
||||
* - Click "Show" next to "Instant readout details"
|
||||
* - Copy the encryption key (32 hex characters)
|
||||
*
|
||||
* 2. Update the device configurations below with your devices' MAC addresses
|
||||
* and encryption keys
|
||||
* 1. Get your device encryption keys from VictronConnect app
|
||||
* (Settings > Product Info > Instant readout via Bluetooth > Show)
|
||||
* 2. Update the device configurations below with your MAC and key
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "VictronBLE.h"
|
||||
|
||||
// Create VictronBLE instance
|
||||
VictronBLE victron;
|
||||
|
||||
// Device callback class - gets called when new data arrives
|
||||
class MyVictronCallback : public VictronDeviceCallback {
|
||||
public:
|
||||
uint32_t solarChargerCount = 0;
|
||||
uint32_t batteryMonitorCount = 0;
|
||||
uint32_t inverterCount = 0;
|
||||
uint32_t dcdcConverterCount = 0;
|
||||
static uint32_t solarChargerCount = 0;
|
||||
static uint32_t batteryMonitorCount = 0;
|
||||
static uint32_t inverterCount = 0;
|
||||
static uint32_t dcdcConverterCount = 0;
|
||||
|
||||
void onSolarChargerData(const SolarChargerData& data) override {
|
||||
solarChargerCount++;
|
||||
Serial.println("\n=== Solar Charger: " + data.deviceName + " (#" + String(solarChargerCount) + ") ===");
|
||||
Serial.println("MAC: " + data.macAddress);
|
||||
Serial.println("RSSI: " + String(data.rssi) + " dBm");
|
||||
Serial.println("State: " + getChargeStateName(data.chargeState));
|
||||
Serial.println("Battery: " + String(data.batteryVoltage, 2) + " V");
|
||||
Serial.println("Current: " + String(data.batteryCurrent, 2) + " A");
|
||||
Serial.println("Panel Voltage: " + String(data.panelVoltage, 1) + " V");
|
||||
Serial.println("Panel Power: " + String(data.panelPower) + " W");
|
||||
Serial.println("Yield Today: " + String(data.yieldToday) + " Wh");
|
||||
if (data.loadCurrent > 0) {
|
||||
Serial.println("Load Current: " + String(data.loadCurrent, 2) + " A");
|
||||
}
|
||||
Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago");
|
||||
static const char* chargeStateName(uint8_t state) {
|
||||
switch (state) {
|
||||
case CHARGER_OFF: return "Off";
|
||||
case CHARGER_LOW_POWER: return "Low Power";
|
||||
case CHARGER_FAULT: return "Fault";
|
||||
case CHARGER_BULK: return "Bulk";
|
||||
case CHARGER_ABSORPTION: return "Absorption";
|
||||
case CHARGER_FLOAT: return "Float";
|
||||
case CHARGER_STORAGE: return "Storage";
|
||||
case CHARGER_EQUALIZE: return "Equalize";
|
||||
case CHARGER_INVERTING: return "Inverting";
|
||||
case CHARGER_POWER_SUPPLY: return "Power Supply";
|
||||
case CHARGER_EXTERNAL_CONTROL: return "External Control";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void onBatteryMonitorData(const BatteryMonitorData& data) override {
|
||||
batteryMonitorCount++;
|
||||
Serial.println("\n=== Battery Monitor: " + data.deviceName + " (#" + String(batteryMonitorCount) + ") ===");
|
||||
Serial.println("MAC: " + data.macAddress);
|
||||
Serial.println("RSSI: " + String(data.rssi) + " dBm");
|
||||
Serial.println("Voltage: " + String(data.voltage, 2) + " V");
|
||||
Serial.println("Current: " + String(data.current, 2) + " A");
|
||||
Serial.println("SOC: " + String(data.soc, 1) + " %");
|
||||
Serial.println("Consumed: " + String(data.consumedAh, 2) + " Ah");
|
||||
|
||||
if (data.remainingMinutes < 65535) {
|
||||
int hours = data.remainingMinutes / 60;
|
||||
int mins = data.remainingMinutes % 60;
|
||||
Serial.println("Time Remaining: " + String(hours) + "h " + String(mins) + "m");
|
||||
void onVictronData(const VictronDevice* dev) {
|
||||
switch (dev->deviceType) {
|
||||
case DEVICE_TYPE_SOLAR_CHARGER: {
|
||||
const auto& s = dev->solar;
|
||||
solarChargerCount++;
|
||||
Serial.printf("\n=== Solar Charger: %s (#%lu) ===\n", dev->name, solarChargerCount);
|
||||
Serial.printf("MAC: %s\n", dev->mac);
|
||||
Serial.printf("RSSI: %d dBm\n", dev->rssi);
|
||||
Serial.printf("State: %s\n", chargeStateName(s.chargeState));
|
||||
Serial.printf("Battery: %.2f V\n", s.batteryVoltage);
|
||||
Serial.printf("Current: %.2f A\n", s.batteryCurrent);
|
||||
Serial.printf("Panel Power: %.0f W\n", s.panelPower);
|
||||
Serial.printf("Yield Today: %u Wh\n", s.yieldToday);
|
||||
if (s.loadCurrent > 0)
|
||||
Serial.printf("Load Current: %.2f A\n", s.loadCurrent);
|
||||
Serial.printf("Last Update: %lus ago\n", (millis() - dev->lastUpdate) / 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.temperature > 0) {
|
||||
Serial.println("Temperature: " + String(data.temperature, 1) + " °C");
|
||||
case DEVICE_TYPE_BATTERY_MONITOR: {
|
||||
const auto& b = dev->battery;
|
||||
batteryMonitorCount++;
|
||||
Serial.printf("\n=== Battery Monitor: %s (#%lu) ===\n", dev->name, batteryMonitorCount);
|
||||
Serial.printf("MAC: %s\n", dev->mac);
|
||||
Serial.printf("RSSI: %d dBm\n", dev->rssi);
|
||||
Serial.printf("Voltage: %.2f V\n", b.voltage);
|
||||
Serial.printf("Current: %.2f A\n", b.current);
|
||||
Serial.printf("SOC: %.1f %%\n", b.soc);
|
||||
Serial.printf("Consumed: %.2f Ah\n", b.consumedAh);
|
||||
if (b.remainingMinutes < 65535)
|
||||
Serial.printf("Time Remaining: %dh %dm\n", b.remainingMinutes / 60, b.remainingMinutes % 60);
|
||||
if (b.temperature > 0)
|
||||
Serial.printf("Temperature: %.1f C\n", b.temperature);
|
||||
if (b.auxVoltage > 0)
|
||||
Serial.printf("Aux Voltage: %.2f V\n", b.auxVoltage);
|
||||
if (b.alarmLowVoltage || b.alarmHighVoltage || b.alarmLowSOC ||
|
||||
b.alarmLowTemperature || b.alarmHighTemperature) {
|
||||
Serial.print("ALARMS:");
|
||||
if (b.alarmLowVoltage) Serial.print(" LOW-V");
|
||||
if (b.alarmHighVoltage) Serial.print(" HIGH-V");
|
||||
if (b.alarmLowSOC) Serial.print(" LOW-SOC");
|
||||
if (b.alarmLowTemperature) Serial.print(" LOW-TEMP");
|
||||
if (b.alarmHighTemperature) Serial.print(" HIGH-TEMP");
|
||||
Serial.println();
|
||||
}
|
||||
Serial.printf("Last Update: %lus ago\n", (millis() - dev->lastUpdate) / 1000);
|
||||
break;
|
||||
}
|
||||
if (data.auxVoltage > 0) {
|
||||
Serial.println("Aux Voltage: " + String(data.auxVoltage, 2) + " V");
|
||||
case DEVICE_TYPE_INVERTER: {
|
||||
const auto& inv = dev->inverter;
|
||||
inverterCount++;
|
||||
Serial.printf("\n=== Inverter/Charger: %s (#%lu) ===\n", dev->name, inverterCount);
|
||||
Serial.printf("MAC: %s\n", dev->mac);
|
||||
Serial.printf("RSSI: %d dBm\n", dev->rssi);
|
||||
Serial.printf("Battery: %.2f V\n", inv.batteryVoltage);
|
||||
Serial.printf("Current: %.2f A\n", inv.batteryCurrent);
|
||||
Serial.printf("AC Power: %.0f W\n", inv.acPower);
|
||||
Serial.printf("State: %d\n", inv.state);
|
||||
if (inv.alarmLowVoltage || inv.alarmHighVoltage ||
|
||||
inv.alarmHighTemperature || inv.alarmOverload) {
|
||||
Serial.print("ALARMS:");
|
||||
if (inv.alarmLowVoltage) Serial.print(" LOW-V");
|
||||
if (inv.alarmHighVoltage) Serial.print(" HIGH-V");
|
||||
if (inv.alarmHighTemperature) Serial.print(" TEMP");
|
||||
if (inv.alarmOverload) Serial.print(" OVERLOAD");
|
||||
Serial.println();
|
||||
}
|
||||
Serial.printf("Last Update: %lus ago\n", (millis() - dev->lastUpdate) / 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
// Print alarms
|
||||
if (data.alarmLowVoltage || data.alarmHighVoltage || data.alarmLowSOC ||
|
||||
data.alarmLowTemperature || data.alarmHighTemperature) {
|
||||
Serial.print("ALARMS: ");
|
||||
if (data.alarmLowVoltage) Serial.print("LOW-V ");
|
||||
if (data.alarmHighVoltage) Serial.print("HIGH-V ");
|
||||
if (data.alarmLowSOC) Serial.print("LOW-SOC ");
|
||||
if (data.alarmLowTemperature) Serial.print("LOW-TEMP ");
|
||||
if (data.alarmHighTemperature) Serial.print("HIGH-TEMP ");
|
||||
Serial.println();
|
||||
case DEVICE_TYPE_DCDC_CONVERTER: {
|
||||
const auto& dc = dev->dcdc;
|
||||
dcdcConverterCount++;
|
||||
Serial.printf("\n=== DC-DC Converter: %s (#%lu) ===\n", dev->name, dcdcConverterCount);
|
||||
Serial.printf("MAC: %s\n", dev->mac);
|
||||
Serial.printf("RSSI: %d dBm\n", dev->rssi);
|
||||
Serial.printf("Input: %.2f V\n", dc.inputVoltage);
|
||||
Serial.printf("Output: %.2f V\n", dc.outputVoltage);
|
||||
Serial.printf("Current: %.2f A\n", dc.outputCurrent);
|
||||
Serial.printf("State: %d\n", dc.chargeState);
|
||||
if (dc.errorCode != 0)
|
||||
Serial.printf("Error Code: %d\n", dc.errorCode);
|
||||
Serial.printf("Last Update: %lus ago\n", (millis() - dev->lastUpdate) / 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
void onInverterData(const InverterData& data) override {
|
||||
inverterCount++;
|
||||
Serial.println("\n=== Inverter/Charger: " + data.deviceName + " (#" + String(inverterCount) + ") ===");
|
||||
Serial.println("MAC: " + data.macAddress);
|
||||
Serial.println("RSSI: " + String(data.rssi) + " dBm");
|
||||
Serial.println("Battery: " + String(data.batteryVoltage, 2) + " V");
|
||||
Serial.println("Current: " + String(data.batteryCurrent, 2) + " A");
|
||||
Serial.println("AC Power: " + String(data.acPower) + " W");
|
||||
Serial.println("State: " + String(data.state));
|
||||
|
||||
// Print alarms
|
||||
if (data.alarmLowVoltage || data.alarmHighVoltage ||
|
||||
data.alarmHighTemperature || data.alarmOverload) {
|
||||
Serial.print("ALARMS: ");
|
||||
if (data.alarmLowVoltage) Serial.print("LOW-V ");
|
||||
if (data.alarmHighVoltage) Serial.print("HIGH-V ");
|
||||
if (data.alarmHighTemperature) Serial.print("TEMP ");
|
||||
if (data.alarmOverload) Serial.print("OVERLOAD ");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago");
|
||||
}
|
||||
|
||||
void onDCDCConverterData(const DCDCConverterData& data) override {
|
||||
dcdcConverterCount++;
|
||||
Serial.println("\n=== DC-DC Converter: " + data.deviceName + " (#" + String(dcdcConverterCount) + ") ===");
|
||||
Serial.println("MAC: " + data.macAddress);
|
||||
Serial.println("RSSI: " + String(data.rssi) + " dBm");
|
||||
Serial.println("Input: " + String(data.inputVoltage, 2) + " V");
|
||||
Serial.println("Output: " + String(data.outputVoltage, 2) + " V");
|
||||
Serial.println("Current: " + String(data.outputCurrent, 2) + " A");
|
||||
Serial.println("State: " + String(data.chargeState));
|
||||
if (data.errorCode != 0) {
|
||||
Serial.println("Error Code: " + String(data.errorCode));
|
||||
}
|
||||
Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago");
|
||||
}
|
||||
|
||||
private:
|
||||
String getChargeStateName(SolarChargerState state) {
|
||||
switch (state) {
|
||||
case CHARGER_OFF: return "Off";
|
||||
case CHARGER_LOW_POWER: return "Low Power";
|
||||
case CHARGER_FAULT: return "Fault";
|
||||
case CHARGER_BULK: return "Bulk";
|
||||
case CHARGER_ABSORPTION: return "Absorption";
|
||||
case CHARGER_FLOAT: return "Float";
|
||||
case CHARGER_STORAGE: return "Storage";
|
||||
case CHARGER_EQUALIZE: return "Equalize";
|
||||
case CHARGER_INVERTING: return "Inverting";
|
||||
case CHARGER_POWER_SUPPLY: return "Power Supply";
|
||||
case CHARGER_EXTERNAL_CONTROL: return "External Control";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MyVictronCallback callback;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
@@ -158,102 +133,33 @@ void setup() {
|
||||
Serial.println("VictronBLE Multi-Device Example");
|
||||
Serial.println("=================================\n");
|
||||
|
||||
// Initialize VictronBLE with 5 second scan duration
|
||||
if (!victron.begin(5)) {
|
||||
Serial.println("ERROR: Failed to initialize VictronBLE!");
|
||||
Serial.println(victron.getLastError());
|
||||
while (1) delay(1000);
|
||||
}
|
||||
|
||||
// Enable debug output (optional)
|
||||
victron.setDebug(false);
|
||||
|
||||
// Set callback for data updates
|
||||
victron.setCallback(&callback);
|
||||
|
||||
// Add your devices here
|
||||
// Replace with your actual MAC addresses and encryption keys
|
||||
|
||||
// CORRECT in Alternative
|
||||
// Rainbow48V at MAC e4:05:42:34:14:f3
|
||||
|
||||
// Temporary - Scott Example
|
||||
victron.addDevice(
|
||||
"Rainbow48V", // Device name
|
||||
"E4:05:42:34:14:F3", // MAC address
|
||||
"0ec3adf7433dd61793ff2f3b8ad32ed8", // Encryption key (32 hex chars)
|
||||
DEVICE_TYPE_SOLAR_CHARGER // Device type
|
||||
);
|
||||
victron.setCallback(onVictronData);
|
||||
|
||||
victron.addDevice(
|
||||
"ScottTrailer", // Device name
|
||||
"e64559783cfb",
|
||||
"3fa658aded4f309b9bc17a2318cb1f56",
|
||||
DEVICE_TYPE_SOLAR_CHARGER // Device type
|
||||
);
|
||||
|
||||
// Example: Solar Charger #1
|
||||
/*
|
||||
victron.addDevice(
|
||||
"MPPT 100/30", // Device name
|
||||
"E7:48:D4:28:B7:9C", // MAC address
|
||||
"0df4d0395b7d1a876c0c33ecb9e70dcd", // Encryption key (32 hex chars)
|
||||
DEVICE_TYPE_SOLAR_CHARGER // Device type
|
||||
);
|
||||
*/
|
||||
|
||||
// Example: Solar Charger #2
|
||||
/*
|
||||
victron.addDevice(
|
||||
"MPPT 75/15",
|
||||
"AA:BB:CC:DD:EE:FF",
|
||||
"1234567890abcdef1234567890abcdef",
|
||||
"Rainbow48V",
|
||||
"E4:05:42:34:14:F3",
|
||||
"0ec3adf7433dd61793ff2f3b8ad32ed8",
|
||||
DEVICE_TYPE_SOLAR_CHARGER
|
||||
);
|
||||
*/
|
||||
|
||||
// Example: Battery Monitor (SmartShunt)
|
||||
/*
|
||||
victron.addDevice(
|
||||
"SmartShunt",
|
||||
"11:22:33:44:55:66",
|
||||
"fedcba0987654321fedcba0987654321",
|
||||
DEVICE_TYPE_BATTERY_MONITOR
|
||||
"ScottTrailer",
|
||||
"e64559783cfb",
|
||||
"3fa658aded4f309b9bc17a2318cb1f56",
|
||||
DEVICE_TYPE_SOLAR_CHARGER
|
||||
);
|
||||
*/
|
||||
|
||||
// Example: Inverter/Charger
|
||||
/*
|
||||
victron.addDevice(
|
||||
"MultiPlus",
|
||||
"99:88:77:66:55:44",
|
||||
"abcdefabcdefabcdefabcdefabcdefab",
|
||||
DEVICE_TYPE_INVERTER
|
||||
);
|
||||
*/
|
||||
|
||||
Serial.println("Configured " + String(victron.getDeviceCount()) + " devices");
|
||||
Serial.printf("Configured %d devices\n", (int)victron.getDeviceCount());
|
||||
Serial.println("\nStarting BLE scan...\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Process BLE scanning and data updates
|
||||
victron.loop();
|
||||
|
||||
// Optional: You can also manually query device data
|
||||
// This is useful if you're not using callbacks
|
||||
/*
|
||||
SolarChargerData solarData;
|
||||
if (victron.getSolarChargerData("E7:48:D4:28:B7:9C", solarData)) {
|
||||
// Do something with solarData
|
||||
}
|
||||
|
||||
BatteryMonitorData batteryData;
|
||||
if (victron.getBatteryMonitorData("11:22:33:44:55:66", batteryData)) {
|
||||
// Do something with batteryData
|
||||
}
|
||||
*/
|
||||
|
||||
// Add a small delay to avoid overwhelming the serial output
|
||||
delay(100);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ struct __attribute__((packed)) SolarChargerPacket {
|
||||
uint8_t chargeState;
|
||||
float batteryVoltage; // V
|
||||
float batteryCurrent; // A
|
||||
float panelVoltage; // V
|
||||
float panelPower; // W
|
||||
uint16_t yieldToday; // Wh
|
||||
float loadCurrent; // A
|
||||
@@ -82,13 +81,12 @@ void onDataRecv(const uint8_t* senderMac, const uint8_t* data, int len) {
|
||||
memcpy(name, pkt->deviceName, 16);
|
||||
name[16] = '\0';
|
||||
|
||||
Serial.printf("[RX #%lu] %s | State:%s Batt:%.2fV %.2fA PV:%.1fV %.0fW Yield:%uWh",
|
||||
Serial.printf("[RX #%lu] %s | State:%s Batt:%.2fV %.2fA PV:%.0fW Yield:%uWh",
|
||||
recvCount,
|
||||
name,
|
||||
chargeStateName(pkt->chargeState),
|
||||
pkt->batteryVoltage,
|
||||
pkt->batteryCurrent,
|
||||
pkt->panelVoltage,
|
||||
pkt->panelPower,
|
||||
pkt->yieldToday);
|
||||
|
||||
@@ -202,7 +200,7 @@ void loop() {
|
||||
M5.Lcd.printf("Batt: %.2fA\n", pkt.batteryCurrent);
|
||||
|
||||
// Row 3: PV
|
||||
M5.Lcd.printf("PV: %.1fV %.0fW\n", pkt.panelVoltage, pkt.panelPower);
|
||||
M5.Lcd.printf("PV: %.0fW\n", pkt.panelPower);
|
||||
|
||||
// Row 4: yield + load
|
||||
M5.Lcd.printf("Yield: %uWh", pkt.yieldToday);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* VictronBLE Repeater Example
|
||||
*
|
||||
* Collects Solar Charger data via BLE and transmits the latest
|
||||
* readings over ESPNow broadcast every 30 seconds. Place this ESP32
|
||||
* readings over ESPNow broadcast every 5 seconds. Place this ESP32
|
||||
* near Victron devices and use a separate Receiver ESP32 at a distance.
|
||||
*
|
||||
* ESPNow range is typically much greater than BLE (~200m+ line of sight).
|
||||
@@ -23,7 +23,6 @@ struct __attribute__((packed)) SolarChargerPacket {
|
||||
uint8_t chargeState;
|
||||
float batteryVoltage; // V
|
||||
float batteryCurrent; // A
|
||||
float panelVoltage; // V
|
||||
float panelPower; // W
|
||||
uint16_t yieldToday; // Wh
|
||||
float loadCurrent; // A
|
||||
@@ -31,10 +30,8 @@ struct __attribute__((packed)) SolarChargerPacket {
|
||||
char deviceName[16]; // Null-terminated, truncated
|
||||
};
|
||||
|
||||
// Broadcast address
|
||||
static const uint8_t BROADCAST_ADDR[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
static const unsigned long SEND_INTERVAL_MS = 5000; // 30 seconds
|
||||
static const unsigned long SEND_INTERVAL_MS = 5000;
|
||||
|
||||
static uint32_t sendCount = 0;
|
||||
static uint32_t sendFailCount = 0;
|
||||
@@ -49,7 +46,6 @@ static unsigned long lastSendTime = 0;
|
||||
|
||||
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)
|
||||
@@ -59,34 +55,28 @@ static int findOrAddCached(const char* name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
class RepeaterCallback : public VictronDeviceCallback {
|
||||
public:
|
||||
void onSolarChargerData(const SolarChargerData& data) override {
|
||||
blePacketCount++;
|
||||
void onVictronData(const VictronDevice* dev) {
|
||||
if (dev->deviceType != DEVICE_TYPE_SOLAR_CHARGER) return;
|
||||
blePacketCount++;
|
||||
const auto& s = dev->solar;
|
||||
|
||||
// Build packet
|
||||
SolarChargerPacket pkt;
|
||||
pkt.chargeState = static_cast<uint8_t>(data.chargeState);
|
||||
pkt.batteryVoltage = data.batteryVoltage;
|
||||
pkt.batteryCurrent = data.batteryCurrent;
|
||||
pkt.panelVoltage = data.panelVoltage;
|
||||
pkt.panelPower = data.panelPower;
|
||||
pkt.yieldToday = data.yieldToday;
|
||||
pkt.loadCurrent = data.loadCurrent;
|
||||
pkt.rssi = data.rssi;
|
||||
memset(pkt.deviceName, 0, sizeof(pkt.deviceName));
|
||||
strncpy(pkt.deviceName, data.deviceName.c_str(), sizeof(pkt.deviceName) - 1);
|
||||
SolarChargerPacket pkt;
|
||||
pkt.chargeState = s.chargeState;
|
||||
pkt.batteryVoltage = s.batteryVoltage;
|
||||
pkt.batteryCurrent = s.batteryCurrent;
|
||||
pkt.panelPower = s.panelPower;
|
||||
pkt.yieldToday = s.yieldToday;
|
||||
pkt.loadCurrent = s.loadCurrent;
|
||||
pkt.rssi = dev->rssi;
|
||||
memset(pkt.deviceName, 0, sizeof(pkt.deviceName));
|
||||
strncpy(pkt.deviceName, dev->name, sizeof(pkt.deviceName) - 1);
|
||||
|
||||
// Cache it
|
||||
int idx = findOrAddCached(pkt.deviceName);
|
||||
if (idx >= 0) {
|
||||
cachedPackets[idx] = pkt;
|
||||
cachedValid[idx] = true;
|
||||
}
|
||||
int idx = findOrAddCached(pkt.deviceName);
|
||||
if (idx >= 0) {
|
||||
cachedPackets[idx] = pkt;
|
||||
cachedValid[idx] = true;
|
||||
}
|
||||
};
|
||||
|
||||
RepeaterCallback callback;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
@@ -98,7 +88,8 @@ void setup() {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.disconnect();
|
||||
|
||||
Serial.println("MAC: " + WiFi.macAddress());
|
||||
Serial.print("MAC: ");
|
||||
Serial.println(WiFi.macAddress());
|
||||
|
||||
// Init ESPNow
|
||||
if (esp_now_init() != ESP_OK) {
|
||||
@@ -106,10 +97,9 @@ void setup() {
|
||||
while (1) delay(1000);
|
||||
}
|
||||
|
||||
// Add broadcast peer
|
||||
esp_now_peer_info_t peerInfo = {};
|
||||
memcpy(peerInfo.peer_addr, BROADCAST_ADDR, 6);
|
||||
peerInfo.channel = 0; // Use current channel
|
||||
peerInfo.channel = 0;
|
||||
peerInfo.encrypt = false;
|
||||
|
||||
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
|
||||
@@ -122,14 +112,12 @@ void setup() {
|
||||
// Init VictronBLE
|
||||
if (!victron.begin(5)) {
|
||||
Serial.println("ERROR: Failed to initialize VictronBLE!");
|
||||
Serial.println(victron.getLastError());
|
||||
while (1) delay(1000);
|
||||
}
|
||||
|
||||
victron.setDebug(false);
|
||||
victron.setCallback(&callback);
|
||||
victron.setCallback(onVictronData);
|
||||
|
||||
// Add your devices here
|
||||
victron.addDevice(
|
||||
"Rainbow48V",
|
||||
"E4:05:42:34:14:F3",
|
||||
@@ -144,14 +132,13 @@ void setup() {
|
||||
DEVICE_TYPE_SOLAR_CHARGER
|
||||
);
|
||||
|
||||
Serial.println("Configured " + String(victron.getDeviceCount()) + " BLE devices");
|
||||
Serial.println("Packet size: " + String(sizeof(SolarChargerPacket)) + " bytes\n");
|
||||
Serial.printf("Configured %d BLE devices\n", (int)victron.getDeviceCount());
|
||||
Serial.printf("Packet size: %d bytes\n\n", (int)sizeof(SolarChargerPacket));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
victron.loop();
|
||||
|
||||
// Send cached packets every 30 seconds
|
||||
unsigned long now = millis();
|
||||
if (now - lastSendTime >= SEND_INTERVAL_MS) {
|
||||
lastSendTime = now;
|
||||
@@ -167,11 +154,10 @@ void loop() {
|
||||
if (result == ESP_OK) {
|
||||
sendCount++;
|
||||
sent++;
|
||||
Serial.printf("[ESPNow] Sent %s: %.2fV %.1fA PV:%.1fV %.0fW State:%d\n",
|
||||
Serial.printf("[ESPNow] Sent %s: %.2fV %.1fA %.0fW State:%d\n",
|
||||
cachedPackets[i].deviceName,
|
||||
cachedPackets[i].batteryVoltage,
|
||||
cachedPackets[i].batteryCurrent,
|
||||
cachedPackets[i].panelVoltage,
|
||||
cachedPackets[i].panelPower,
|
||||
cachedPackets[i].chargeState);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user