Improved structs
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
|
||||
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]; // (31 bytes max per BLE spec - size of previous elements)
|
||||
uint8_t nullPad; // extra byte because toCharArray() adds a \0 byte.
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user