Cleaning up by using structs and reusing data blocks

This commit is contained in:
2025-12-29 19:12:47 +11:00
parent 6a517246ea
commit 03d8da3b7d
5 changed files with 72 additions and 128 deletions

View File

@@ -119,11 +119,6 @@ build_flags =
lib_deps = lib_deps =
M5StickC M5StickC
elapsedMillis elapsedMillis
TaskScheduler
Button2
ArduinoJson
https://github.com/scottp/PsychicHttp.git
[env:tough] [env:tough]
board = m5stack-core2 board = m5stack-core2
@@ -146,7 +141,3 @@ build_flags =
lib_deps = lib_deps =
M5Unified M5Unified
elapsedMillis elapsedMillis
TaskScheduler
Button2
ArduinoJson
https://github.com/scottp/PsychicHttp.git

View File

@@ -208,24 +208,6 @@ void setup() {
DEVICE_TYPE_SOLAR_CHARGER // Device type DEVICE_TYPE_SOLAR_CHARGER // Device type
); );
/*
*
[VictronBLE] Encrypted data: A0 01 83 2C 0E CF D6 04 89 72 6E 81 56 E4 2D F1 83
[VictronBLE] IV: 02 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[VictronBLE] Decrypted data: E1 1C 99 32 D5 7E 81 A3 EB 8C 25 97 3E 0E DD 2D C4
[VictronBLE] Unknown device type: 0x10
[VictronBLE] BLE Device: 3ffd0148:3ffd014e, RSSI: -27 dBm
[VictronBLE] BLE Device: 3ffd0148:3ffd014e, RSSI: -81 dBm, Mfg ID: 0x2e1 (Victron)
[VictronBLE] Processing data from: Rainbow48Vc
[VictronBLE] Encrypted data: A0 01 83 2C 0E CF D6 04 89 72 6E 81 56 E4 2D F1 83
[VictronBLE] IV: 02 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[VictronBLE] Decrypted data: E1 1C 99 32 D5 7E 81 A3 EB 8C 25 97 3E 0E DD 2D C4
[VictronBLE] Unknown device type: 0x10
[VictronBLE] BLE Device: 3ffd0148:3ffd014e, RSSI: -49 dBm, Mfg ID: 0x75
[VictronBLE] BLE Device: 3ffd0148:3ffd014e, RSSI: -26 dBm
*/
// Example: Solar Charger #1 // Example: Solar Charger #1
/* /*
victron.addDevice( victron.addDevice(

View File

@@ -1,6 +1,6 @@
/* /*
TODO Scott's original test code - this does work for MPPT chargers - use it as a base
*/ */

View File

@@ -132,100 +132,89 @@ void VictronBLE::loop() {
// BLE callback implementation // BLE callback implementation
void VictronBLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice advertisedDevice) { void VictronBLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice advertisedDevice) {
// XXX why victronBLE and not just this? or nothing since inside same object - to do with callback?
if (victronBLE) { if (victronBLE) {
// Debug: Log all discovered BLE devices
if (victronBLE->debugEnabled) {
String mac = victronBLE->macAddressToString(advertisedDevice.getAddress());
String debugMsg = "BLE Device: " + mac;
debugMsg += ", RSSI: " + String(advertisedDevice.getRSSI()) + " dBm";
if (advertisedDevice.haveName()) {
debugMsg += ", Name: " + String(advertisedDevice.getName().c_str());
}
if (advertisedDevice.haveManufacturerData()) {
std::string mfgData = advertisedDevice.getManufacturerData();
if (mfgData.length() >= 2) {
uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0];
debugMsg += ", Mfg ID: 0x" + String(mfgId, HEX);
if (mfgId == VICTRON_MANUFACTURER_ID) {
debugMsg += " (Victron)";
}
}
}
victronBLE->debugPrint(debugMsg);
}
victronBLE->processDevice(advertisedDevice); victronBLE->processDevice(advertisedDevice);
} }
} }
// Process advertised device // Process advertised device
void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) { void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
// XXX Improve mac address handling into somewhere...
String mac = macAddressToString(advertisedDevice.getAddress()); String mac = macAddressToString(advertisedDevice.getAddress());
// XXX normalize mac not working here, but is in experiment
String normalizedMAC = normalizeMAC(mac); String normalizedMAC = normalizeMAC(mac);
// TODO: Consider skipping with no manufacturer data?
memset(&manufacturerData, 0, sizeof(manufacturerData));
if (advertisedDevice.haveManufacturerData()) {
std::string mfgData = advertisedDevice.getManufacturerData();
// XXX Storing it this way is not thread safe - is that issue on this ESP32?
mfgData.copy((char*)&manufacturerData, (mfgData.length() > sizeof(manufacturerData) ? sizeof(manufacturerData) : mfgData.length()));
// XXX Rather than copy, we can use pointers to .data
// Delete string? Or keep, or alternative buuffer handling
}
// Pointer? XXX
// Debug: Log all discovered BLE devices
if (debugEnabled) {
String debugMsg = "";
debugMsg += "BLE Device: " + mac;
debugMsg += ", RSSI: " + String(advertisedDevice.getRSSI()) + " dBm";
if (advertisedDevice.haveName())
debugMsg += ", Name: " + String(advertisedDevice.getName().c_str());
debugMsg += ", Mfg ID: 0x" + String(manufacturerData.vendorID, HEX);
if (manufacturerData.vendorID == VICTRON_MANUFACTURER_ID) {
debugMsg += " (Victron)";
}
debugPrint(debugMsg);
}
// 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 // XXX Check if the device is a Victron device
// This needs lots of improvemet and only do in debug // This needs lots of improvemet and only do in debug
if (advertisedDevice.haveManufacturerData()) { if (manufacturerData.vendorID == VICTRON_MANUFACTURER_ID) {
std::string mfgData = advertisedDevice.getManufacturerData(); debugPrint("Found unmonitored Victron Device: " + normalizeMAC(mac));
if (mfgData.length() >= 2) { // DeviceInfo* deviceInfo = new DeviceInfo(mac, advertisedDevice.getName());
uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0]; // devices.insert({normalizedMAC, deviceInfo});
if (mfgId == VICTRON_MANUFACTURER_ID) { // XXX What type of Victron device is it?
debugPrint("Found unmonitored Victron Device: " + normalizeMAC(mac)); // Check if it's a Victron Energy device
// DeviceInfo* deviceInfo = new DeviceInfo(mac, advertisedDevice.getName()); /*
// devices.insert({normalizedMAC, deviceInfo}); if (advertisedDevice.haveServiceData()) {
// XXX What type of Victron device is it? std::string serviceData = advertisedDevice.getServiceData();
// Check if it's a Victron Energy device if (serviceData.length() >= 2) {
/* uint16_t serviceId = (uint8_t)serviceData[1] << 8 | (uint8_t)serviceData[0];
if (advertisedDevice.haveServiceData()) { if (serviceId == VICTRON_ENERGY_SERVICE_ID) {
std::string serviceData = advertisedDevice.getServiceData(); debugPrint("Found Victron Energy Device: " + mac);
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
} }
DeviceInfo* deviceInfo = it->second; DeviceInfo* deviceInfo = it->second;
// Check if device has manufacturer data
if (!advertisedDevice.haveManufacturerData()) {
return;
}
std::string mfgData = advertisedDevice.getManufacturerData();
if (mfgData.length() < 2) {
return;
}
debugPrint("Manufacturer Length = " + String(mfgData.length()));
// XXX Use struct like code in Sh3dNg // 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]; if (manufacturerData.vendorID != VICTRON_MANUFACTURER_ID) {
if (mfgId != VICTRON_MANUFACTURER_ID) { debugPrint("Skipping non VICTRON");
return; return;
} }
debugPrint("Processing data from: " + deviceInfo->config.name); debugPrint("Processing data from: " + deviceInfo->config.name);
// Parse the advertisement // Parse the advertisement
if (parseAdvertisement((const uint8_t*)mfgData.data(), mfgData.length(), normalizedMAC)) { if (parseAdvertisement(normalizedMAC)) {
// Update RSSI // Update RSSI
if (deviceInfo->data) { if (deviceInfo->data) {
deviceInfo->data->rssi = advertisedDevice.getRSSI(); deviceInfo->data->rssi = advertisedDevice.getRSSI();
@@ -235,8 +224,8 @@ void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) {
} }
// Parse advertisement data // Parse advertisement data
bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, bool VictronBLE::parseAdvertisement(const String& macAddress) {
const String& macAddress) { // XXX We already searched above - try not to again?
auto it = devices.find(macAddress); auto it = devices.find(macAddress);
if (it == devices.end()) { if (it == devices.end()) {
debugPrint("parseAdvertisement: Device not found"); debugPrint("parseAdvertisement: Device not found");
@@ -245,69 +234,48 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len,
DeviceInfo* deviceInfo = it->second; DeviceInfo* deviceInfo = it->second;
// Verify minimum size for victronManufacturerData struct
if (len < sizeof(victronManufacturerData)) {
debugPrint("Manufacturer data too short: " + String(len) + " bytes, expected: " + String(sizeof(victronManufacturerData)));
return false;
}
// Cast manufacturer data to struct for easy access
const victronManufacturerData* vicData = (const victronManufacturerData*)manufacturerData;
if (debugEnabled) { if (debugEnabled) {
debugPrint("Vendor ID: 0x" + String(vicData->vendorID, HEX)); debugPrint("Vendor ID: 0x" + String(manufacturerData.vendorID, HEX));
debugPrint("Beacon Type: 0x" + String(vicData->beaconType, HEX)); debugPrint("Beacon Type: 0x" + String(manufacturerData.beaconType, HEX));
debugPrint("Model ID: 0x" + String(vicData->modelID, HEX)); debugPrint("Model ID: 0x" + String(manufacturerData.modelID, HEX));
debugPrint("Readout Type: 0x" + String(vicData->readoutType, HEX)); debugPrint("Readout Type: 0x" + String(manufacturerData.readoutType, HEX));
debugPrint("Record Type: 0x" + String(vicData->victronRecordType, HEX)); debugPrint("Record Type: 0x" + String(manufacturerData.victronRecordType, HEX));
debugPrint("Nonce: 0x" + String(vicData->nonceDataCounter, HEX)); debugPrint("Nonce: 0x" + String(manufacturerData.nonceDataCounter, HEX));
} }
// Get device type from record type field // Get device type from record type field
uint8_t deviceType = vicData->victronRecordType; uint8_t deviceType = manufacturerData.victronRecordType;
// 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] = vicData->nonceDataCounter & 0xFF; // Low byte iv[0] = manufacturerData.nonceDataCounter & 0xFF; // Low byte
iv[1] = (vicData->nonceDataCounter >> 8) & 0xFF; // High byte iv[1] = (manufacturerData.nonceDataCounter >> 8) & 0xFF; // High byte
// Remaining bytes stay zero // Remaining bytes stay zero
// Get pointer to encrypted data
const uint8_t* encryptedData = vicData->victronEncryptedData;
size_t encryptedLen = sizeof(vicData->victronEncryptedData);
if (debugEnabled) {
debugPrintHex("IV", iv, 16);
debugPrintHex("Encrypted data", encryptedData, encryptedLen);
}
// Decrypt the data // Decrypt the data
uint8_t decrypted[32]; // Max expected size uint8_t decrypted[32]; // Max expected size
if (!decryptAdvertisement(encryptedData, encryptedLen, if (!decryptAdvertisement(manufacturerData.victronEncryptedData,
sizeof(manufacturerData.victronEncryptedData),
deviceInfo->encryptionKeyBytes, iv, decrypted)) { deviceInfo->encryptionKeyBytes, iv, decrypted)) {
lastError = "Decryption failed"; lastError = "Decryption failed";
return false; return false;
} }
if (debugEnabled) {
debugPrintHex("Decrypted data", decrypted, encryptedLen);
}
// Parse based on device type // Parse based on device type
bool parseOk = false; bool parseOk = false;
switch (deviceType) { switch (deviceType) {
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, encryptedLen, parseOk = parseSolarCharger(decrypted, sizeof(decrypted),
*(SolarChargerData*)deviceInfo->data); *(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, encryptedLen, parseOk = parseBatteryMonitor(decrypted, sizeof(decrypted),
*(BatteryMonitorData*)deviceInfo->data); *(BatteryMonitorData*)deviceInfo->data);
} }
break; break;
@@ -317,14 +285,14 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len,
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, encryptedLen, parseOk = parseInverter(decrypted, sizeof(decrypted),
*(InverterData*)deviceInfo->data); *(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, encryptedLen, parseOk = parseDCDCConverter(decrypted, sizeof(decrypted),
*(DCDCConverterData*)deviceInfo->data); *(DCDCConverterData*)deviceInfo->data);
} }
break; break;
@@ -363,6 +331,7 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len,
} }
// Decrypt advertisement using AES-128-CTR // Decrypt advertisement using AES-128-CTR
// XX Compare mbedlts_aes vs esp_aes
bool VictronBLE::decryptAdvertisement(const uint8_t* encrypted, size_t encLen, bool VictronBLE::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) {

View File

@@ -296,13 +296,15 @@ private:
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 uint8_t* manufacturerData, size_t len, bool parseAdvertisement(const String& macAddress);
const String& macAddress);
void processDevice(BLEAdvertisedDevice advertisedDevice); void processDevice(BLEAdvertisedDevice advertisedDevice);
VictronDeviceData* createDeviceData(VictronDeviceType type); VictronDeviceData* createDeviceData(VictronDeviceType type);