From 139c6f961db5dea90ef93a2189cebe62ac1c984b Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Thu, 18 Dec 2025 22:27:15 +1100 Subject: [PATCH] Work on decoding using structs --- TODO | 4 ++ src/VictronBLE.cpp | 21 ++++++++-- src/VictronBLE.h | 97 +++++++++++++++++++++++++++++++--------------- 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/TODO b/TODO index c7e7123..f8bf8a0 100644 --- a/TODO +++ b/TODO @@ -2,3 +2,7 @@ * 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 +* 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 diff --git a/src/VictronBLE.cpp b/src/VictronBLE.cpp index 376b719..f5a08fb 100644 --- a/src/VictronBLE.cpp +++ b/src/VictronBLE.cpp @@ -213,6 +213,8 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) { return; } + // XXX Use struct like code in Sh3dNg + // Check if it's Victron (manufacturer ID 0x02E1) uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0]; if (mfgId != VICTRON_MANUFACTURER_ID) { @@ -236,9 +238,11 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, const String& macAddress) { auto it = devices.find(macAddress); if (it == devices.end()) { + debugPrint("parseAdvertisement: Device not found"); return false; } + // XXX Work out second? DeviceInfo* deviceInfo = it->second; if (len < 6) { @@ -246,18 +250,29 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, 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)] - 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 + // XXX These look wrong uint8_t iv[16] = {0}; iv[0] = manufacturerData[3]; iv[1] = manufacturerData[4]; // Rest of IV is zero-padded // Encrypted data starts at byte 5 - const uint8_t* encryptedData = manufacturerData + 5; - size_t encryptedLen = len - 5; + // const uint8_t* encryptedData = manufacturerData + 5; + // size_t encryptedLen = len - 5; + + // XXX Experiment + const uint8_t* encryptedData = vicData->victronEncryptedData; + size_t encryptedLen = sizeof(vicData->victronEncryptedData); if (debugEnabled) { debugPrintHex("Encrypted data", encryptedData, encryptedLen); diff --git a/src/VictronBLE.h b/src/VictronBLE.h index 7ec24ea..7ce2055 100644 --- a/src/VictronBLE.h +++ b/src/VictronBLE.h @@ -1,9 +1,9 @@ /** * VictronBLE - ESP32 library for Victron Energy BLE devices - * + * * Based on Victron's official BLE Advertising protocol documentation * Inspired by hoberman's examples and keshavdv's Python library - * + * * Copyright (c) 2025 Scott Penrose * License: MIT */ @@ -53,6 +53,39 @@ enum SolarChargerState { 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 struct VictronDeviceData { String deviceName; @@ -61,8 +94,8 @@ struct VictronDeviceData { int8_t rssi; uint32_t lastUpdate; bool dataValid; - - VictronDeviceData() : deviceType(DEVICE_TYPE_UNKNOWN), rssi(-100), + + VictronDeviceData() : deviceType(DEVICE_TYPE_UNKNOWN), rssi(-100), lastUpdate(0), dataValid(false) {} }; @@ -75,8 +108,8 @@ struct SolarChargerData : public VictronDeviceData { float panelPower; // W uint16_t yieldToday; // Wh float loadCurrent; // A - - SolarChargerData() : chargeState(CHARGER_OFF), batteryVoltage(0), + + SolarChargerData() : chargeState(CHARGER_OFF), batteryVoltage(0), batteryCurrent(0), panelVoltage(0), panelPower(0), yieldToday(0), loadCurrent(0) { deviceType = DEVICE_TYPE_SOLAR_CHARGER; @@ -97,8 +130,8 @@ struct BatteryMonitorData : public VictronDeviceData { bool alarmLowSOC; bool alarmLowTemperature; bool alarmHighTemperature; - - BatteryMonitorData() : voltage(0), current(0), temperature(0), + + BatteryMonitorData() : voltage(0), current(0), temperature(0), auxVoltage(0), remainingMinutes(0), consumedAh(0), soc(0), alarmLowVoltage(false), alarmHighVoltage(false), alarmLowSOC(false), alarmLowTemperature(false), @@ -117,7 +150,7 @@ struct InverterData : public VictronDeviceData { bool alarmLowVoltage; bool alarmHighTemperature; bool alarmOverload; - + InverterData() : batteryVoltage(0), batteryCurrent(0), acPower(0), state(0), alarmHighVoltage(false), alarmLowVoltage(false), alarmHighTemperature(false), alarmOverload(false) { @@ -132,7 +165,7 @@ struct DCDCConverterData : public VictronDeviceData { float outputCurrent; // A uint8_t chargeState; uint8_t errorCode; - + DCDCConverterData() : inputVoltage(0), outputVoltage(0), outputCurrent(0), chargeState(0), errorCode(0) { deviceType = DEVICE_TYPE_DCDC_CONVERTER; @@ -158,7 +191,7 @@ struct VictronDeviceConfig { String macAddress; String encryptionKey; // 32 character hex string VictronDeviceType expectedType; - + VictronDeviceConfig() : expectedType(DEVICE_TYPE_UNKNOWN) {} VictronDeviceConfig(String n, String mac, String key, VictronDeviceType type = DEVICE_TYPE_UNKNOWN) : name(n), macAddress(mac), encryptionKey(key), expectedType(type) {} @@ -169,50 +202,50 @@ class VictronBLE { public: VictronBLE(); ~VictronBLE(); - + // Initialize BLE and start scanning bool begin(uint32_t scanDuration = 5); - + // Add a device to monitor 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); - + // Remove a device void removeDevice(String macAddress); - + // Get device count size_t getDeviceCount() const { return devices.size(); } - + // Set callback for data updates void setCallback(VictronDeviceCallback* cb) { callback = cb; } - + // Process scanning (call in loop()) void loop(); - + // Get latest data for a device bool getSolarChargerData(String macAddress, SolarChargerData& data); bool getBatteryMonitorData(String macAddress, BatteryMonitorData& data); bool getInverterData(String macAddress, InverterData& data); bool getDCDCConverterData(String macAddress, DCDCConverterData& data); - + // Get all devices of a specific type std::vector getDevicesByType(VictronDeviceType type); - + // Enable/disable debug output void setDebug(bool enable) { debugEnabled = enable; } - + // Get last error message String getLastError() const { return lastError; } - + private: friend class VictronBLEAdvertisedDeviceCallbacks; - + struct DeviceInfo { VictronDeviceConfig config; VictronDeviceData* data; uint8_t encryptionKeyBytes[16]; - + DeviceInfo() : data(nullptr) { memset(encryptionKeyBytes, 0, 16); } @@ -220,7 +253,7 @@ private: if (data) delete data; } }; - + std::map devices; BLEScan* pBLEScan; VictronDeviceCallback* callback; @@ -228,25 +261,25 @@ private: String lastError; uint32_t scanDuration; bool initialized; - + // Internal methods 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, uint8_t* decrypted); bool parseAdvertisement(const uint8_t* manufacturerData, size_t len, const String& macAddress); void processDevice(BLEAdvertisedDevice advertisedDevice); - + VictronDeviceData* createDeviceData(VictronDeviceType type); bool parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result); bool parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result); bool parseInverter(const uint8_t* data, size_t len, InverterData& result); bool parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result); - + void debugPrint(const String& message); void debugPrintHex(const char* label, const uint8_t* data, size_t len); - + String macAddressToString(BLEAddress address); String normalizeMAC(String mac); }; @@ -256,7 +289,7 @@ class VictronBLEAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { public: VictronBLEAdvertisedDeviceCallbacks(VictronBLE* parent) : victronBLE(parent) {} void onResult(BLEAdvertisedDevice advertisedDevice) override; - + private: VictronBLE* victronBLE; };