From 97a71ce34c4ad4d6dd05e8bd4197eb321538e63f Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Thu, 18 Dec 2025 20:43:10 +1100 Subject: [PATCH] TODO and m5stick and debug --- TODO | 4 + examples/MultiDevice/platformio.ini | 21 +++ examples/MultiDevice/src/main.cpp | 64 +++---- src/VictronBLE.cpp | 251 +++++++++++++++++----------- 4 files changed, 214 insertions(+), 126 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..c7e7123 --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +# Misc Stuff + +* 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 diff --git a/examples/MultiDevice/platformio.ini b/examples/MultiDevice/platformio.ini index 1bff469..48a6b43 100644 --- a/examples/MultiDevice/platformio.ini +++ b/examples/MultiDevice/platformio.ini @@ -36,3 +36,24 @@ framework = arduino monitor_speed = 115200 build_flags = -DCORE_DEBUG_LEVEL=3 + +[env:m5stick] +platform = espressif32 +board = m5stick-c +framework = arduino +board_build.mcu = esp32 +board_build.f_cpu = 240000000L +board_build.partitions = no_ota.csv +#upload_protocol = espota +#upload_port = Button.local +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +build_flags = + -Os +lib_deps = + M5StickC + elapsedMillis + TaskScheduler + Button2 + ArduinoJson + https://github.com/scottp/PsychicHttp.git diff --git a/examples/MultiDevice/src/main.cpp b/examples/MultiDevice/src/main.cpp index 4d0d2a2..4bcbb91 100644 --- a/examples/MultiDevice/src/main.cpp +++ b/examples/MultiDevice/src/main.cpp @@ -1,13 +1,13 @@ /** * VictronBLE Example - * + * * This example demonstrates how to use the VictronBLE library to read data * from multiple Victron devices simultaneously. - * + * * Hardware Requirements: * - ESP32 board * - Victron devices with BLE (SmartSolar, SmartShunt, etc.) - * + * * Setup: * 1. Get your device encryption keys from the VictronConnect app: * - Open VictronConnect @@ -16,7 +16,7 @@ * - Enable "Instant readout via Bluetooth" * - Click "Show" next to "Instant readout details" * - Copy the encryption key (32 hex characters) - * + * * 2. Update the device configurations below with your devices' MAC addresses * and encryption keys */ @@ -45,7 +45,7 @@ public: } Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago"); } - + void onBatteryMonitorData(const BatteryMonitorData& data) override { Serial.println("\n=== Battery Monitor: " + data.deviceName + " ==="); Serial.println("MAC: " + data.macAddress); @@ -54,20 +54,20 @@ public: Serial.println("Current: " + String(data.current, 2) + " A"); Serial.println("SOC: " + String(data.soc, 1) + " %"); Serial.println("Consumed: " + String(data.consumedAh, 2) + " Ah"); - + if (data.remainingMinutes < 65535) { int hours = data.remainingMinutes / 60; int mins = data.remainingMinutes % 60; Serial.println("Time Remaining: " + String(hours) + "h " + String(mins) + "m"); } - + if (data.temperature > 0) { Serial.println("Temperature: " + String(data.temperature, 1) + " °C"); } if (data.auxVoltage > 0) { Serial.println("Aux Voltage: " + String(data.auxVoltage, 2) + " V"); } - + // Print alarms if (data.alarmLowVoltage || data.alarmHighVoltage || data.alarmLowSOC || data.alarmLowTemperature || data.alarmHighTemperature) { @@ -79,10 +79,10 @@ public: if (data.alarmHighTemperature) Serial.print("HIGH-TEMP "); Serial.println(); } - + Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago"); } - + void onInverterData(const InverterData& data) override { Serial.println("\n=== Inverter/Charger: " + data.deviceName + " ==="); Serial.println("MAC: " + data.macAddress); @@ -91,9 +91,9 @@ public: Serial.println("Current: " + String(data.batteryCurrent, 2) + " A"); Serial.println("AC Power: " + String(data.acPower) + " W"); Serial.println("State: " + String(data.state)); - + // Print alarms - if (data.alarmLowVoltage || data.alarmHighVoltage || + if (data.alarmLowVoltage || data.alarmHighVoltage || data.alarmHighTemperature || data.alarmOverload) { Serial.print("ALARMS: "); if (data.alarmLowVoltage) Serial.print("LOW-V "); @@ -102,10 +102,10 @@ public: if (data.alarmOverload) Serial.print("OVERLOAD "); Serial.println(); } - + Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago"); } - + void onDCDCConverterData(const DCDCConverterData& data) override { Serial.println("\n=== DC-DC Converter: " + data.deviceName + " ==="); Serial.println("MAC: " + data.macAddress); @@ -119,7 +119,7 @@ public: } Serial.println("Last Update: " + String((millis() - data.lastUpdate) / 1000) + "s ago"); } - + private: String getChargeStateName(SolarChargerState state) { switch (state) { @@ -144,27 +144,35 @@ MyVictronCallback callback; void setup() { Serial.begin(115200); delay(1000); - + Serial.println("\n\n================================="); Serial.println("VictronBLE Multi-Device Example"); Serial.println("=================================\n"); - + // Initialize VictronBLE with 5 second scan duration if (!victron.begin(5)) { Serial.println("ERROR: Failed to initialize VictronBLE!"); Serial.println(victron.getLastError()); while (1) delay(1000); } - + // Enable debug output (optional) victron.setDebug(true); - + // Set callback for data updates victron.setCallback(&callback); - + // Add your devices here // Replace with your actual MAC addresses and encryption keys - + + // Temporary - Scott Example + victron.addDevice( + "Rainbow48V", // Device name + "E4:05:42:34:14:F3", // MAC address + "0ec3adf7433dd61793ff2f3b8ad32ed8", // Encryption key (32 hex chars) + DEVICE_TYPE_SOLAR_CHARGER // Device type + ); + // Example: Solar Charger #1 victron.addDevice( "MPPT 100/30", // Device name @@ -172,7 +180,7 @@ void setup() { "0df4d0395b7d1a876c0c33ecb9e70dcd", // Encryption key (32 hex chars) DEVICE_TYPE_SOLAR_CHARGER // Device type ); - + // Example: Solar Charger #2 victron.addDevice( "MPPT 75/15", @@ -180,7 +188,7 @@ void setup() { "1234567890abcdef1234567890abcdef", DEVICE_TYPE_SOLAR_CHARGER ); - + // Example: Battery Monitor (SmartShunt) victron.addDevice( "SmartShunt", @@ -188,7 +196,7 @@ void setup() { "fedcba0987654321fedcba0987654321", DEVICE_TYPE_BATTERY_MONITOR ); - + // Example: Inverter/Charger victron.addDevice( "MultiPlus", @@ -196,7 +204,7 @@ void setup() { "abcdefabcdefabcdefabcdefabcdefab", DEVICE_TYPE_INVERTER ); - + Serial.println("Configured " + String(victron.getDeviceCount()) + " devices"); Serial.println("\nStarting BLE scan...\n"); } @@ -204,7 +212,7 @@ void setup() { void loop() { // Process BLE scanning and data updates victron.loop(); - + // Optional: You can also manually query device data // This is useful if you're not using callbacks /* @@ -212,13 +220,13 @@ void loop() { if (victron.getSolarChargerData("E7:48:D4:28:B7:9C", solarData)) { // Do something with solarData } - + BatteryMonitorData batteryData; if (victron.getBatteryMonitorData("11:22:33:44:55:66", batteryData)) { // Do something with batteryData } */ - + // Add a small delay to avoid overwhelming the serial output delay(100); } diff --git a/src/VictronBLE.cpp b/src/VictronBLE.cpp index c067f5f..0673dfd 100644 --- a/src/VictronBLE.cpp +++ b/src/VictronBLE.cpp @@ -1,7 +1,7 @@ /** * VictronBLE - ESP32 library for Victron Energy BLE devices * Implementation file - * + * * Copyright (c) 2025 Scott Penrose * License: MIT */ @@ -9,8 +9,8 @@ #include "VictronBLE.h" // Constructor -VictronBLE::VictronBLE() - : pBLEScan(nullptr), callback(nullptr), debugEnabled(false), +VictronBLE::VictronBLE() + : pBLEScan(nullptr), callback(nullptr), debugEnabled(false), scanDuration(5), initialized(false) { } @@ -20,7 +20,7 @@ VictronBLE::~VictronBLE() { delete pair.second; } devices.clear(); - + if (pBLEScan) { pBLEScan->stop(); } @@ -32,27 +32,27 @@ bool VictronBLE::begin(uint32_t scanDuration) { debugPrint("VictronBLE already initialized"); return true; } - + this->scanDuration = scanDuration; - + debugPrint("Initializing VictronBLE..."); - + BLEDevice::init("VictronBLE"); pBLEScan = BLEDevice::getScan(); - + if (!pBLEScan) { lastError = "Failed to create BLE scanner"; return false; } - + pBLEScan->setAdvertisedDeviceCallbacks(new VictronBLEAdvertisedDeviceCallbacks(this), true); pBLEScan->setActiveScan(false); // Passive scan - lower power pBLEScan->setInterval(100); pBLEScan->setWindow(99); - + initialized = true; debugPrint("VictronBLE initialized successfully"); - + return true; } @@ -62,42 +62,42 @@ bool VictronBLE::addDevice(const VictronDeviceConfig& config) { lastError = "MAC address cannot be empty"; return false; } - + if (config.encryptionKey.length() != 32) { lastError = "Encryption key must be 32 hex characters"; return false; } - + String normalizedMAC = normalizeMAC(config.macAddress); - + // Check if device already exists if (devices.find(normalizedMAC) != devices.end()) { debugPrint("Device " + normalizedMAC + " already exists, updating config"); delete devices[normalizedMAC]; } - + DeviceInfo* info = new DeviceInfo(); info->config = config; info->config.macAddress = normalizedMAC; - + // Convert encryption key from hex string to bytes if (!hexStringToBytes(config.encryptionKey, info->encryptionKeyBytes, 16)) { lastError = "Invalid encryption key format"; delete info; return false; } - + // Create appropriate data structure based on device type info->data = createDeviceData(config.expectedType); if (info->data) { info->data->macAddress = normalizedMAC; info->data->deviceName = config.name; } - + devices[normalizedMAC] = info; - + debugPrint("Added device: " + config.name + " (" + normalizedMAC + ")"); - + return true; } @@ -110,7 +110,7 @@ bool VictronBLE::addDevice(String name, String macAddress, String encryptionKey, // Remove a device void VictronBLE::removeDevice(String macAddress) { String normalizedMAC = normalizeMAC(macAddress); - + auto it = devices.find(normalizedMAC); if (it != devices.end()) { delete it->second; @@ -124,7 +124,7 @@ void VictronBLE::loop() { if (!initialized) { return; } - + // Start a scan BLEScanResults scanResults = pBLEScan->start(scanDuration, false); pBLEScan->clearResults(); @@ -133,6 +133,30 @@ void VictronBLE::loop() { // BLE callback implementation void VictronBLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice advertisedDevice) { 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); } } @@ -141,33 +165,62 @@ void VictronBLEAdvertisedDeviceCallbacks::onResult(BLEAdvertisedDevice advertise void VictronBLE::processDevice(BLEAdvertisedDevice advertisedDevice) { String mac = macAddressToString(advertisedDevice.getAddress()); String normalizedMAC = normalizeMAC(mac); - + // Check if this is one of our configured devices auto it = devices.find(normalizedMAC); if (it == devices.end()) { + + // XXX Check if the device is a Victron device + // This needs lots of improvemet and only do in debug + if (advertisedDevice.haveManufacturerData()) { + std::string mfgData = advertisedDevice.getManufacturerData(); + if (mfgData.length() >= 2) { + uint16_t mfgId = (uint8_t)mfgData[1] << 8 | (uint8_t)mfgData[0]; + if (mfgId == VICTRON_MANUFACTURER_ID) { + debugPrint("Found unmonitored Victron Device: " + 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 } - + DeviceInfo* deviceInfo = it->second; - + // Check if device has manufacturer data if (!advertisedDevice.haveManufacturerData()) { return; } - + std::string mfgData = advertisedDevice.getManufacturerData(); if (mfgData.length() < 2) { return; } - + // 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) { return; } - + debugPrint("Processing data from: " + deviceInfo->config.name); - + // Parse the advertisement if (parseAdvertisement((const uint8_t*)mfgData.data(), mfgData.length(), normalizedMAC)) { // Update RSSI @@ -185,62 +238,62 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, if (it == devices.end()) { return false; } - + DeviceInfo* deviceInfo = it->second; - + if (len < 6) { debugPrint("Manufacturer data too short"); return false; } - + // Structure: [MfgID(2)] [DeviceType(1)] [IV(2)] [EncryptedData(n)] uint8_t deviceType = manufacturerData[2]; - + // Extract IV (initialization vector) - bytes 3-4, little-endian 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; - + if (debugEnabled) { debugPrintHex("Encrypted data", encryptedData, encryptedLen); debugPrintHex("IV", iv, 16); } - + // Decrypt the data uint8_t decrypted[32]; // Max expected size - if (!decryptAdvertisement(encryptedData, encryptedLen, + if (!decryptAdvertisement(encryptedData, encryptedLen, deviceInfo->encryptionKeyBytes, iv, decrypted)) { lastError = "Decryption failed"; return false; } - + if (debugEnabled) { debugPrintHex("Decrypted data", decrypted, encryptedLen); } - + // Parse based on device type bool parseOk = false; - + switch (deviceType) { case DEVICE_TYPE_SOLAR_CHARGER: if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_SOLAR_CHARGER) { - parseOk = parseSolarCharger(decrypted, encryptedLen, + parseOk = parseSolarCharger(decrypted, encryptedLen, *(SolarChargerData*)deviceInfo->data); } break; - + case DEVICE_TYPE_BATTERY_MONITOR: if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) { parseOk = parseBatteryMonitor(decrypted, encryptedLen, *(BatteryMonitorData*)deviceInfo->data); } break; - + case DEVICE_TYPE_INVERTER: case DEVICE_TYPE_INVERTER_RS: case DEVICE_TYPE_MULTI_RS: @@ -250,22 +303,22 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, *(InverterData*)deviceInfo->data); } break; - + case DEVICE_TYPE_DCDC_CONVERTER: if (deviceInfo->data && deviceInfo->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) { parseOk = parseDCDCConverter(decrypted, encryptedLen, *(DCDCConverterData*)deviceInfo->data); } break; - + default: debugPrint("Unknown device type: 0x" + String(deviceType, HEX)); return false; } - + if (parseOk && deviceInfo->data) { deviceInfo->data->dataValid = true; - + // Call appropriate callback if (callback) { switch (deviceType) { @@ -287,7 +340,7 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, } } } - + return parseOk; } @@ -297,27 +350,27 @@ bool VictronBLE::decryptAdvertisement(const uint8_t* encrypted, size_t encLen, uint8_t* decrypted) { mbedtls_aes_context aes; mbedtls_aes_init(&aes); - + // Set encryption key int ret = mbedtls_aes_setkey_enc(&aes, key, 128); if (ret != 0) { mbedtls_aes_free(&aes); return false; } - + // AES-CTR decryption size_t nc_off = 0; uint8_t nonce_counter[16]; uint8_t stream_block[16]; - + memcpy(nonce_counter, iv, 16); memset(stream_block, 0, 16); - + ret = mbedtls_aes_crypt_ctr(&aes, encLen, &nc_off, nonce_counter, stream_block, encrypted, decrypted); - + mbedtls_aes_free(&aes); - + return (ret == 0); } @@ -327,59 +380,59 @@ bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarCharger debugPrint("Solar charger data too short"); return false; } - + // Byte 0: Charge state result.chargeState = (SolarChargerState)data[0]; - + // Bytes 1-2: Battery voltage (10 mV units) uint16_t vBat = data[1] | (data[2] << 8); result.batteryVoltage = vBat * 0.01f; - + // Bytes 3-4: Battery current (10 mA units, signed) int16_t iBat = (int16_t)(data[3] | (data[4] << 8)); result.batteryCurrent = iBat * 0.01f; - + // Bytes 5-6: Yield today (10 Wh units) uint16_t yield = data[5] | (data[6] << 8); result.yieldToday = yield * 10; - + // Bytes 7-8: PV power (1 W units) uint16_t pvPower = data[7] | (data[8] << 8); result.panelPower = pvPower; - + // Bytes 9-10: Load current (10 mA units) uint16_t iLoad = data[9] | (data[10] << 8); if (iLoad != 0xFFFF) { // 0xFFFF means no load output result.loadCurrent = iLoad * 0.01f; } - + // Calculate PV voltage from power and current (if current > 0) if (result.batteryCurrent > 0.1f) { result.panelVoltage = result.panelPower / result.batteryCurrent; } - + debugPrint("Solar Charger: " + String(result.batteryVoltage, 2) + "V, " + String(result.batteryCurrent, 2) + "A, " + String(result.panelPower) + "W, State: " + String(result.chargeState)); - + return true; } -// Parse Battery Monitor data +// Parse Battery Monitor data bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMonitorData& result) { if (len < 15) { debugPrint("Battery monitor data too short"); return false; } - + // Bytes 0-1: Remaining time (1 minute units) uint16_t timeRemaining = data[0] | (data[1] << 8); result.remainingMinutes = timeRemaining; - + // Bytes 2-3: Battery voltage (10 mV units) uint16_t vBat = data[2] | (data[3] << 8); result.voltage = vBat * 0.01f; - + // Byte 4: Alarms uint8_t alarms = data[4]; result.alarmLowVoltage = (alarms & 0x01) != 0; @@ -387,7 +440,7 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon result.alarmLowSOC = (alarms & 0x04) != 0; result.alarmLowTemperature = (alarms & 0x10) != 0; result.alarmHighTemperature = (alarms & 0x20) != 0; - + // Bytes 5-6: Aux voltage/temperature (10 mV or 0.01K units) uint16_t aux = data[5] | (data[6] << 8); if (aux < 3000) { // If < 30V, it's voltage @@ -397,28 +450,28 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon result.temperature = (aux * 0.01f) - 273.15f; result.auxVoltage = 0; } - + // Bytes 7-9: Battery current (22-bit signed, 1 mA units) int32_t current = data[7] | (data[8] << 8) | ((data[9] & 0x3F) << 16); if (current & 0x200000) { // Sign extend if negative current |= 0xFFC00000; } result.current = current * 0.001f; - + // Bytes 9-11: Consumed Ah (18-bit signed, 10 mAh units) int32_t consumedAh = ((data[9] & 0xC0) >> 6) | (data[10] << 2) | ((data[11] & 0xFF) << 10); if (consumedAh & 0x20000) { // Sign extend consumedAh |= 0xFFFC0000; } result.consumedAh = consumedAh * 0.01f; - + // Bytes 12-13: SOC (10 = 1.0%) uint16_t soc = data[12] | ((data[13] & 0x03) << 8); result.soc = soc * 0.1f; - + debugPrint("Battery Monitor: " + String(result.voltage, 2) + "V, " + String(result.current, 2) + "A, SOC: " + String(result.soc, 1) + "%"); - + return true; } @@ -428,35 +481,35 @@ bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& re debugPrint("Inverter data too short"); return false; } - + // Byte 0: Device state result.state = data[0]; - + // Bytes 1-2: Battery voltage (10 mV units) uint16_t vBat = data[1] | (data[2] << 8); result.batteryVoltage = vBat * 0.01f; - + // Bytes 3-4: Battery current (10 mA units, signed) int16_t iBat = (int16_t)(data[3] | (data[4] << 8)); result.batteryCurrent = iBat * 0.01f; - + // Bytes 5-7: AC Power (1 W units, signed 24-bit) int32_t acPower = data[5] | (data[6] << 8) | (data[7] << 16); if (acPower & 0x800000) { // Sign extend acPower |= 0xFF000000; } result.acPower = acPower; - + // Byte 8: Alarms uint8_t alarms = data[8]; result.alarmLowVoltage = (alarms & 0x01) != 0; result.alarmHighVoltage = (alarms & 0x02) != 0; result.alarmHighTemperature = (alarms & 0x04) != 0; result.alarmOverload = (alarms & 0x08) != 0; - + debugPrint("Inverter: " + String(result.batteryVoltage, 2) + "V, " + String(result.acPower) + "W, State: " + String(result.state)); - + return true; } @@ -466,28 +519,28 @@ bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConvert debugPrint("DC-DC converter data too short"); return false; } - + // Byte 0: Charge state result.chargeState = data[0]; - + // Bytes 1-2: Input voltage (10 mV units) uint16_t vIn = data[1] | (data[2] << 8); result.inputVoltage = vIn * 0.01f; - + // Bytes 3-4: Output voltage (10 mV units) uint16_t vOut = data[3] | (data[4] << 8); result.outputVoltage = vOut * 0.01f; - + // Bytes 5-6: Output current (10 mA units) uint16_t iOut = data[5] | (data[6] << 8); result.outputCurrent = iOut * 0.01f; - + // Byte 7: Error code result.errorCode = data[7]; - + debugPrint("DC-DC Converter: In=" + String(result.inputVoltage, 2) + "V, Out=" + String(result.outputVoltage, 2) + "V, " + String(result.outputCurrent, 2) + "A"); - + return true; } @@ -495,8 +548,8 @@ bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConvert bool VictronBLE::getSolarChargerData(String macAddress, SolarChargerData& data) { String normalizedMAC = normalizeMAC(macAddress); 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) { data = *(SolarChargerData*)it->second->data; return data.dataValid; @@ -507,7 +560,7 @@ bool VictronBLE::getSolarChargerData(String macAddress, SolarChargerData& data) bool VictronBLE::getBatteryMonitorData(String macAddress, BatteryMonitorData& data) { String normalizedMAC = normalizeMAC(macAddress); auto it = devices.find(normalizedMAC); - + if (it != devices.end() && it->second->data && it->second->data->deviceType == DEVICE_TYPE_BATTERY_MONITOR) { data = *(BatteryMonitorData*)it->second->data; @@ -519,7 +572,7 @@ bool VictronBLE::getBatteryMonitorData(String macAddress, BatteryMonitorData& da bool VictronBLE::getInverterData(String macAddress, InverterData& data) { String normalizedMAC = normalizeMAC(macAddress); auto it = devices.find(normalizedMAC); - + if (it != devices.end() && it->second->data && it->second->data->deviceType == DEVICE_TYPE_INVERTER) { data = *(InverterData*)it->second->data; @@ -531,7 +584,7 @@ bool VictronBLE::getInverterData(String macAddress, InverterData& data) { bool VictronBLE::getDCDCConverterData(String macAddress, DCDCConverterData& data) { String normalizedMAC = normalizeMAC(macAddress); auto it = devices.find(normalizedMAC); - + if (it != devices.end() && it->second->data && it->second->data->deviceType == DEVICE_TYPE_DCDC_CONVERTER) { data = *(DCDCConverterData*)it->second->data; @@ -543,13 +596,13 @@ bool VictronBLE::getDCDCConverterData(String macAddress, DCDCConverterData& data // Get devices by type std::vector VictronBLE::getDevicesByType(VictronDeviceType type) { std::vector result; - + for (const auto& pair : devices) { if (pair.second->data && pair.second->data->deviceType == type) { result.push_back(pair.first); } } - + return result; } @@ -577,7 +630,7 @@ bool VictronBLE::hexStringToBytes(const String& hex, uint8_t* bytes, size_t len) if (hex.length() != len * 2) { return false; } - + for (size_t i = 0; i < len; i++) { String byteStr = hex.substring(i * 2, i * 2 + 2); char* endPtr; @@ -586,7 +639,7 @@ bool VictronBLE::hexStringToBytes(const String& hex, uint8_t* bytes, size_t len) return false; } } - + return true; } @@ -604,7 +657,9 @@ String VictronBLE::macAddressToString(BLEAddress address) { String VictronBLE::normalizeMAC(String mac) { String normalized = mac; normalized.toLowerCase(); - normalized.replace("-", ":"); + // XXX - is this right, was - to : but not consistent location of pairs or not + normalized.replace("-", ""); + normalized.replace(":", ""); return normalized; } @@ -617,7 +672,7 @@ void VictronBLE::debugPrint(const String& message) { void VictronBLE::debugPrintHex(const char* label, const uint8_t* data, size_t len) { if (!debugEnabled) return; - + Serial.print("[VictronBLE] "); Serial.print(label); Serial.print(": ");