New version with smaller memory footprint etc
This commit is contained in:
@@ -102,3 +102,19 @@ Arduino/ESP32 library for reading Victron Energy devices via Bluetooth Low Energ
|
|||||||
- library.json
|
- library.json
|
||||||
- library.properties
|
- library.properties
|
||||||
|
|
||||||
|
|
||||||
|
### Session: 2026-02-12 18:23
|
||||||
|
**Commits:**
|
||||||
|
```
|
||||||
|
a843eb9 Keep v0.3.1
|
||||||
|
5a210fb Experimenting with a claude file and created new logging example
|
||||||
|
```
|
||||||
|
**Modified files:**
|
||||||
|
- .claude/CLAUDE.md
|
||||||
|
- README.md
|
||||||
|
- VERSIONS
|
||||||
|
- library.json
|
||||||
|
- library.properties
|
||||||
|
- src/VictronBLE.cpp
|
||||||
|
- src/VictronBLE.h
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
VictronBLE::VictronBLE()
|
VictronBLE::VictronBLE()
|
||||||
: pBLEScan(nullptr), callback(nullptr), debugEnabled(false),
|
: pBLEScan(nullptr), scanCallback(nullptr), callback(nullptr),
|
||||||
scanDuration(5), initialized(false) {
|
debugEnabled(false), scanDuration(5), initialized(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
@@ -24,35 +24,38 @@ VictronBLE::~VictronBLE() {
|
|||||||
if (pBLEScan) {
|
if (pBLEScan) {
|
||||||
pBLEScan->stop();
|
pBLEScan->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete scanCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize BLE
|
// Initialize BLE
|
||||||
bool VictronBLE::begin(uint32_t scanDuration) {
|
bool VictronBLE::begin(uint32_t scanDuration) {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
debugPrint("VictronBLE already initialized");
|
if (debugEnabled) debugPrint("VictronBLE already initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->scanDuration = scanDuration;
|
this->scanDuration = scanDuration;
|
||||||
|
|
||||||
debugPrint("Initializing VictronBLE...");
|
if (debugEnabled) debugPrint("Initializing VictronBLE...");
|
||||||
|
|
||||||
BLEDevice::init("VictronBLE");
|
BLEDevice::init("VictronBLE");
|
||||||
pBLEScan = BLEDevice::getScan();
|
pBLEScan = BLEDevice::getScan();
|
||||||
|
|
||||||
if (!pBLEScan) {
|
if (!pBLEScan) {
|
||||||
lastError = "Failed to create BLE scanner";
|
lastError = "Failed to create BLE scanner";
|
||||||
debugPrint(lastError);
|
if (debugEnabled) debugPrint(lastError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBLEScan->setAdvertisedDeviceCallbacks(new VictronBLEAdvertisedDeviceCallbacks(this), true);
|
scanCallback = new VictronBLEAdvertisedDeviceCallbacks(this);
|
||||||
|
pBLEScan->setAdvertisedDeviceCallbacks(scanCallback, true);
|
||||||
pBLEScan->setActiveScan(false); // Passive scan - lower power
|
pBLEScan->setActiveScan(false); // Passive scan - lower power
|
||||||
pBLEScan->setInterval(100);
|
pBLEScan->setInterval(100);
|
||||||
pBLEScan->setWindow(99);
|
pBLEScan->setWindow(99);
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
debugPrint("VictronBLE initialized successfully");
|
if (debugEnabled) debugPrint("VictronBLE initialized successfully");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -61,13 +64,13 @@ bool VictronBLE::begin(uint32_t scanDuration) {
|
|||||||
bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
||||||
if (config.macAddress.length() == 0) {
|
if (config.macAddress.length() == 0) {
|
||||||
lastError = "MAC address cannot be empty";
|
lastError = "MAC address cannot be empty";
|
||||||
debugPrint(lastError);
|
if (debugEnabled) debugPrint(lastError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.encryptionKey.length() != 32) {
|
if (config.encryptionKey.length() != 32) {
|
||||||
lastError = "Encryption key must be 32 hex characters";
|
lastError = "Encryption key must be 32 hex characters";
|
||||||
debugPrint(lastError);
|
if (debugEnabled) debugPrint(lastError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
|||||||
|
|
||||||
// Check if device already exists
|
// Check if device already exists
|
||||||
if (devices.find(normalizedMAC) != devices.end()) {
|
if (devices.find(normalizedMAC) != devices.end()) {
|
||||||
debugPrint("Device " + normalizedMAC + " already exists, updating config");
|
if (debugEnabled) debugPrint("Device " + normalizedMAC + " already exists, updating config");
|
||||||
delete devices[normalizedMAC];
|
delete devices[normalizedMAC];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
|||||||
// Convert encryption key from hex string to bytes
|
// Convert encryption key from hex string to bytes
|
||||||
if (!hexStringToBytes(config.encryptionKey, info->encryptionKeyBytes, 16)) {
|
if (!hexStringToBytes(config.encryptionKey, info->encryptionKeyBytes, 16)) {
|
||||||
lastError = "Invalid encryption key format";
|
lastError = "Invalid encryption key format";
|
||||||
debugPrint(lastError);
|
if (debugEnabled) debugPrint(lastError);
|
||||||
delete info;
|
delete info;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -100,8 +103,8 @@ bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
|||||||
|
|
||||||
devices[normalizedMAC] = info;
|
devices[normalizedMAC] = info;
|
||||||
|
|
||||||
debugPrint("Added device: " + config.name + " (MAC: " + normalizedMAC + ")");
|
|
||||||
if (debugEnabled) {
|
if (debugEnabled) {
|
||||||
|
debugPrint("Added device: " + config.name + " (MAC: " + normalizedMAC + ")");
|
||||||
debugPrint(" Original MAC input: " + config.macAddress);
|
debugPrint(" Original MAC input: " + config.macAddress);
|
||||||
debugPrint(" Stored normalized: " + normalizedMAC);
|
debugPrint(" Stored normalized: " + normalizedMAC);
|
||||||
}
|
}
|
||||||
@@ -109,21 +112,21 @@ bool VictronBLE::addDevice(const VictronDeviceConfig& config) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VictronBLE::addDevice(String name, String macAddress, String encryptionKey,
|
bool VictronBLE::addDevice(const String& name, const String& macAddress, const String& encryptionKey,
|
||||||
VictronDeviceType expectedType) {
|
VictronDeviceType expectedType) {
|
||||||
VictronDeviceConfig config(name, macAddress, encryptionKey, expectedType);
|
VictronDeviceConfig config(name, macAddress, encryptionKey, expectedType);
|
||||||
return addDevice(config);
|
return addDevice(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a device
|
// Remove a device
|
||||||
void VictronBLE::removeDevice(String macAddress) {
|
void VictronBLE::removeDevice(const String& macAddress) {
|
||||||
String normalizedMAC = normalizeMAC(macAddress);
|
String normalizedMAC = normalizeMAC(macAddress);
|
||||||
|
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
if (it != devices.end()) {
|
if (it != devices.end()) {
|
||||||
delete it->second;
|
delete it->second;
|
||||||
devices.erase(it);
|
devices.erase(it);
|
||||||
debugPrint("Removed device: " + normalizedMAC);
|
if (debugEnabled) debugPrint("Removed device: " + normalizedMAC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +149,7 @@ void VictronBLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice advertise
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process advertised device
|
// Process advertised device
|
||||||
void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
|
void VictronBLE::processDevice(BLEAdvertisedDevice& advertisedDevice) {
|
||||||
// Get MAC address from the advertised device
|
// Get MAC address from the advertised device
|
||||||
String mac = macAddressToString(advertisedDevice.getAddress());
|
String mac = macAddressToString(advertisedDevice.getAddress());
|
||||||
String normalizedMAC = normalizeMAC(mac);
|
String normalizedMAC = normalizeMAC(mac);
|
||||||
@@ -155,28 +158,25 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
|
|||||||
debugPrint("Raw MAC: " + mac + " -> Normalized: " + normalizedMAC);
|
debugPrint("Raw MAC: " + mac + " -> Normalized: " + normalizedMAC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider skipping with no manufacturer data?
|
// Parse manufacturer data into local struct
|
||||||
memset(&manufacturerData, 0, sizeof(manufacturerData));
|
victronManufacturerData mfgData;
|
||||||
|
memset(&mfgData, 0, sizeof(mfgData));
|
||||||
if (advertisedDevice.haveManufacturerData()) {
|
if (advertisedDevice.haveManufacturerData()) {
|
||||||
std::string mfgData = advertisedDevice.getManufacturerData();
|
std::string rawMfgData = advertisedDevice.getManufacturerData();
|
||||||
// XXX Storing it this way is not thread safe - is that issue on this ESP32?
|
if (debugEnabled) debugPrint("Getting manufacturer data: Size=" + String(rawMfgData.length()));
|
||||||
debugPrint("Getting manufacturer data: Size=" + String(mfgData.length()));
|
rawMfgData.copy(reinterpret_cast<char*>(&mfgData),
|
||||||
mfgData.copy((char*)&manufacturerData, (mfgData.length() > sizeof(manufacturerData) ? sizeof(manufacturerData) : mfgData.length()));
|
(rawMfgData.length() > sizeof(mfgData) ? sizeof(mfgData) : rawMfgData.length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer? XXX
|
|
||||||
|
|
||||||
// Debug: Log all discovered BLE devices
|
// Debug: Log all discovered BLE devices
|
||||||
if (debugEnabled) {
|
if (debugEnabled) {
|
||||||
String debugMsg = "";
|
String debugMsg = "BLE Device: " + mac;
|
||||||
|
|
||||||
debugMsg += "BLE Device: " + mac;
|
|
||||||
debugMsg += ", RSSI: " + String(advertisedDevice.getRSSI()) + " dBm";
|
debugMsg += ", RSSI: " + String(advertisedDevice.getRSSI()) + " dBm";
|
||||||
if (advertisedDevice.haveName())
|
if (advertisedDevice.haveName())
|
||||||
debugMsg += ", Name: " + String(advertisedDevice.getName().c_str());
|
debugMsg += ", Name: " + String(advertisedDevice.getName().c_str());
|
||||||
|
|
||||||
debugMsg += ", Mfg ID: 0x" + String(manufacturerData.vendorID, HEX);
|
debugMsg += ", Mfg ID: 0x" + String(mfgData.vendorID, HEX);
|
||||||
if (manufacturerData.vendorID == VICTRON_MANUFACTURER_ID) {
|
if (mfgData.vendorID == VICTRON_MANUFACTURER_ID) {
|
||||||
debugMsg += " (Victron)";
|
debugMsg += " (Victron)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,25 +186,8 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
|
|||||||
// Check if this is one of our configured devices
|
// Check if this is one of our configured devices
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
if (it == devices.end()) {
|
if (it == devices.end()) {
|
||||||
// XXX Check if the device is a Victron device
|
if (debugEnabled && mfgData.vendorID == VICTRON_MANUFACTURER_ID) {
|
||||||
// This needs lots of improvemet and only do in debug
|
debugPrint("Found unmonitored Victron Device: " + normalizedMAC);
|
||||||
if (manufacturerData.vendorID == VICTRON_MANUFACTURER_ID) {
|
|
||||||
debugPrint("Found unmonitored Victron Device: " + normalizeMAC(mac));
|
|
||||||
// DeviceInfo* deviceInfo = new DeviceInfo(mac, advertisedDevice.getName());
|
|
||||||
// devices.insert({normalizedMAC, deviceInfo});
|
|
||||||
// XXX What type of Victron device is it?
|
|
||||||
// Check if it's a Victron Energy device
|
|
||||||
/*
|
|
||||||
if (advertisedDevice.haveServiceData()) {
|
|
||||||
std::string serviceData = advertisedDevice.getServiceData();
|
|
||||||
if (serviceData.length() >= 2) {
|
|
||||||
uint16_t serviceId = (uint8_t)serviceData[1] << 8 | (uint8_t)serviceData[0];
|
|
||||||
if (serviceId == VICTRON_ENERGY_SERVICE_ID) {
|
|
||||||
debugPrint("Found Victron Energy Device: " + mac);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
return; // Not a device we're monitoring
|
return; // Not a device we're monitoring
|
||||||
}
|
}
|
||||||
@@ -212,15 +195,15 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
|
|||||||
DeviceInfo* deviceInfo = it->second;
|
DeviceInfo* deviceInfo = it->second;
|
||||||
|
|
||||||
// Check if it's Victron (manufacturer ID 0x02E1)
|
// Check if it's Victron (manufacturer ID 0x02E1)
|
||||||
if (manufacturerData.vendorID != VICTRON_MANUFACTURER_ID) {
|
if (mfgData.vendorID != VICTRON_MANUFACTURER_ID) {
|
||||||
debugPrint("Skipping non VICTRON");
|
if (debugEnabled) debugPrint("Skipping non VICTRON");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint("Processing data from: " + deviceInfo->config.name);
|
if (debugEnabled) debugPrint("Processing data from: " + deviceInfo->config.name);
|
||||||
|
|
||||||
// Parse the advertisement
|
// Parse the advertisement
|
||||||
if (parseAdvertisement(normalizedMAC)) {
|
if (parseAdvertisement(deviceInfo, mfgData)) {
|
||||||
// Update RSSI
|
// Update RSSI
|
||||||
if (deviceInfo->data) {
|
if (deviceInfo->data) {
|
||||||
deviceInfo->data->rssi = advertisedDevice.getRSSI();
|
deviceInfo->data->rssi = advertisedDevice.getRSSI();
|
||||||
@@ -230,55 +213,47 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse advertisement data
|
// Parse advertisement data
|
||||||
bool VictronBLE::parseAdvertisement(const String& macAddress) {
|
bool VictronBLE::parseAdvertisement(DeviceInfo* deviceInfo, const victronManufacturerData& mfgData) {
|
||||||
// XXX We already searched above - try not to again?
|
|
||||||
auto it = devices.find(macAddress);
|
|
||||||
if (it == devices.end()) {
|
|
||||||
debugPrint("parseAdvertisement: Device not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceInfo* deviceInfo = it->second;
|
|
||||||
|
|
||||||
if (debugEnabled) {
|
if (debugEnabled) {
|
||||||
debugPrint("Vendor ID: 0x" + String(manufacturerData.vendorID, HEX));
|
debugPrint("Vendor ID: 0x" + String(mfgData.vendorID, HEX));
|
||||||
debugPrint("Beacon Type: 0x" + String(manufacturerData.beaconType, HEX));
|
debugPrint("Beacon Type: 0x" + String(mfgData.beaconType, HEX));
|
||||||
debugPrint("Record Type: 0x" + String(manufacturerData.victronRecordType, HEX));
|
debugPrint("Record Type: 0x" + String(mfgData.victronRecordType, HEX));
|
||||||
debugPrint("Nonce: 0x" + String(manufacturerData.nonceDataCounter, HEX));
|
debugPrint("Nonce: 0x" + String(mfgData.nonceDataCounter, HEX));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build IV (initialization vector) from nonce
|
// Build IV (initialization vector) from nonce
|
||||||
// IV is 16 bytes: nonce (2 bytes little-endian) + zeros (14 bytes)
|
// IV is 16 bytes: nonce (2 bytes little-endian) + zeros (14 bytes)
|
||||||
uint8_t iv[16] = {0};
|
uint8_t iv[16] = {0};
|
||||||
iv[0] = manufacturerData.nonceDataCounter & 0xFF; // Low byte
|
iv[0] = mfgData.nonceDataCounter & 0xFF; // Low byte
|
||||||
iv[1] = (manufacturerData.nonceDataCounter >> 8) & 0xFF; // High byte
|
iv[1] = (mfgData.nonceDataCounter >> 8) & 0xFF; // High byte
|
||||||
// Remaining bytes stay zero
|
// Remaining bytes stay zero
|
||||||
|
|
||||||
// Decrypt the data
|
// Decrypt the data
|
||||||
uint8_t decrypted[32]; // Max expected size
|
const size_t encryptedLen = sizeof(mfgData.victronEncryptedData);
|
||||||
if (!decryptAdvertisement(manufacturerData.victronEncryptedData,
|
uint8_t decrypted[encryptedLen];
|
||||||
sizeof(manufacturerData.victronEncryptedData),
|
if (!decryptAdvertisement(mfgData.victronEncryptedData,
|
||||||
|
encryptedLen,
|
||||||
deviceInfo->encryptionKeyBytes, iv, decrypted)) {
|
deviceInfo->encryptionKeyBytes, iv, decrypted)) {
|
||||||
lastError = "Decryption failed";
|
lastError = "Decryption failed";
|
||||||
debugPrint(lastError);
|
if (debugEnabled) debugPrint(lastError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse based on device type
|
// Parse based on device type
|
||||||
bool parseOk = false;
|
bool parseOk = false;
|
||||||
|
|
||||||
switch (manufacturerData.victronRecordType) {
|
switch (mfgData.victronRecordType) {
|
||||||
case DEVICE_TYPE_SOLAR_CHARGER:
|
case DEVICE_TYPE_SOLAR_CHARGER:
|
||||||
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_SOLAR_CHARGER) {
|
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_SOLAR_CHARGER) {
|
||||||
parseOk = parseSolarCharger(decrypted, sizeof(decrypted),
|
parseOk = parseSolarCharger(decrypted, encryptedLen,
|
||||||
*(SolarChargerData*)deviceInfo->data);
|
*static_cast<SolarChargerData*>(deviceInfo->data));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DEVICE_TYPE_BATTERY_MONITOR:
|
case DEVICE_TYPE_BATTERY_MONITOR:
|
||||||
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) {
|
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) {
|
||||||
parseOk = parseBatteryMonitor(decrypted, sizeof(decrypted),
|
parseOk = parseBatteryMonitor(decrypted, encryptedLen,
|
||||||
*(BatteryMonitorData*)deviceInfo->data);
|
*static_cast<BatteryMonitorData*>(deviceInfo->data));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -287,20 +262,20 @@ bool VictronBLE::parseAdvertisement(const String& macAddress) {
|
|||||||
case DEVICE_TYPE_MULTI_RS:
|
case DEVICE_TYPE_MULTI_RS:
|
||||||
case DEVICE_TYPE_VE_BUS:
|
case DEVICE_TYPE_VE_BUS:
|
||||||
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_INVERTER) {
|
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_INVERTER) {
|
||||||
parseOk = parseInverter(decrypted, sizeof(decrypted),
|
parseOk = parseInverter(decrypted, encryptedLen,
|
||||||
*(InverterData*)deviceInfo->data);
|
*static_cast<InverterData*>(deviceInfo->data));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DEVICE_TYPE_DCDC_CONVERTER:
|
case DEVICE_TYPE_DCDC_CONVERTER:
|
||||||
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) {
|
if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) {
|
||||||
parseOk = parseDCDCConverter(decrypted, sizeof(decrypted),
|
parseOk = parseDCDCConverter(decrypted, encryptedLen,
|
||||||
*(DCDCConverterData*)deviceInfo->data);
|
*static_cast<DCDCConverterData*>(deviceInfo->data));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
debugPrint("Unknown device type: 0x" + String(manufacturerData.victronRecordType, HEX));
|
if (debugEnabled) debugPrint("Unknown device type: 0x" + String(mfgData.victronRecordType, HEX));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,21 +284,21 @@ bool VictronBLE::parseAdvertisement(const String& macAddress) {
|
|||||||
|
|
||||||
// Call appropriate callback
|
// Call appropriate callback
|
||||||
if (callback) {
|
if (callback) {
|
||||||
switch (manufacturerData.victronRecordType) {
|
switch (mfgData.victronRecordType) {
|
||||||
case DEVICE_TYPE_SOLAR_CHARGER:
|
case DEVICE_TYPE_SOLAR_CHARGER:
|
||||||
callback->onSolarChargerData(*(SolarChargerData*)deviceInfo->data);
|
callback->onSolarChargerData(*static_cast<SolarChargerData*>(deviceInfo->data));
|
||||||
break;
|
break;
|
||||||
case DEVICE_TYPE_BATTERY_MONITOR:
|
case DEVICE_TYPE_BATTERY_MONITOR:
|
||||||
callback->onBatteryMonitorData(*(BatteryMonitorData*)deviceInfo->data);
|
callback->onBatteryMonitorData(*static_cast<BatteryMonitorData*>(deviceInfo->data));
|
||||||
break;
|
break;
|
||||||
case DEVICE_TYPE_INVERTER:
|
case DEVICE_TYPE_INVERTER:
|
||||||
case DEVICE_TYPE_INVERTER_RS:
|
case DEVICE_TYPE_INVERTER_RS:
|
||||||
case DEVICE_TYPE_MULTI_RS:
|
case DEVICE_TYPE_MULTI_RS:
|
||||||
case DEVICE_TYPE_VE_BUS:
|
case DEVICE_TYPE_VE_BUS:
|
||||||
callback->onInverterData(*(InverterData*)deviceInfo->data);
|
callback->onInverterData(*static_cast<InverterData*>(deviceInfo->data));
|
||||||
break;
|
break;
|
||||||
case DEVICE_TYPE_DCDC_CONVERTER:
|
case DEVICE_TYPE_DCDC_CONVERTER:
|
||||||
callback->onDCDCConverterData(*(DCDCConverterData*)deviceInfo->data);
|
callback->onDCDCConverterData(*static_cast<DCDCConverterData*>(deviceInfo->data));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,15 +340,14 @@ bool VictronBLE::decryptAdvertisement(const uint8_t* encrypted, size_t encLen,
|
|||||||
// Parse Solar Charger data
|
// Parse Solar Charger data
|
||||||
bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result) {
|
bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result) {
|
||||||
if (len < sizeof(victronSolarChargerPayload)) {
|
if (len < sizeof(victronSolarChargerPayload)) {
|
||||||
debugPrint("Solar charger data too short: " + String(len) + " bytes");
|
if (debugEnabled) debugPrint("Solar charger data too short: " + String(len) + " bytes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast decrypted data to struct for easy access
|
const auto* payload = reinterpret_cast<const victronSolarChargerPayload*>(data);
|
||||||
const victronSolarChargerPayload* payload = (const victronSolarChargerPayload*)data;
|
|
||||||
|
|
||||||
// Parse charge state
|
// Parse charge state
|
||||||
result.chargeState = (SolarChargerState)payload->deviceState;
|
result.chargeState = static_cast<SolarChargerState>(payload->deviceState);
|
||||||
|
|
||||||
// Parse battery voltage (10 mV units -> volts)
|
// Parse battery voltage (10 mV units -> volts)
|
||||||
result.batteryVoltage = payload->batteryVoltage * 0.01f;
|
result.batteryVoltage = payload->batteryVoltage * 0.01f;
|
||||||
@@ -401,9 +375,11 @@ bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarCharger
|
|||||||
result.panelVoltage = 0;
|
result.panelVoltage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint("Solar Charger: " + String(result.batteryVoltage, 2) + "V, " +
|
if (debugEnabled) {
|
||||||
String(result.batteryCurrent, 2) + "A, " +
|
debugPrint("Solar Charger: " + String(result.batteryVoltage, 2) + "V, " +
|
||||||
String(result.panelPower) + "W, State: " + String(result.chargeState));
|
String(result.batteryCurrent, 2) + "A, " +
|
||||||
|
String(result.panelPower) + "W, State: " + String(result.chargeState));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -411,12 +387,11 @@ bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarCharger
|
|||||||
// Parse Battery Monitor data
|
// Parse Battery Monitor data
|
||||||
bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result) {
|
bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result) {
|
||||||
if (len < sizeof(victronBatteryMonitorPayload)) {
|
if (len < sizeof(victronBatteryMonitorPayload)) {
|
||||||
debugPrint("Battery monitor data too short: " + String(len) + " bytes");
|
if (debugEnabled) debugPrint("Battery monitor data too short: " + String(len) + " bytes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast decrypted data to struct for easy access
|
const auto* payload = reinterpret_cast<const victronBatteryMonitorPayload*>(data);
|
||||||
const victronBatteryMonitorPayload* payload = (const victronBatteryMonitorPayload*)data;
|
|
||||||
|
|
||||||
// Parse remaining time (1 minute units)
|
// Parse remaining time (1 minute units)
|
||||||
result.remainingMinutes = payload->remainingMins;
|
result.remainingMinutes = payload->remainingMins;
|
||||||
@@ -441,7 +416,6 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse battery current (22-bit signed, 1 mA units)
|
// Parse battery current (22-bit signed, 1 mA units)
|
||||||
// Bits 0-7: currentLow, Bits 8-15: currentMid, Bits 16-21: low 6 bits of currentHigh_consumedLow
|
|
||||||
int32_t current = payload->currentLow |
|
int32_t current = payload->currentLow |
|
||||||
(payload->currentMid << 8) |
|
(payload->currentMid << 8) |
|
||||||
((payload->currentHigh_consumedLow & 0x3F) << 16);
|
((payload->currentHigh_consumedLow & 0x3F) << 16);
|
||||||
@@ -452,7 +426,6 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon
|
|||||||
result.current = current * 0.001f; // Convert mA to A
|
result.current = current * 0.001f; // Convert mA to A
|
||||||
|
|
||||||
// Parse consumed Ah (18-bit signed, 10 mAh units)
|
// Parse consumed Ah (18-bit signed, 10 mAh units)
|
||||||
// Bits 0-1: high 2 bits of currentHigh_consumedLow, Bits 2-9: consumedMid, Bits 10-17: consumedHigh
|
|
||||||
int32_t consumedAh = ((payload->currentHigh_consumedLow & 0xC0) >> 6) |
|
int32_t consumedAh = ((payload->currentHigh_consumedLow & 0xC0) >> 6) |
|
||||||
(payload->consumedMid << 2) |
|
(payload->consumedMid << 2) |
|
||||||
(payload->consumedHigh << 10);
|
(payload->consumedHigh << 10);
|
||||||
@@ -465,8 +438,10 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon
|
|||||||
// Parse SOC (10-bit value, 10 = 1.0%)
|
// Parse SOC (10-bit value, 10 = 1.0%)
|
||||||
result.soc = (payload->soc & 0x3FF) * 0.1f;
|
result.soc = (payload->soc & 0x3FF) * 0.1f;
|
||||||
|
|
||||||
debugPrint("Battery Monitor: " + String(result.voltage, 2) + "V, " +
|
if (debugEnabled) {
|
||||||
String(result.current, 2) + "A, SOC: " + String(result.soc, 1) + "%");
|
debugPrint("Battery Monitor: " + String(result.voltage, 2) + "V, " +
|
||||||
|
String(result.current, 2) + "A, SOC: " + String(result.soc, 1) + "%");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -474,12 +449,11 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon
|
|||||||
// Parse Inverter data
|
// Parse Inverter data
|
||||||
bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& result) {
|
bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& result) {
|
||||||
if (len < sizeof(victronInverterPayload)) {
|
if (len < sizeof(victronInverterPayload)) {
|
||||||
debugPrint("Inverter data too short: " + String(len) + " bytes");
|
if (debugEnabled) debugPrint("Inverter data too short: " + String(len) + " bytes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast decrypted data to struct for easy access
|
const auto* payload = reinterpret_cast<const victronInverterPayload*>(data);
|
||||||
const victronInverterPayload* payload = (const victronInverterPayload*)data;
|
|
||||||
|
|
||||||
// Parse device state
|
// Parse device state
|
||||||
result.state = payload->deviceState;
|
result.state = payload->deviceState;
|
||||||
@@ -506,8 +480,10 @@ bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& re
|
|||||||
result.alarmHighTemperature = (payload->alarms & 0x04) != 0;
|
result.alarmHighTemperature = (payload->alarms & 0x04) != 0;
|
||||||
result.alarmOverload = (payload->alarms & 0x08) != 0;
|
result.alarmOverload = (payload->alarms & 0x08) != 0;
|
||||||
|
|
||||||
debugPrint("Inverter: " + String(result.batteryVoltage, 2) + "V, " +
|
if (debugEnabled) {
|
||||||
String(result.acPower) + "W, State: " + String(result.state));
|
debugPrint("Inverter: " + String(result.batteryVoltage, 2) + "V, " +
|
||||||
|
String(result.acPower) + "W, State: " + String(result.state));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -515,12 +491,11 @@ bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& re
|
|||||||
// Parse DC-DC Converter data
|
// Parse DC-DC Converter data
|
||||||
bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result) {
|
bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result) {
|
||||||
if (len < sizeof(victronDCDCConverterPayload)) {
|
if (len < sizeof(victronDCDCConverterPayload)) {
|
||||||
debugPrint("DC-DC converter data too short: " + String(len) + " bytes");
|
if (debugEnabled) debugPrint("DC-DC converter data too short: " + String(len) + " bytes");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast decrypted data to struct for easy access
|
const auto* payload = reinterpret_cast<const victronDCDCConverterPayload*>(data);
|
||||||
const victronDCDCConverterPayload* payload = (const victronDCDCConverterPayload*)data;
|
|
||||||
|
|
||||||
// Parse charge state
|
// Parse charge state
|
||||||
result.chargeState = payload->chargeState;
|
result.chargeState = payload->chargeState;
|
||||||
@@ -537,56 +512,58 @@ bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConvert
|
|||||||
// Parse output current (10 mA units -> amps)
|
// Parse output current (10 mA units -> amps)
|
||||||
result.outputCurrent = payload->outputCurrent * 0.01f;
|
result.outputCurrent = payload->outputCurrent * 0.01f;
|
||||||
|
|
||||||
debugPrint("DC-DC Converter: In=" + String(result.inputVoltage, 2) + "V, Out=" +
|
if (debugEnabled) {
|
||||||
String(result.outputVoltage, 2) + "V, " + String(result.outputCurrent, 2) + "A");
|
debugPrint("DC-DC Converter: In=" + String(result.inputVoltage, 2) + "V, Out=" +
|
||||||
|
String(result.outputVoltage, 2) + "V, " + String(result.outputCurrent, 2) + "A");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get data methods
|
// Get data methods
|
||||||
bool VictronBLE::getSolarChargerData(String macAddress, SolarChargerData& data) {
|
bool VictronBLE::getSolarChargerData(const String& macAddress, SolarChargerData& data) {
|
||||||
String normalizedMAC = normalizeMAC(macAddress);
|
String normalizedMAC = normalizeMAC(macAddress);
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
|
|
||||||
if (it != devices.end() && it->second->data &&
|
if (it != devices.end() && it->second->data &&
|
||||||
it->second->data->deviceType == DEVICE_TYPE_SOLAR_CHARGER) {
|
it->second->data->deviceType == DEVICE_TYPE_SOLAR_CHARGER) {
|
||||||
data = *(SolarChargerData*)it->second->data;
|
data = *static_cast<SolarChargerData*>(it->second->data);
|
||||||
return data.dataValid;
|
return data.dataValid;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VictronBLE::getBatteryMonitorData(String macAddress, BatteryMonitorData& data) {
|
bool VictronBLE::getBatteryMonitorData(const String& macAddress, BatteryMonitorData& data) {
|
||||||
String normalizedMAC = normalizeMAC(macAddress);
|
String normalizedMAC = normalizeMAC(macAddress);
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
|
|
||||||
if (it != devices.end() && it->second->data &&
|
if (it != devices.end() && it->second->data &&
|
||||||
it->second->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) {
|
it->second->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) {
|
||||||
data = *(BatteryMonitorData*)it->second->data;
|
data = *static_cast<BatteryMonitorData*>(it->second->data);
|
||||||
return data.dataValid;
|
return data.dataValid;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VictronBLE::getInverterData(String macAddress, InverterData& data) {
|
bool VictronBLE::getInverterData(const String& macAddress, InverterData& data) {
|
||||||
String normalizedMAC = normalizeMAC(macAddress);
|
String normalizedMAC = normalizeMAC(macAddress);
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
|
|
||||||
if (it != devices.end() && it->second->data &&
|
if (it != devices.end() && it->second->data &&
|
||||||
it->second->data->deviceType == DEVICE_TYPE_INVERTER) {
|
it->second->data->deviceType == DEVICE_TYPE_INVERTER) {
|
||||||
data = *(InverterData*)it->second->data;
|
data = *static_cast<InverterData*>(it->second->data);
|
||||||
return data.dataValid;
|
return data.dataValid;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VictronBLE::getDCDCConverterData(String macAddress, DCDCConverterData& data) {
|
bool VictronBLE::getDCDCConverterData(const String& macAddress, DCDCConverterData& data) {
|
||||||
String normalizedMAC = normalizeMAC(macAddress);
|
String normalizedMAC = normalizeMAC(macAddress);
|
||||||
auto it = devices.find(normalizedMAC);
|
auto it = devices.find(normalizedMAC);
|
||||||
|
|
||||||
if (it != devices.end() && it->second->data &&
|
if (it != devices.end() && it->second->data &&
|
||||||
it->second->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) {
|
it->second->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) {
|
||||||
data = *(DCDCConverterData*)it->second->data;
|
data = *static_cast<DCDCConverterData*>(it->second->data);
|
||||||
return data.dataValid;
|
return data.dataValid;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -644,21 +621,19 @@ bool VictronBLE::hexStringToBytes(const String& hex, uint8_t* bytes, size_t len)
|
|||||||
|
|
||||||
// Helper: MAC address to string
|
// Helper: MAC address to string
|
||||||
String VictronBLE::macAddressToString(BLEAddress address) {
|
String VictronBLE::macAddressToString(BLEAddress address) {
|
||||||
// Use the BLEAddress toString() method which provides consistent formatting
|
|
||||||
return String(address.toString().c_str());
|
return String(address.toString().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Normalize MAC address format
|
// Helper: Normalize MAC address format
|
||||||
String VictronBLE::normalizeMAC(String mac) {
|
String VictronBLE::normalizeMAC(const String& mac) {
|
||||||
String normalized = mac;
|
String normalized = mac;
|
||||||
normalized.toLowerCase();
|
normalized.toLowerCase();
|
||||||
// XXX - is this right, was - to : but not consistent location of pairs or not
|
|
||||||
normalized.replace("-", "");
|
normalized.replace("-", "");
|
||||||
normalized.replace(":", "");
|
normalized.replace(":", "");
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug helpers
|
// Debug helper
|
||||||
void VictronBLE::debugPrint(const String& message) {
|
void VictronBLE::debugPrint(const String& message) {
|
||||||
if (debugEnabled)
|
if (debugEnabled)
|
||||||
Serial.println("[VictronBLE] " + message);
|
Serial.println("[VictronBLE] " + message);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#include "mbedtls/aes.h"
|
#include "mbedtls/aes.h"
|
||||||
|
|
||||||
// Victron manufacturer ID
|
// Victron manufacturer ID
|
||||||
#define VICTRON_MANUFACTURER_ID 0x02E1
|
static constexpr uint16_t VICTRON_MANUFACTURER_ID = 0x02E1;
|
||||||
|
|
||||||
// Device type IDs from Victron protocol
|
// Device type IDs from Victron protocol
|
||||||
enum VictronDeviceType {
|
enum VictronDeviceType {
|
||||||
@@ -57,7 +57,7 @@ enum SolarChargerState {
|
|||||||
// Must use __attribute__((packed)) to prevent compiler padding
|
// Must use __attribute__((packed)) to prevent compiler padding
|
||||||
|
|
||||||
// Manufacturer data structure (outer envelope)
|
// Manufacturer data structure (outer envelope)
|
||||||
typedef struct {
|
struct victronManufacturerData {
|
||||||
uint16_t vendorID; // vendor ID
|
uint16_t vendorID; // vendor ID
|
||||||
uint8_t beaconType; // Should be 0x10 (Product Advertisement) for the packets we want
|
uint8_t beaconType; // Should be 0x10 (Product Advertisement) for the packets we want
|
||||||
uint8_t unknownData1[3]; // Unknown data
|
uint8_t unknownData1[3]; // Unknown data
|
||||||
@@ -66,11 +66,11 @@ typedef struct {
|
|||||||
uint8_t encryptKeyMatch; // Should match pre-shared encryption key byte 0
|
uint8_t encryptKeyMatch; // Should match pre-shared encryption key byte 0
|
||||||
uint8_t victronEncryptedData[21]; // (31 bytes max per BLE spec - size of previous elements)
|
uint8_t victronEncryptedData[21]; // (31 bytes max per BLE spec - size of previous elements)
|
||||||
uint8_t nullPad; // extra byte because toCharArray() adds a \0 byte.
|
uint8_t nullPad; // extra byte because toCharArray() adds a \0 byte.
|
||||||
} __attribute__((packed)) victronManufacturerData;
|
} __attribute__((packed));
|
||||||
// Decrypted payload structures for each device type
|
// Decrypted payload structures for each device type
|
||||||
|
|
||||||
// Solar Charger decrypted payload
|
// Solar Charger decrypted payload
|
||||||
typedef struct {
|
struct victronSolarChargerPayload {
|
||||||
uint8_t deviceState; // Charge state (SolarChargerState enum)
|
uint8_t deviceState; // Charge state (SolarChargerState enum)
|
||||||
uint8_t errorCode; // Error code
|
uint8_t errorCode; // Error code
|
||||||
int16_t batteryVoltage; // Battery voltage in 10mV units
|
int16_t batteryVoltage; // Battery voltage in 10mV units
|
||||||
@@ -79,10 +79,10 @@ typedef struct {
|
|||||||
uint16_t inputPower; // PV power in 1W units
|
uint16_t inputPower; // PV power in 1W units
|
||||||
uint16_t loadCurrent; // Load current in 10mA units (0xFFFF = no load)
|
uint16_t loadCurrent; // Load current in 10mA units (0xFFFF = no load)
|
||||||
uint8_t reserved[2]; // Reserved bytes
|
uint8_t reserved[2]; // Reserved bytes
|
||||||
} __attribute__((packed)) victronSolarChargerPayload;
|
} __attribute__((packed));
|
||||||
|
|
||||||
// Battery Monitor decrypted payload
|
// Battery Monitor decrypted payload
|
||||||
typedef struct {
|
struct victronBatteryMonitorPayload {
|
||||||
uint16_t remainingMins; // Time remaining in minutes
|
uint16_t remainingMins; // Time remaining in minutes
|
||||||
uint16_t batteryVoltage; // Battery voltage in 10mV units
|
uint16_t batteryVoltage; // Battery voltage in 10mV units
|
||||||
uint8_t alarms; // Alarm bits
|
uint8_t alarms; // Alarm bits
|
||||||
@@ -94,10 +94,10 @@ typedef struct {
|
|||||||
uint8_t consumedHigh; // Consumed Ah bits 10-17
|
uint8_t consumedHigh; // Consumed Ah bits 10-17
|
||||||
uint16_t soc; // State of charge in 0.1% units (10-bit value)
|
uint16_t soc; // State of charge in 0.1% units (10-bit value)
|
||||||
uint8_t reserved[2]; // Reserved bytes
|
uint8_t reserved[2]; // Reserved bytes
|
||||||
} __attribute__((packed)) victronBatteryMonitorPayload;
|
} __attribute__((packed));
|
||||||
|
|
||||||
// Inverter decrypted payload
|
// Inverter decrypted payload
|
||||||
typedef struct {
|
struct victronInverterPayload {
|
||||||
uint8_t deviceState; // Device state
|
uint8_t deviceState; // Device state
|
||||||
uint8_t errorCode; // Error code
|
uint8_t errorCode; // Error code
|
||||||
uint16_t batteryVoltage; // Battery voltage in 10mV units
|
uint16_t batteryVoltage; // Battery voltage in 10mV units
|
||||||
@@ -107,17 +107,17 @@ typedef struct {
|
|||||||
uint8_t acPowerHigh; // AC Power bits 16-23 (signed 24-bit)
|
uint8_t acPowerHigh; // AC Power bits 16-23 (signed 24-bit)
|
||||||
uint8_t alarms; // Alarm bits
|
uint8_t alarms; // Alarm bits
|
||||||
uint8_t reserved[4]; // Reserved bytes
|
uint8_t reserved[4]; // Reserved bytes
|
||||||
} __attribute__((packed)) victronInverterPayload;
|
} __attribute__((packed));
|
||||||
|
|
||||||
// DC-DC Converter decrypted payload
|
// DC-DC Converter decrypted payload
|
||||||
typedef struct {
|
struct victronDCDCConverterPayload {
|
||||||
uint8_t chargeState; // Charge state
|
uint8_t chargeState; // Charge state
|
||||||
uint8_t errorCode; // Error code
|
uint8_t errorCode; // Error code
|
||||||
uint16_t inputVoltage; // Input voltage in 10mV units
|
uint16_t inputVoltage; // Input voltage in 10mV units
|
||||||
uint16_t outputVoltage; // Output voltage in 10mV units
|
uint16_t outputVoltage; // Output voltage in 10mV units
|
||||||
uint16_t outputCurrent; // Output current in 10mA units
|
uint16_t outputCurrent; // Output current in 10mA units
|
||||||
uint8_t reserved[6]; // Reserved bytes
|
uint8_t reserved[6]; // Reserved bytes
|
||||||
} __attribute__((packed)) victronDCDCConverterPayload;
|
} __attribute__((packed));
|
||||||
|
|
||||||
// Base structure for all device data
|
// Base structure for all device data
|
||||||
struct VictronDeviceData {
|
struct VictronDeviceData {
|
||||||
@@ -205,8 +205,9 @@ struct DCDCConverterData : public VictronDeviceData {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declarations
|
||||||
class VictronBLE;
|
class VictronBLE;
|
||||||
|
class VictronBLEAdvertisedDeviceCallbacks;
|
||||||
|
|
||||||
// Callback interface for device data updates
|
// Callback interface for device data updates
|
||||||
class VictronDeviceCallback {
|
class VictronDeviceCallback {
|
||||||
@@ -226,7 +227,7 @@ struct VictronDeviceConfig {
|
|||||||
VictronDeviceType expectedType;
|
VictronDeviceType expectedType;
|
||||||
|
|
||||||
VictronDeviceConfig() : expectedType(DEVICE_TYPE_UNKNOWN) {}
|
VictronDeviceConfig() : expectedType(DEVICE_TYPE_UNKNOWN) {}
|
||||||
VictronDeviceConfig(String n, String mac, String key, VictronDeviceType type = DEVICE_TYPE_UNKNOWN)
|
VictronDeviceConfig(const String& n, const String& mac, const String& key, VictronDeviceType type = DEVICE_TYPE_UNKNOWN)
|
||||||
: name(n), macAddress(mac), encryptionKey(key), expectedType(type) {}
|
: name(n), macAddress(mac), encryptionKey(key), expectedType(type) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,11 +242,11 @@ public:
|
|||||||
|
|
||||||
// Add a device to monitor
|
// Add a device to monitor
|
||||||
bool addDevice(const VictronDeviceConfig& config);
|
bool addDevice(const VictronDeviceConfig& config);
|
||||||
bool addDevice(String name, String macAddress, String encryptionKey,
|
bool addDevice(const String& name, const String& macAddress, const String& encryptionKey,
|
||||||
VictronDeviceType expectedType = DEVICE_TYPE_UNKNOWN);
|
VictronDeviceType expectedType = DEVICE_TYPE_UNKNOWN);
|
||||||
|
|
||||||
// Remove a device
|
// Remove a device
|
||||||
void removeDevice(String macAddress);
|
void removeDevice(const String& macAddress);
|
||||||
|
|
||||||
// Get device count
|
// Get device count
|
||||||
size_t getDeviceCount() const { return devices.size(); }
|
size_t getDeviceCount() const { return devices.size(); }
|
||||||
@@ -257,10 +258,10 @@ public:
|
|||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
// Get latest data for a device
|
// Get latest data for a device
|
||||||
bool getSolarChargerData(String macAddress, SolarChargerData& data);
|
bool getSolarChargerData(const String& macAddress, SolarChargerData& data);
|
||||||
bool getBatteryMonitorData(String macAddress, BatteryMonitorData& data);
|
bool getBatteryMonitorData(const String& macAddress, BatteryMonitorData& data);
|
||||||
bool getInverterData(String macAddress, InverterData& data);
|
bool getInverterData(const String& macAddress, InverterData& data);
|
||||||
bool getDCDCConverterData(String macAddress, DCDCConverterData& data);
|
bool getDCDCConverterData(const String& macAddress, DCDCConverterData& data);
|
||||||
|
|
||||||
// Get all devices of a specific type
|
// Get all devices of a specific type
|
||||||
std::vector<String> getDevicesByType(VictronDeviceType type);
|
std::vector<String> getDevicesByType(VictronDeviceType type);
|
||||||
@@ -285,26 +286,26 @@ private:
|
|||||||
~DeviceInfo() {
|
~DeviceInfo() {
|
||||||
if (data) delete data;
|
if (data) delete data;
|
||||||
}
|
}
|
||||||
|
DeviceInfo(const DeviceInfo&) = delete;
|
||||||
|
DeviceInfo& operator=(const DeviceInfo&) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<String, DeviceInfo*> devices;
|
std::map<String, DeviceInfo*> devices;
|
||||||
BLEScan* pBLEScan;
|
BLEScan* pBLEScan;
|
||||||
|
VictronBLEAdvertisedDeviceCallbacks* scanCallback;
|
||||||
VictronDeviceCallback* callback;
|
VictronDeviceCallback* callback;
|
||||||
bool debugEnabled;
|
bool debugEnabled;
|
||||||
String lastError;
|
String lastError;
|
||||||
uint32_t scanDuration;
|
uint32_t scanDuration;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
|
|
||||||
// XXX Experiment with actual victron data
|
|
||||||
victronManufacturerData manufacturerData;
|
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
bool hexStringToBytes(const String& hex, uint8_t* bytes, size_t len);
|
bool hexStringToBytes(const String& hex, uint8_t* bytes, size_t len);
|
||||||
bool decryptAdvertisement(const uint8_t* encrypted, size_t encLen,
|
bool decryptAdvertisement(const uint8_t* encrypted, size_t encLen,
|
||||||
const uint8_t* key, const uint8_t* iv,
|
const uint8_t* key, const uint8_t* iv,
|
||||||
uint8_t* decrypted);
|
uint8_t* decrypted);
|
||||||
bool parseAdvertisement(const String& macAddress);
|
bool parseAdvertisement(DeviceInfo* deviceInfo, const victronManufacturerData& mfgData);
|
||||||
void processDevice(BLEAdvertisedDevice advertisedDevice);
|
void processDevice(BLEAdvertisedDevice& advertisedDevice);
|
||||||
|
|
||||||
VictronDeviceData* createDeviceData(VictronDeviceType type);
|
VictronDeviceData* createDeviceData(VictronDeviceType type);
|
||||||
bool parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result);
|
bool parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result);
|
||||||
@@ -315,7 +316,7 @@ private:
|
|||||||
void debugPrint(const String& message);
|
void debugPrint(const String& message);
|
||||||
|
|
||||||
String macAddressToString(BLEAddress address);
|
String macAddressToString(BLEAddress address);
|
||||||
String normalizeMAC(String mac);
|
String normalizeMAC(const String& mac);
|
||||||
};
|
};
|
||||||
|
|
||||||
// BLE scan callback class
|
// BLE scan callback class
|
||||||
|
|||||||
Reference in New Issue
Block a user