Work on decoding using structs

This commit is contained in:
2025-12-18 22:27:15 +11:00
parent 2ccac7b0c8
commit 139c6f961d
3 changed files with 87 additions and 35 deletions

4
TODO
View File

@@ -2,3 +2,7 @@
* Consider support for upper/lower case MAC address and optionaly ":" * Consider support for upper/lower case MAC address and optionaly ":"
* Scanning - list devices publishing, should be able to get list even without knowing MAC / Encryption key * Scanning - list devices publishing, should be able to get list even without knowing MAC / Encryption key
* Struct vs Manual
* Sh3dNg version and examples uses structs to get data - seems to work
* Example generated uses manually managing a string
* Reconsider what is best and use

View File

@@ -213,6 +213,8 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
return; return;
} }
// XXX Use struct like code in Sh3dNg
// Check if it's Victron (manufacturer ID 0x02E1) // Check if it's Victron (manufacturer ID 0x02E1)
uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0]; uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0];
if (mfgId != VICTRON_MANUFACTURER_ID) { if (mfgId != VICTRON_MANUFACTURER_ID) {
@@ -236,9 +238,11 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len,
const String& macAddress) { const String& macAddress) {
auto it = devices.find(macAddress); auto it = devices.find(macAddress);
if (it == devices.end()) { if (it == devices.end()) {
debugPrint("parseAdvertisement: Device not found");
return false; return false;
} }
// XXX Work out second?
DeviceInfo* deviceInfo = it->second; DeviceInfo* deviceInfo = it->second;
if (len < 6) { if (len < 6) {
@@ -246,18 +250,29 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len,
return false; return false;
} }
// XXX map to struct - NOTE: Check size first (exact? or bigger?)
victronManufacturerData * vicData=(victronManufacturerData *)manufacturerData;
debugPrint("VendorID" + String(vicData->vendorID));
debugPrint("Record Type" + String(vicData->victronRecordType));
// Structure: [MfgID(2)] [DeviceType(1)] [IV(2)] [EncryptedData(n)] // Structure: [MfgID(2)] [DeviceType(1)] [IV(2)] [EncryptedData(n)]
uint8_t deviceType = manufacturerData[2]; // XXX This is actually 4 - Struct would help - it was 2
uint8_t deviceType = manufacturerData[4];
// Extract IV (initialization vector) - bytes 3-4, little-endian // Extract IV (initialization vector) - bytes 3-4, little-endian
// XXX These look wrong
uint8_t iv[16] = {0}; uint8_t iv[16] = {0};
iv[0] = manufacturerData[3]; iv[0] = manufacturerData[3];
iv[1] = manufacturerData[4]; iv[1] = manufacturerData[4];
// Rest of IV is zero-padded // Rest of IV is zero-padded
// Encrypted data starts at byte 5 // Encrypted data starts at byte 5
const uint8_t* encryptedData = manufacturerData + 5; // const uint8_t* encryptedData = manufacturerData + 5;
size_t encryptedLen = len - 5; // size_t encryptedLen = len - 5;
// XXX Experiment
const uint8_t* encryptedData = vicData->victronEncryptedData;
size_t encryptedLen = sizeof(vicData->victronEncryptedData);
if (debugEnabled) { if (debugEnabled) {
debugPrintHex("Encrypted data", encryptedData, encryptedLen); debugPrintHex("Encrypted data", encryptedData, encryptedLen);

View File

@@ -1,9 +1,9 @@
/** /**
* VictronBLE - ESP32 library for Victron Energy BLE devices * VictronBLE - ESP32 library for Victron Energy BLE devices
* *
* Based on Victron's official BLE Advertising protocol documentation * Based on Victron's official BLE Advertising protocol documentation
* Inspired by hoberman's examples and keshavdv's Python library * Inspired by hoberman's examples and keshavdv's Python library
* *
* Copyright (c) 2025 Scott Penrose * Copyright (c) 2025 Scott Penrose
* License: MIT * License: MIT
*/ */
@@ -53,6 +53,39 @@ enum SolarChargerState {
CHARGER_EXTERNAL_CONTROL = 252 CHARGER_EXTERNAL_CONTROL = 252
}; };
// XXX HARD Core structs
// Used for decoding
// But then data is put into specific device structs
// Which means a lot of overlap - reconsider...
// NOTE: c struct vs classes
// Must use the "packed" attribute to make sure the compiler doesn't add any padding to deal with
// word alignment.
typedef struct {
uint8_t deviceState;
uint8_t errorCode;
int16_t batteryVoltage;
int16_t batteryCurrent;
uint16_t todayYield;
uint16_t inputPower;
uint8_t outputCurrentLo; // Low 8 bits of output current (in 0.1 Amp increments)
uint8_t outputCurrentHi; // High 1 bit of ourput current (must mask off unused bits)
uint8_t unused[4];
} __attribute__((packed)) victronPanelData; // XXX Specific type -
typedef struct {
uint16_t vendorID; // vendor ID
uint8_t beaconType; // Should be 0x10 (Product Advertisement) for the packets we want
uint8_t unknownData1[3]; // Unknown data
uint8_t victronRecordType; // Should be 0x01 (Solar Charger) for the packets we want
uint16_t nonceDataCounter; // Nonce
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 nullPad; // extra byte because toCharArray() adds a \0 byte.
} __attribute__((packed)) victronManufacturerData;
// XXX End of new bit above
// Base structure for all device data // Base structure for all device data
struct VictronDeviceData { struct VictronDeviceData {
String deviceName; String deviceName;
@@ -61,8 +94,8 @@ struct VictronDeviceData {
int8_t rssi; int8_t rssi;
uint32_t lastUpdate; uint32_t lastUpdate;
bool dataValid; bool dataValid;
VictronDeviceData() : deviceType(DEVICE_TYPE_UNKNOWN), rssi(-100), VictronDeviceData() : deviceType(DEVICE_TYPE_UNKNOWN), rssi(-100),
lastUpdate(0), dataValid(false) {} lastUpdate(0), dataValid(false) {}
}; };
@@ -75,8 +108,8 @@ struct SolarChargerData : public VictronDeviceData {
float panelPower; // W float panelPower; // W
uint16_t yieldToday; // Wh uint16_t yieldToday; // Wh
float loadCurrent; // A float loadCurrent; // A
SolarChargerData() : chargeState(CHARGER_OFF), batteryVoltage(0), SolarChargerData() : chargeState(CHARGER_OFF), batteryVoltage(0),
batteryCurrent(0), panelVoltage(0), panelPower(0), batteryCurrent(0), panelVoltage(0), panelPower(0),
yieldToday(0), loadCurrent(0) { yieldToday(0), loadCurrent(0) {
deviceType = DEVICE_TYPE_SOLAR_CHARGER; deviceType = DEVICE_TYPE_SOLAR_CHARGER;
@@ -97,8 +130,8 @@ struct BatteryMonitorData : public VictronDeviceData {
bool alarmLowSOC; bool alarmLowSOC;
bool alarmLowTemperature; bool alarmLowTemperature;
bool alarmHighTemperature; bool alarmHighTemperature;
BatteryMonitorData() : voltage(0), current(0), temperature(0), BatteryMonitorData() : voltage(0), current(0), temperature(0),
auxVoltage(0), remainingMinutes(0), consumedAh(0), auxVoltage(0), remainingMinutes(0), consumedAh(0),
soc(0), alarmLowVoltage(false), alarmHighVoltage(false), soc(0), alarmLowVoltage(false), alarmHighVoltage(false),
alarmLowSOC(false), alarmLowTemperature(false), alarmLowSOC(false), alarmLowTemperature(false),
@@ -117,7 +150,7 @@ struct InverterData : public VictronDeviceData {
bool alarmLowVoltage; bool alarmLowVoltage;
bool alarmHighTemperature; bool alarmHighTemperature;
bool alarmOverload; bool alarmOverload;
InverterData() : batteryVoltage(0), batteryCurrent(0), acPower(0), InverterData() : batteryVoltage(0), batteryCurrent(0), acPower(0),
state(0), alarmHighVoltage(false), alarmLowVoltage(false), state(0), alarmHighVoltage(false), alarmLowVoltage(false),
alarmHighTemperature(false), alarmOverload(false) { alarmHighTemperature(false), alarmOverload(false) {
@@ -132,7 +165,7 @@ struct DCDCConverterData : public VictronDeviceData {
float outputCurrent; // A float outputCurrent; // A
uint8_t chargeState; uint8_t chargeState;
uint8_t errorCode; uint8_t errorCode;
DCDCConverterData() : inputVoltage(0), outputVoltage(0), outputCurrent(0), DCDCConverterData() : inputVoltage(0), outputVoltage(0), outputCurrent(0),
chargeState(0), errorCode(0) { chargeState(0), errorCode(0) {
deviceType = DEVICE_TYPE_DCDC_CONVERTER; deviceType = DEVICE_TYPE_DCDC_CONVERTER;
@@ -158,7 +191,7 @@ struct VictronDeviceConfig {
String macAddress; String macAddress;
String encryptionKey; // 32 character hex string String encryptionKey; // 32 character hex string
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(String n, String mac, String key, VictronDeviceType type = DEVICE_TYPE_UNKNOWN)
: name(n), macAddress(mac), encryptionKey(key), expectedType(type) {} : name(n), macAddress(mac), encryptionKey(key), expectedType(type) {}
@@ -169,50 +202,50 @@ class VictronBLE {
public: public:
VictronBLE(); VictronBLE();
~VictronBLE(); ~VictronBLE();
// Initialize BLE and start scanning // Initialize BLE and start scanning
bool begin(uint32_t scanDuration = 5); bool begin(uint32_t scanDuration = 5);
// 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(String name, String macAddress, String encryptionKey,
VictronDeviceType expectedType = DEVICE_TYPE_UNKNOWN); VictronDeviceType expectedType = DEVICE_TYPE_UNKNOWN);
// Remove a device // Remove a device
void removeDevice(String macAddress); void removeDevice(String macAddress);
// Get device count // Get device count
size_t getDeviceCount() const { return devices.size(); } size_t getDeviceCount() const { return devices.size(); }
// Set callback for data updates // Set callback for data updates
void setCallback(VictronDeviceCallback* cb) { callback = cb; } void setCallback(VictronDeviceCallback* cb) { callback = cb; }
// Process scanning (call in loop()) // Process scanning (call in loop())
void loop(); void loop();
// Get latest data for a device // Get latest data for a device
bool getSolarChargerData(String macAddress, SolarChargerData& data); bool getSolarChargerData(String macAddress, SolarChargerData& data);
bool getBatteryMonitorData(String macAddress, BatteryMonitorData& data); bool getBatteryMonitorData(String macAddress, BatteryMonitorData& data);
bool getInverterData(String macAddress, InverterData& data); bool getInverterData(String macAddress, InverterData& data);
bool getDCDCConverterData(String macAddress, DCDCConverterData& data); bool getDCDCConverterData(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);
// Enable/disable debug output // Enable/disable debug output
void setDebug(bool enable) { debugEnabled = enable; } void setDebug(bool enable) { debugEnabled = enable; }
// Get last error message // Get last error message
String getLastError() const { return lastError; } String getLastError() const { return lastError; }
private: private:
friend class VictronBLEAdvertisedDeviceCallbacks; friend class VictronBLEAdvertisedDeviceCallbacks;
struct DeviceInfo { struct DeviceInfo {
VictronDeviceConfig config; VictronDeviceConfig config;
VictronDeviceData* data; VictronDeviceData* data;
uint8_t encryptionKeyBytes[16]; uint8_t encryptionKeyBytes[16];
DeviceInfo() : data(nullptr) { DeviceInfo() : data(nullptr) {
memset(encryptionKeyBytes, 0, 16); memset(encryptionKeyBytes, 0, 16);
} }
@@ -220,7 +253,7 @@ private:
if (data) delete data; if (data) delete data;
} }
}; };
std::map<String, DeviceInfo*> devices; std::map<String, DeviceInfo*> devices;
BLEScan* pBLEScan; BLEScan* pBLEScan;
VictronDeviceCallback* callback; VictronDeviceCallback* callback;
@@ -228,25 +261,25 @@ private:
String lastError; String lastError;
uint32_t scanDuration; uint32_t scanDuration;
bool initialized; bool initialized;
// 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 uint8_t* manufacturerData, size_t len, bool parseAdvertisement(const uint8_t* manufacturerData, size_t len,
const String& macAddress); const String& macAddress);
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);
bool parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result); bool parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result);
bool parseInverter(const uint8_t* data, size_t len, InverterData& result); bool parseInverter(const uint8_t* data, size_t len, InverterData& result);
bool parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result); bool parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result);
void debugPrint(const String& message); void debugPrint(const String& message);
void debugPrintHex(const char* label, const uint8_t* data, size_t len); void debugPrintHex(const char* label, const uint8_t* data, size_t len);
String macAddressToString(BLEAddress address); String macAddressToString(BLEAddress address);
String normalizeMAC(String mac); String normalizeMAC(String mac);
}; };
@@ -256,7 +289,7 @@ class VictronBLEAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
public: public:
VictronBLEAdvertisedDeviceCallbacks(VictronBLE* parent) : victronBLE(parent) {} VictronBLEAdvertisedDeviceCallbacks(VictronBLE* parent) : victronBLE(parent) {}
void onResult(BLEAdvertisedDevice advertisedDevice) override; void onResult(BLEAdvertisedDevice advertisedDevice) override;
private: private:
VictronBLE* victronBLE; VictronBLE* victronBLE;
}; };