Work on decoding using structs
This commit is contained in:
4
TODO
4
TODO
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user