From 8e5eba47d79c27fcb1c6b299083cb093f021f79c Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Sun, 28 Dec 2025 23:27:40 +1100 Subject: [PATCH 1/3] Working C3 build --- examples/MultiDevice/platformio.ini | 62 ++++++++++++++++++++++++++++- examples/MultiDevice/src/main.cpp | 7 +++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/examples/MultiDevice/platformio.ini b/examples/MultiDevice/platformio.ini index 01fb603..c349f6a 100644 --- a/examples/MultiDevice/platformio.ini +++ b/examples/MultiDevice/platformio.ini @@ -26,16 +26,74 @@ platform = espressif32 board = esp32-s3-devkitc-1 framework = arduino monitor_speed = 115200 +monitor_filters = esp32_exception_decoder build_flags = - -DCORE_DEBUG_LEVEL=3 +# -DCORE_DEBUG_LEVEL=3 + +[env:esp32-s3-debug] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +#monitor_speed = 115200 +#monitor_filters = esp32_exception_decoder +upload_protocol = esp-builtin + +; Debug configuration for GDB +debug_tool = esp-builtin +debug_init_break = tbreak setup +debug_speed = 5000 +debug_load_mode = always + +; Build flags for debugging +build_flags = + -DCORE_DEBUG_LEVEL=5 ; Maximum ESP32 debug level + -O0 ; Disable optimization for debugging + -g3 ; Maximum debug information +build_type = debug [env:esp32-c3] platform = espressif32 +framework = arduino +board = esp32-c3-devkitm-1 +board_build.mcu = esp32c3 +board_build.f_cpu = 160000000L +board_build.flash_mode = dio +board_build.partitions = default.csv +monitor_speed = 115200 +monitor_filters = time, default, esp32_exception_decoder +upload_speed = 921600 +# NOTE: Need these two ARDUIO_USB modes to work with serial +build_flags = + -Os + -I src + -D ARDUINO_ESP32C3_DEV + -D CONFIG_IDF_TARGET_ESP32C3 + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 +lib_deps = + elapsedMillis + +[env:esp32-c3-debug] +platform = espressif32 board = esp32-c3-devkitc-02 framework = arduino monitor_speed = 115200 + +; Upload configuration +upload_protocol = esp-builtin + +; Debug configuration for GDB +debug_tool = esp-builtin +debug_init_break = tbreak setup +debug_speed = 5000 +debug_load_mode = always + +; Build flags for debugging build_flags = - -DCORE_DEBUG_LEVEL=3 + -DCORE_DEBUG_LEVEL=5 ; Maximum ESP32 debug level + -O0 ; Disable optimization for debugging + -g3 ; Maximum debug information +build_type = debug [env:m5stick] platform = espressif32 diff --git a/examples/MultiDevice/src/main.cpp b/examples/MultiDevice/src/main.cpp index 3e2fd0c..3c33f99 100644 --- a/examples/MultiDevice/src/main.cpp +++ b/examples/MultiDevice/src/main.cpp @@ -143,7 +143,7 @@ MyVictronCallback callback; void setup() { Serial.begin(115200); - delay(1000); + delay(2000); Serial.println("\n\n================================="); Serial.println("VictronBLE Multi-Device Example"); @@ -155,12 +155,14 @@ void setup() { Serial.println(victron.getLastError()); while (1) delay(1000); } + delay(1000); // Enable debug output (optional) victron.setDebug(true); // Set callback for data updates victron.setCallback(&callback); + delay(1000); // Add your devices here // Replace with your actual MAC addresses and encryption keys @@ -187,6 +189,7 @@ void setup() { "0ec3adf7433dd61793ff2f3b8ad32ed8", // Encryption key (32 hex chars) DEVICE_TYPE_SOLAR_CHARGER // Device type ); + delay(1000); /* * @@ -237,8 +240,10 @@ void setup() { DEVICE_TYPE_INVERTER ); + delay(1000); Serial.println("Configured " + String(victron.getDeviceCount()) + " devices"); Serial.println("\nStarting BLE scan...\n"); + delay(1000); } void loop() { From 9f0f2ce8fd8a90788c9d120e3d3605433c1a426f Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Sun, 28 Dec 2025 23:35:27 +1100 Subject: [PATCH 2/3] Improved structs --- src/VictronBLE.cpp | 225 ++++++++++++++++++++++++--------------------- src/VictronBLE.h | 90 ++++++++++++------ 2 files changed, 181 insertions(+), 134 deletions(-) diff --git a/src/VictronBLE.cpp b/src/VictronBLE.cpp index f5a08fb..a4804c7 100644 --- a/src/VictronBLE.cpp +++ b/src/VictronBLE.cpp @@ -242,41 +242,43 @@ bool VictronBLE::parseAdvertisement(const uint8_t* manufacturerData, size_t len, return false; } - // XXX Work out second? DeviceInfo* deviceInfo = it->second; - if (len < 6) { - debugPrint("Manufacturer data too short"); + // Verify minimum size for victronManufacturerData struct + if (len < sizeof(victronManufacturerData)) { + debugPrint("Manufacturer data too short: " + String(len) + " bytes"); 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)); + // Cast manufacturer data to struct for easy access + const victronManufacturerData* vicData = (const victronManufacturerData*)manufacturerData; - // Structure: [MfgID(2)] [DeviceType(1)] [IV(2)] [EncryptedData(n)] - // XXX This is actually 4 - Struct would help - it was 2 - uint8_t deviceType = manufacturerData[4]; + if (debugEnabled) { + debugPrint("Vendor ID: 0x" + String(vicData->vendorID, HEX)); + debugPrint("Beacon Type: 0x" + String(vicData->beaconType, HEX)); + debugPrint("Model ID: 0x" + String(vicData->modelID, HEX)); + debugPrint("Readout Type: 0x" + String(vicData->readoutType, HEX)); + debugPrint("Record Type: 0x" + String(vicData->victronRecordType, HEX)); + debugPrint("Nonce: 0x" + String(vicData->nonceDataCounter, HEX)); + } - // Extract IV (initialization vector) - bytes 3-4, little-endian - // XXX These look wrong + // Get device type from record type field + uint8_t deviceType = vicData->victronRecordType; + + // Build IV (initialization vector) from nonce + // IV is 16 bytes: nonce (2 bytes little-endian) + zeros (14 bytes) uint8_t iv[16] = {0}; - iv[0] = manufacturerData[3]; - iv[1] = manufacturerData[4]; - // Rest of IV is zero-padded + iv[0] = vicData->nonceDataCounter & 0xFF; // Low byte + iv[1] = (vicData->nonceDataCounter >> 8) & 0xFF; // High byte + // Remaining bytes stay zero - // Encrypted data starts at byte 5 - // const uint8_t* encryptedData = manufacturerData + 5; - // size_t encryptedLen = len - 5; - - // XXX Experiment + // Get pointer to encrypted data const uint8_t* encryptedData = vicData->victronEncryptedData; size_t encryptedLen = sizeof(vicData->victronEncryptedData); if (debugEnabled) { - debugPrintHex("Encrypted data", encryptedData, encryptedLen); debugPrintHex("IV", iv, 16); + debugPrintHex("Encrypted data", encryptedData, encryptedLen); } // Decrypt the data @@ -391,39 +393,41 @@ bool VictronBLE::decryptAdvertisement(const uint8_t* encrypted, size_t encLen, // Parse Solar Charger data bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarChargerData& result) { - if (len < 12) { - debugPrint("Solar charger data too short"); + if (len < sizeof(victronSolarChargerPayload)) { + debugPrint("Solar charger data too short: " + String(len) + " bytes"); return false; } - // Byte 0: Charge state - result.chargeState = (SolarChargerState)data[0]; + // Cast decrypted data to struct for easy access + const victronSolarChargerPayload* payload = (const victronSolarChargerPayload*)data; - // Bytes 1-2: Battery voltage (10 mV units) - uint16_t vBat = data[1] | (data[2] << 8); - result.batteryVoltage = vBat * 0.01f; + // Parse charge state + result.chargeState = (SolarChargerState)payload->deviceState; - // Bytes 3-4: Battery current (10 mA units, signed) - int16_t iBat = (int16_t)(data[3] | (data[4] << 8)); - result.batteryCurrent = iBat * 0.01f; + // Parse battery voltage (10 mV units -> volts) + result.batteryVoltage = payload->batteryVoltage * 0.01f; - // Bytes 5-6: Yield today (10 Wh units) - uint16_t yield = data[5] | (data[6] << 8); - result.yieldToday = yield * 10; + // Parse battery current (10 mA units, signed -> amps) + result.batteryCurrent = payload->batteryCurrent * 0.01f; - // Bytes 7-8: PV power (1 W units) - uint16_t pvPower = data[7] | (data[8] << 8); - result.panelPower = pvPower; + // Parse yield today (10 Wh units -> Wh) + result.yieldToday = payload->yieldToday * 10; - // 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; + // Parse PV power (1 W units) + result.panelPower = payload->inputPower; + + // Parse load current (10 mA units -> amps, 0xFFFF = no load) + if (payload->loadCurrent != 0xFFFF) { + result.loadCurrent = payload->loadCurrent * 0.01f; + } else { + result.loadCurrent = 0; } // Calculate PV voltage from power and current (if current > 0) if (result.batteryCurrent > 0.1f) { result.panelVoltage = result.panelPower / result.batteryCurrent; + } else { + result.panelVoltage = 0; } debugPrint("Solar Charger: " + String(result.batteryVoltage, 2) + "V, " + @@ -435,54 +439,60 @@ bool VictronBLE::parseSolarCharger(const uint8_t* data, size_t len, SolarCharger // 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"); + if (len < sizeof(victronBatteryMonitorPayload)) { + debugPrint("Battery monitor data too short: " + String(len) + " bytes"); return false; } - // Bytes 0-1: Remaining time (1 minute units) - uint16_t timeRemaining = data[0] | (data[1] << 8); - result.remainingMinutes = timeRemaining; + // Cast decrypted data to struct for easy access + const victronBatteryMonitorPayload* payload = (const victronBatteryMonitorPayload*)data; - // Bytes 2-3: Battery voltage (10 mV units) - uint16_t vBat = data[2] | (data[3] << 8); - result.voltage = vBat * 0.01f; + // Parse remaining time (1 minute units) + result.remainingMinutes = payload->remainingMins; - // Byte 4: Alarms - uint8_t alarms = data[4]; - result.alarmLowVoltage = (alarms & 0x01) != 0; - result.alarmHighVoltage = (alarms & 0x02) != 0; - result.alarmLowSOC = (alarms & 0x04) != 0; - result.alarmLowTemperature = (alarms & 0x10) != 0; - result.alarmHighTemperature = (alarms & 0x20) != 0; + // Parse battery voltage (10 mV units -> volts) + result.voltage = payload->batteryVoltage * 0.01f; - // 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 - result.auxVoltage = aux * 0.01f; + // Parse alarm bits + result.alarmLowVoltage = (payload->alarms & 0x01) != 0; + result.alarmHighVoltage = (payload->alarms & 0x02) != 0; + result.alarmLowSOC = (payload->alarms & 0x04) != 0; + result.alarmLowTemperature = (payload->alarms & 0x10) != 0; + result.alarmHighTemperature = (payload->alarms & 0x20) != 0; + + // Parse aux data: voltage (10 mV units) or temperature (0.01K units) + if (payload->auxData < 3000) { // If < 30V, it's voltage + result.auxVoltage = payload->auxData * 0.01f; result.temperature = 0; } else { // Otherwise temperature in 0.01 Kelvin - result.temperature = (aux * 0.01f) - 273.15f; + result.temperature = (payload->auxData * 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 + // Parse battery current (22-bit signed, 1 mA units) + // Bits 0-7: currentLow, Bits 8-15: currentMid, Bits 16-21: low 6 bits of currentHigh_consumedLow + int32_t current = payload->currentLow | + (payload->currentMid << 8) | + ((payload->currentHigh_consumedLow & 0x3F) << 16); + // Sign extend from 22 bits to 32 bits + if (current & 0x200000) { current |= 0xFFC00000; } - result.current = current * 0.001f; + result.current = current * 0.001f; // Convert mA to A - // 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 + // Parse consumed Ah (18-bit signed, 10 mAh units) + // Bits 0-1: high 2 bits of currentHigh_consumedLow, Bits 2-9: consumedMid, Bits 10-17: consumedHigh + int32_t consumedAh = ((payload->currentHigh_consumedLow & 0xC0) >> 6) | + (payload->consumedMid << 2) | + (payload->consumedHigh << 10); + // Sign extend from 18 bits to 32 bits + if (consumedAh & 0x20000) { consumedAh |= 0xFFFC0000; } - result.consumedAh = consumedAh * 0.01f; + result.consumedAh = consumedAh * 0.01f; // Convert 10mAh to Ah - // Bytes 12-13: SOC (10 = 1.0%) - uint16_t soc = data[12] | ((data[13] & 0x03) << 8); - result.soc = soc * 0.1f; + // Parse SOC (10-bit value, 10 = 1.0%) + result.soc = (payload->soc & 0x3FF) * 0.1f; debugPrint("Battery Monitor: " + String(result.voltage, 2) + "V, " + String(result.current, 2) + "A, SOC: " + String(result.soc, 1) + "%"); @@ -492,35 +502,38 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon // Parse Inverter data bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& result) { - if (len < 10) { - debugPrint("Inverter data too short"); + if (len < sizeof(victronInverterPayload)) { + debugPrint("Inverter data too short: " + String(len) + " bytes"); return false; } - // Byte 0: Device state - result.state = data[0]; + // Cast decrypted data to struct for easy access + const victronInverterPayload* payload = (const victronInverterPayload*)data; - // Bytes 1-2: Battery voltage (10 mV units) - uint16_t vBat = data[1] | (data[2] << 8); - result.batteryVoltage = vBat * 0.01f; + // Parse device state + result.state = payload->deviceState; - // Bytes 3-4: Battery current (10 mA units, signed) - int16_t iBat = (int16_t)(data[3] | (data[4] << 8)); - result.batteryCurrent = iBat * 0.01f; + // Parse battery voltage (10 mV units -> volts) + result.batteryVoltage = payload->batteryVoltage * 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 + // Parse battery current (10 mA units, signed -> amps) + result.batteryCurrent = payload->batteryCurrent * 0.01f; + + // Parse AC Power (signed 24-bit, 1 W units) + int32_t acPower = payload->acPowerLow | + (payload->acPowerMid << 8) | + (payload->acPowerHigh << 16); + // Sign extend from 24 bits to 32 bits + if (acPower & 0x800000) { 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; + // Parse alarm bits + result.alarmLowVoltage = (payload->alarms & 0x01) != 0; + result.alarmHighVoltage = (payload->alarms & 0x02) != 0; + result.alarmHighTemperature = (payload->alarms & 0x04) != 0; + result.alarmOverload = (payload->alarms & 0x08) != 0; debugPrint("Inverter: " + String(result.batteryVoltage, 2) + "V, " + String(result.acPower) + "W, State: " + String(result.state)); @@ -530,28 +543,28 @@ bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& re // Parse DC-DC Converter data bool VictronBLE::parseDCDCConverter(const uint8_t* data, size_t len, DCDCConverterData& result) { - if (len < 10) { - debugPrint("DC-DC converter data too short"); + if (len < sizeof(victronDCDCConverterPayload)) { + debugPrint("DC-DC converter data too short: " + String(len) + " bytes"); return false; } - // Byte 0: Charge state - result.chargeState = data[0]; + // Cast decrypted data to struct for easy access + const victronDCDCConverterPayload* payload = (const victronDCDCConverterPayload*)data; - // Bytes 1-2: Input voltage (10 mV units) - uint16_t vIn = data[1] | (data[2] << 8); - result.inputVoltage = vIn * 0.01f; + // Parse charge state + result.chargeState = payload->chargeState; - // Bytes 3-4: Output voltage (10 mV units) - uint16_t vOut = data[3] | (data[4] << 8); - result.outputVoltage = vOut * 0.01f; + // Parse error code + result.errorCode = payload->errorCode; - // Bytes 5-6: Output current (10 mA units) - uint16_t iOut = data[5] | (data[6] << 8); - result.outputCurrent = iOut * 0.01f; + // Parse input voltage (10 mV units -> volts) + result.inputVoltage = payload->inputVoltage * 0.01f; - // Byte 7: Error code - result.errorCode = data[7]; + // Parse output voltage (10 mV units -> volts) + result.outputVoltage = payload->outputVoltage * 0.01f; + + // Parse output current (10 mA units -> amps) + result.outputCurrent = payload->outputCurrent * 0.01f; debugPrint("DC-DC Converter: In=" + String(result.inputVoltage, 2) + "V, Out=" + String(result.outputVoltage, 2) + "V, " + String(result.outputCurrent, 2) + "A"); diff --git a/src/VictronBLE.h b/src/VictronBLE.h index 7ce2055..d3d4cef 100644 --- a/src/VictronBLE.h +++ b/src/VictronBLE.h @@ -53,38 +53,72 @@ 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 +// Binary data structures for decoding BLE advertisements +// Must use __attribute__((packed)) to prevent compiler padding -// Must use the "packed" attribute to make sure the compiler doesn't add any padding to deal with -// word alignment. +// Manufacturer data structure (outer envelope) 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. + uint16_t vendorID; // Victron vendor ID (0x02E1) + uint8_t beaconType; // Should be 0x10 (Product Advertisement) + uint8_t modelID; // Model identifier byte + uint8_t readoutType; // Type of data readout + uint8_t victronRecordType; // Record type (device type) + uint16_t nonceDataCounter; // Nonce for encryption (IV bytes 0-1) + uint8_t encryptKeyMatch; // Should match pre-shared encryption key byte 0 + uint8_t victronEncryptedData[21]; // Encrypted payload (max 21 bytes) } __attribute__((packed)) victronManufacturerData; -// XXX End of new bit above +// Decrypted payload structures for each device type + +// Solar Charger decrypted payload +typedef struct { + uint8_t deviceState; // Charge state (SolarChargerState enum) + uint8_t errorCode; // Error code + int16_t batteryVoltage; // Battery voltage in 10mV units + int16_t batteryCurrent; // Battery current in 10mA units (signed) + uint16_t yieldToday; // Yield today in 10Wh units + uint16_t inputPower; // PV power in 1W units + uint16_t loadCurrent; // Load current in 10mA units (0xFFFF = no load) + uint8_t reserved[2]; // Reserved bytes +} __attribute__((packed)) victronSolarChargerPayload; + +// Battery Monitor decrypted payload +typedef struct { + uint16_t remainingMins; // Time remaining in minutes + uint16_t batteryVoltage; // Battery voltage in 10mV units + uint8_t alarms; // Alarm bits + uint16_t auxData; // Aux voltage (10mV) or temperature (0.01K) + uint8_t currentLow; // Battery current bits 0-7 + uint8_t currentMid; // Battery current bits 8-15 + uint8_t currentHigh_consumedLow; // Current bits 16-21 (low 6 bits), consumed bits 0-1 (high 2 bits) + uint8_t consumedMid; // Consumed Ah bits 2-9 + uint8_t consumedHigh; // Consumed Ah bits 10-17 + uint16_t soc; // State of charge in 0.1% units (10-bit value) + uint8_t reserved[2]; // Reserved bytes +} __attribute__((packed)) victronBatteryMonitorPayload; + +// Inverter decrypted payload +typedef struct { + uint8_t deviceState; // Device state + uint8_t errorCode; // Error code + uint16_t batteryVoltage; // Battery voltage in 10mV units + int16_t batteryCurrent; // Battery current in 10mA units (signed) + uint8_t acPowerLow; // AC Power bits 0-7 + uint8_t acPowerMid; // AC Power bits 8-15 + uint8_t acPowerHigh; // AC Power bits 16-23 (signed 24-bit) + uint8_t alarms; // Alarm bits + uint8_t reserved[4]; // Reserved bytes +} __attribute__((packed)) victronInverterPayload; + +// DC-DC Converter decrypted payload +typedef struct { + uint8_t chargeState; // Charge state + uint8_t errorCode; // Error code + uint16_t inputVoltage; // Input voltage in 10mV units + uint16_t outputVoltage; // Output voltage in 10mV units + uint16_t outputCurrent; // Output current in 10mA units + uint8_t reserved[6]; // Reserved bytes +} __attribute__((packed)) victronDCDCConverterPayload; // Base structure for all device data struct VictronDeviceData { From 2bd60949554e6b51718baa1f776b8829690e1696 Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Mon, 29 Dec 2025 11:09:33 +1100 Subject: [PATCH 3/3] Cleanup only --- examples/MultiDevice/src/main.cpp | 15 +++++++++------ src/VictronBLE.cpp | 13 +++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/MultiDevice/src/main.cpp b/examples/MultiDevice/src/main.cpp index 3c33f99..a102683 100644 --- a/examples/MultiDevice/src/main.cpp +++ b/examples/MultiDevice/src/main.cpp @@ -143,7 +143,7 @@ MyVictronCallback callback; void setup() { Serial.begin(115200); - delay(2000); + delay(1000); Serial.println("\n\n================================="); Serial.println("VictronBLE Multi-Device Example"); @@ -155,14 +155,12 @@ void setup() { Serial.println(victron.getLastError()); while (1) delay(1000); } - delay(1000); // Enable debug output (optional) victron.setDebug(true); // Set callback for data updates victron.setCallback(&callback); - delay(1000); // Add your devices here // Replace with your actual MAC addresses and encryption keys @@ -189,7 +187,6 @@ void setup() { "0ec3adf7433dd61793ff2f3b8ad32ed8", // Encryption key (32 hex chars) DEVICE_TYPE_SOLAR_CHARGER // Device type ); - delay(1000); /* * @@ -209,41 +206,47 @@ void setup() { */ // Example: Solar Charger #1 + /* victron.addDevice( "MPPT 100/30", // Device name "E7:48:D4:28:B7:9C", // MAC address "0df4d0395b7d1a876c0c33ecb9e70dcd", // Encryption key (32 hex chars) DEVICE_TYPE_SOLAR_CHARGER // Device type ); + */ // Example: Solar Charger #2 + /* victron.addDevice( "MPPT 75/15", "AA:BB:CC:DD:EE:FF", "1234567890abcdef1234567890abcdef", DEVICE_TYPE_SOLAR_CHARGER ); + */ // Example: Battery Monitor (SmartShunt) + /* victron.addDevice( "SmartShunt", "11:22:33:44:55:66", "fedcba0987654321fedcba0987654321", DEVICE_TYPE_BATTERY_MONITOR ); + */ // Example: Inverter/Charger + /* victron.addDevice( "MultiPlus", "99:88:77:66:55:44", "abcdefabcdefabcdefabcdefabcdefab", DEVICE_TYPE_INVERTER ); + */ - delay(1000); Serial.println("Configured " + String(victron.getDeviceCount()) + " devices"); Serial.println("\nStarting BLE scan...\n"); - delay(1000); } void loop() { diff --git a/src/VictronBLE.cpp b/src/VictronBLE.cpp index a4804c7..dcea477 100644 --- a/src/VictronBLE.cpp +++ b/src/VictronBLE.cpp @@ -471,8 +471,8 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon // Parse battery current (22-bit signed, 1 mA units) // Bits 0-7: currentLow, Bits 8-15: currentMid, Bits 16-21: low 6 bits of currentHigh_consumedLow - int32_t current = payload->currentLow | - (payload->currentMid << 8) | + int32_t current = payload->currentLow | + (payload->currentMid << 8) | ((payload->currentHigh_consumedLow & 0x3F) << 16); // Sign extend from 22 bits to 32 bits if (current & 0x200000) { @@ -482,8 +482,8 @@ bool VictronBLE::parseBatteryMonitor(const uint8_t* data, size_t len, BatteryMon // Parse consumed Ah (18-bit signed, 10 mAh units) // Bits 0-1: high 2 bits of currentHigh_consumedLow, Bits 2-9: consumedMid, Bits 10-17: consumedHigh - int32_t consumedAh = ((payload->currentHigh_consumedLow & 0xC0) >> 6) | - (payload->consumedMid << 2) | + int32_t consumedAh = ((payload->currentHigh_consumedLow & 0xC0) >> 6) | + (payload->consumedMid << 2) | (payload->consumedHigh << 10); // Sign extend from 18 bits to 32 bits if (consumedAh & 0x20000) { @@ -520,8 +520,8 @@ bool VictronBLE::parseInverter(const uint8_t* data, size_t len, InverterData& re result.batteryCurrent = payload->batteryCurrent * 0.01f; // Parse AC Power (signed 24-bit, 1 W units) - int32_t acPower = payload->acPowerLow | - (payload->acPowerMid << 8) | + int32_t acPower = payload->acPowerLow | + (payload->acPowerMid << 8) | (payload->acPowerHigh << 16); // Sign extend from 24 bits to 32 bits if (acPower & 0x800000) { @@ -698,6 +698,7 @@ void VictronBLE::debugPrint(const String& message) { } } +// XXX Can't we use debugPrintf instead for hex struct etc? void VictronBLE::debugPrintHex(const char* label, const uint8_t* data, size_t len) { if (!debugEnabled) return;