UNPKG

node-red-contrib-victron-ble

Version:

node-red node to parse Instant Readout advertisement data from Victron BLE devices

354 lines (348 loc) 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BitReader = exports.kelvinToCelsius = exports.Device = exports.ACInState = exports.AlarmNotification = exports.AlarmReason = exports.OffReason = exports.ChargerError = exports.OperationMode = exports.EnumField = void 0; const crypto_1 = require("crypto"); require("reflect-metadata"); // Decorator to mark a property as an enum field function EnumField(enumType) { return function (target, propertyKey) { Reflect.defineMetadata('enumType', enumType, target, propertyKey); }; } exports.EnumField = EnumField; // Sourced from VE.Direct docs var OperationMode; (function (OperationMode) { OperationMode[OperationMode["OFF"] = 0] = "OFF"; OperationMode[OperationMode["LOW_POWER"] = 1] = "LOW_POWER"; OperationMode[OperationMode["FAULT"] = 2] = "FAULT"; OperationMode[OperationMode["BULK"] = 3] = "BULK"; OperationMode[OperationMode["ABSORPTION"] = 4] = "ABSORPTION"; OperationMode[OperationMode["FLOAT"] = 5] = "FLOAT"; OperationMode[OperationMode["STORAGE"] = 6] = "STORAGE"; OperationMode[OperationMode["EQUALIZE_MANUAL"] = 7] = "EQUALIZE_MANUAL"; OperationMode[OperationMode["INVERTING"] = 9] = "INVERTING"; OperationMode[OperationMode["POWER_SUPPLY"] = 11] = "POWER_SUPPLY"; OperationMode[OperationMode["STARTING_UP"] = 245] = "STARTING_UP"; OperationMode[OperationMode["REPEATED_ABSORPTION"] = 246] = "REPEATED_ABSORPTION"; OperationMode[OperationMode["RECONDITION"] = 247] = "RECONDITION"; OperationMode[OperationMode["BATTERY_SAFE"] = 248] = "BATTERY_SAFE"; OperationMode[OperationMode["ACTIVE"] = 249] = "ACTIVE"; OperationMode[OperationMode["EXTERNAL_CONTROL"] = 252] = "EXTERNAL_CONTROL"; OperationMode[OperationMode["NOT_AVAILABLE"] = 255] = "NOT_AVAILABLE"; })(OperationMode || (exports.OperationMode = OperationMode = {})); // Source: VE.Direct-Protocol-3.32.pdf & https://www.victronenergy.com/live/mppt-error-codes var ChargerError; (function (ChargerError) { // No error ChargerError[ChargerError["NO_ERROR"] = 0] = "NO_ERROR"; // Err 1 - Battery temperature too high ChargerError[ChargerError["TEMPERATURE_BATTERY_HIGH"] = 1] = "TEMPERATURE_BATTERY_HIGH"; // Err 2 - Battery voltage too high ChargerError[ChargerError["VOLTAGE_HIGH"] = 2] = "VOLTAGE_HIGH"; // Err 3 - Remote temperature sensor failure (auto-reset) ChargerError[ChargerError["REMOTE_TEMPERATURE_A"] = 3] = "REMOTE_TEMPERATURE_A"; // Err 4 - Remote temperature sensor failure (auto-reset) ChargerError[ChargerError["REMOTE_TEMPERATURE_B"] = 4] = "REMOTE_TEMPERATURE_B"; // Err 5 - Remote temperature sensor failure (not auto-reset) ChargerError[ChargerError["REMOTE_TEMPERATURE_C"] = 5] = "REMOTE_TEMPERATURE_C"; // Err 6 - Remote battery voltage sense failure ChargerError[ChargerError["REMOTE_BATTERY_A"] = 6] = "REMOTE_BATTERY_A"; // Err 7 - Remote battery voltage sense failure ChargerError[ChargerError["REMOTE_BATTERY_B"] = 7] = "REMOTE_BATTERY_B"; // Err 8 - Remote battery voltage sense failure ChargerError[ChargerError["REMOTE_BATTERY_C"] = 8] = "REMOTE_BATTERY_C"; // Err 11 - Battery high ripple voltage ChargerError[ChargerError["HIGH_RIPPLE"] = 11] = "HIGH_RIPPLE"; // Err 14 - Battery temperature too low ChargerError[ChargerError["TEMPERATURE_BATTERY_LOW"] = 14] = "TEMPERATURE_BATTERY_LOW"; // Err 17 - Charger temperature too high ChargerError[ChargerError["TEMPERATURE_CHARGER"] = 17] = "TEMPERATURE_CHARGER"; // Err 18 - Charger over current ChargerError[ChargerError["OVER_CURRENT"] = 18] = "OVER_CURRENT"; // Err 20 - Bulk time limit exceeded ChargerError[ChargerError["BULK_TIME"] = 20] = "BULK_TIME"; // Err 21 - Current sensor issue (sensor bias/sensor broken) ChargerError[ChargerError["CURRENT_SENSOR"] = 21] = "CURRENT_SENSOR"; // Err 22 - Internal temperature sensor failure ChargerError[ChargerError["INTERNAL_TEMPERATURE_A"] = 22] = "INTERNAL_TEMPERATURE_A"; // Err 23 - Internal temperature sensor failure ChargerError[ChargerError["INTERNAL_TEMPERATURE_B"] = 23] = "INTERNAL_TEMPERATURE_B"; // Err 24 - Fan failure ChargerError[ChargerError["FAN"] = 24] = "FAN"; // Err 26 - Terminals overheated ChargerError[ChargerError["OVERHEATED"] = 26] = "OVERHEATED"; // Err 27 - Charger short circuit ChargerError[ChargerError["SHORT_CIRCUIT"] = 27] = "SHORT_CIRCUIT"; // Err 28 - Power stage issue Converter issue (dual converter models only) ChargerError[ChargerError["CONVERTER_ISSUE"] = 28] = "CONVERTER_ISSUE"; // Err 29 - Over-Charge protection ChargerError[ChargerError["OVER_CHARGE"] = 29] = "OVER_CHARGE"; // Err 33 - Input voltage too high (solar panel) PV over-voltage ChargerError[ChargerError["INPUT_VOLTAGE"] = 33] = "INPUT_VOLTAGE"; // Err 34 - Input current too high (solar panel) PV over-current ChargerError[ChargerError["INPUT_CURRENT"] = 34] = "INPUT_CURRENT"; // Err 35 - PV over-power ChargerError[ChargerError["INPUT_POWER"] = 35] = "INPUT_POWER"; // Err 38 - Input shutdown (due to excessive battery voltage) ChargerError[ChargerError["INPUT_SHUTDOWN_VOLTAGE"] = 38] = "INPUT_SHUTDOWN_VOLTAGE"; // Err 39 - Input shutdown (due to current flow during off mode) ChargerError[ChargerError["INPUT_SHUTDOWN_CURRENT"] = 39] = "INPUT_SHUTDOWN_CURRENT"; // Err 40 - PV Input failed to shutdown ChargerError[ChargerError["INPUT_SHUTDOWN_FAILURE"] = 40] = "INPUT_SHUTDOWN_FAILURE"; // Err 41 - Inverter shutdown (PV isolation) ChargerError[ChargerError["INVERTER_SHUTDOWN_41"] = 41] = "INVERTER_SHUTDOWN_41"; // Err 42 - Inverter shutdown (PV isolation) ChargerError[ChargerError["INVERTER_SHUTDOWN_42"] = 42] = "INVERTER_SHUTDOWN_42"; // Err 43 - Inverter shutdown (Ground Fault) ChargerError[ChargerError["INVERTER_SHUTDOWN_43"] = 43] = "INVERTER_SHUTDOWN_43"; // Err 50 - Inverter overload ChargerError[ChargerError["INVERTER_OVERLOAD"] = 50] = "INVERTER_OVERLOAD"; // Err 51 - Inverter temperature too high ChargerError[ChargerError["INVERTER_TEMPERATURE"] = 51] = "INVERTER_TEMPERATURE"; // Err 52 - Inverter peak current ChargerError[ChargerError["INVERTER_PEAK_CURRENT"] = 52] = "INVERTER_PEAK_CURRENT"; // Err 53 - Inverter output voltage ChargerError[ChargerError["INVERTER_OUPUT_VOLTAGE_A"] = 53] = "INVERTER_OUPUT_VOLTAGE_A"; // Err 54 - Inverter output voltage ChargerError[ChargerError["INVERTER_OUPUT_VOLTAGE_B"] = 54] = "INVERTER_OUPUT_VOLTAGE_B"; // Err 55 - Inverter self test failed ChargerError[ChargerError["INVERTER_SELF_TEST_A"] = 55] = "INVERTER_SELF_TEST_A"; // Err 56 - Inverter self test failed ChargerError[ChargerError["INVERTER_SELF_TEST_B"] = 56] = "INVERTER_SELF_TEST_B"; // Err 57 - Inverter ac voltage on output ChargerError[ChargerError["INVERTER_AC"] = 57] = "INVERTER_AC"; // Err 58 - Inverter self test failed ChargerError[ChargerError["INVERTER_SELF_TEST_C"] = 58] = "INVERTER_SELF_TEST_C"; // Information 65 - Communication warning Lost communication with one of devices ChargerError[ChargerError["COMMUNICATION"] = 65] = "COMMUNICATION"; // Information 66 - Incompatible device Synchronised charging device configuration issue ChargerError[ChargerError["SYNCHRONISATION"] = 66] = "SYNCHRONISATION"; // Err 67 - BMS Connection lost ChargerError[ChargerError["BMS"] = 67] = "BMS"; // Err 68 - Network misconfigured ChargerError[ChargerError["NETWORK_A"] = 68] = "NETWORK_A"; // Err 69 - Network misconfigured ChargerError[ChargerError["NETWORK_B"] = 69] = "NETWORK_B"; // Err 70 - Network misconfigured ChargerError[ChargerError["NETWORK_C"] = 70] = "NETWORK_C"; // Err 71 - Network misconfigured ChargerError[ChargerError["NETWORK_D"] = 71] = "NETWORK_D"; // Err 80 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_80"] = 80] = "PV_INPUT_SHUTDOWN_80"; // Err 81 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_81"] = 81] = "PV_INPUT_SHUTDOWN_81"; // Err 82 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_82"] = 82] = "PV_INPUT_SHUTDOWN_82"; // Err 83 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_83"] = 83] = "PV_INPUT_SHUTDOWN_83"; // Err 84 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_84"] = 84] = "PV_INPUT_SHUTDOWN_84"; // Err 85 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_85"] = 85] = "PV_INPUT_SHUTDOWN_85"; // Err 86 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_86"] = 86] = "PV_INPUT_SHUTDOWN_86"; // Err 87 - PV Input shutdown ChargerError[ChargerError["PV_INPUT_SHUTDOWN_87"] = 87] = "PV_INPUT_SHUTDOWN_87"; // Err 114 - CPU temperature too high ChargerError[ChargerError["CPU_TEMPERATURE"] = 114] = "CPU_TEMPERATURE"; // Err 116 - Factory calibration data lost ChargerError[ChargerError["CALIBRATION_LOST"] = 116] = "CALIBRATION_LOST"; // Err 117 - Invalid/incompatible firmware ChargerError[ChargerError["FIRMWARE"] = 117] = "FIRMWARE"; // Err 119 - Settings data lost ChargerError[ChargerError["SETTINGS"] = 119] = "SETTINGS"; // Err 121 - Tester fail ChargerError[ChargerError["TESTER_FAIL"] = 121] = "TESTER_FAIL"; // Err 200 - Internal DC voltage error ChargerError[ChargerError["INTERNAL_DC_VOLTAGE_A"] = 200] = "INTERNAL_DC_VOLTAGE_A"; // Err 201 - Internal DC voltage error ChargerError[ChargerError["INTERNAL_DC_VOLTAGE_B"] = 201] = "INTERNAL_DC_VOLTAGE_B"; // Err 202 - PV residual current sensor self-test failure Internal GFCI sensor error ChargerError[ChargerError["SELF_TEST"] = 202] = "SELF_TEST"; // Err 203 - Internal supply voltage error ChargerError[ChargerError["INTERNAL_SUPPLY_A"] = 203] = "INTERNAL_SUPPLY_A"; // Err 205 - Internal supply voltage error ChargerError[ChargerError["INTERNAL_SUPPLY_B"] = 205] = "INTERNAL_SUPPLY_B"; // Err 212 - Internal supply voltage error ChargerError[ChargerError["INTERNAL_SUPPLY_C"] = 212] = "INTERNAL_SUPPLY_C"; // Err 215 - Internal supply voltage error ChargerError[ChargerError["INTERNAL_SUPPLY_D"] = 215] = "INTERNAL_SUPPLY_D"; })(ChargerError || (exports.ChargerError = ChargerError = {})); var OffReason; (function (OffReason) { OffReason[OffReason["NO_REASON"] = 0] = "NO_REASON"; OffReason[OffReason["NO_INPUT_POWER"] = 1] = "NO_INPUT_POWER"; OffReason[OffReason["SWITCHED_OFF_SWITCH"] = 2] = "SWITCHED_OFF_SWITCH"; OffReason[OffReason["SWITCHED_OFF_REGISTER"] = 4] = "SWITCHED_OFF_REGISTER"; OffReason[OffReason["REMOTE_INPUT"] = 8] = "REMOTE_INPUT"; OffReason[OffReason["PROTECTION_ACTIVE"] = 16] = "PROTECTION_ACTIVE"; OffReason[OffReason["PAY_AS_YOU_GO_OUT_OF_CREDIT"] = 32] = "PAY_AS_YOU_GO_OUT_OF_CREDIT"; OffReason[OffReason["BMS"] = 64] = "BMS"; OffReason[OffReason["ENGINE_SHUTDOWN"] = 128] = "ENGINE_SHUTDOWN"; OffReason[OffReason["ANALYSING_INPUT_VOLTAGE"] = 256] = "ANALYSING_INPUT_VOLTAGE"; })(OffReason || (exports.OffReason = OffReason = {})); var AlarmReason; (function (AlarmReason) { AlarmReason[AlarmReason["NO_ALARM"] = 0] = "NO_ALARM"; AlarmReason[AlarmReason["LOW_VOLTAGE"] = 1] = "LOW_VOLTAGE"; AlarmReason[AlarmReason["HIGH_VOLTAGE"] = 2] = "HIGH_VOLTAGE"; AlarmReason[AlarmReason["LOW_SOC"] = 4] = "LOW_SOC"; AlarmReason[AlarmReason["LOW_STARTER_VOLTAGE"] = 8] = "LOW_STARTER_VOLTAGE"; AlarmReason[AlarmReason["HIGH_STARTER_VOLTAGE"] = 16] = "HIGH_STARTER_VOLTAGE"; AlarmReason[AlarmReason["LOW_TEMPERATURE"] = 32] = "LOW_TEMPERATURE"; AlarmReason[AlarmReason["HIGH_TEMPERATURE"] = 64] = "HIGH_TEMPERATURE"; AlarmReason[AlarmReason["MID_VOLTAGE"] = 128] = "MID_VOLTAGE"; AlarmReason[AlarmReason["OVERLOAD"] = 256] = "OVERLOAD"; AlarmReason[AlarmReason["DC_RIPPLE"] = 512] = "DC_RIPPLE"; AlarmReason[AlarmReason["LOW_V_AC_OUT"] = 1024] = "LOW_V_AC_OUT"; AlarmReason[AlarmReason["HIGH_V_AC_OUT"] = 2048] = "HIGH_V_AC_OUT"; AlarmReason[AlarmReason["SHORT_CIRCUIT"] = 4096] = "SHORT_CIRCUIT"; })(AlarmReason || (exports.AlarmReason = AlarmReason = {})); var AlarmNotification; (function (AlarmNotification) { AlarmNotification[AlarmNotification["NO_ALARM"] = 0] = "NO_ALARM"; AlarmNotification[AlarmNotification["WARNING"] = 1] = "WARNING"; AlarmNotification[AlarmNotification["ALARM"] = 2] = "ALARM"; })(AlarmNotification || (exports.AlarmNotification = AlarmNotification = {})); var ACInState; (function (ACInState) { ACInState[ACInState["AC_IN_1"] = 0] = "AC_IN_1"; ACInState[ACInState["AC_IN_2"] = 1] = "AC_IN_2"; ACInState[ACInState["NOT_CONNECTED"] = 2] = "NOT_CONNECTED"; ACInState[ACInState["UNKNOWN"] = 3] = "UNKNOWN"; })(ACInState || (exports.ACInState = ACInState = {})); /* export abstract class DeviceData { protected _data: Record<string, any>; protected _modelId: number; constructor(modelId: number, data: Record<string, any>) { this._modelId = modelId; this._data = data; } getModelName(): string { const productName = getProductName(this._modelId); return productName || `Model ${this._modelId.toString(16).toUpperCase()}`; } } */ class Device { advertisementKey; constructor(advertisementKey) { this.advertisementKey = advertisementKey; } parseContainer(data) { const prefix = data.readUInt16LE(0); // 2 bytes, little endian const modelId = data.readUInt16LE(2); // 2 bytes, little endian const readoutType = data.readUInt8(4); // 1 byte const iv = data.readUInt16LE(5); // 2 bytes, little endian const encryptedData = data.slice(7); // rest of data starting at offset 7 return { prefix, modelId, readoutType, iv, encryptedData, }; } getModelId(data) { return data.readUInt16LE(2); } decrypt(data) { const container = this.parseContainer(data); // Convert hex key to buffer const key = Buffer.from(this.advertisementKey, 'hex'); // Key check: first byte of encrypted data should match first byte of key if (container.encryptedData[0] !== key[0]) { throw new Error("Incorrect advertisement key"); } // Skip the first byte (key check byte) and get the actual encrypted data const encryptedData = container.encryptedData.slice(1); // Create IV from the 32-bit value (little endian) const iv = Buffer.alloc(16); iv.writeUInt32LE(container.iv, 0); // Create decipher with CTR mode const decipher = (0, crypto_1.createDecipheriv)('aes-128-ctr', key, iv); decipher.setAutoPadding(false); // Decrypt the data (without the key check byte) const decrypted = Buffer.concat([ decipher.update(encryptedData), decipher.final() ]); return decrypted; } parse(data) { const decrypted = this.decrypt(data); this.parseDecrypted(decrypted); } toJson() { const data = {}; for (const key of Object.keys(this)) { if (key === 'advertisementKey') continue; let value = this[key]; if (value === undefined) continue; // Check for enum metadata const enumType = Reflect.getMetadata('enumType', this, key); if (enumType && typeof value === 'number') { for (const enumKey in enumType) { if (enumType[enumKey] === value) { data[key] = enumKey; break; } } } else if (Array.isArray(value)) { data[key] = value.map(v => typeof v === 'number' ? v : v); } else { data[key] = value; } } return data; } } exports.Device = Device; function kelvinToCelsius(tempInKelvin) { return tempInKelvin - 273.15; } exports.kelvinToCelsius = kelvinToCelsius; class BitReader { data; bitPosition = 0; constructor(data) { this.data = data; } readBit() { const byteIndex = Math.floor(this.bitPosition / 8); const bitIndex = this.bitPosition % 8; const byte = this.data[byteIndex]; if (byteIndex > this.data.length) { throw new Error(`length error ${byte} ${byteIndex} / ${this.data.length}`); } const bit = (byte >> bitIndex) & 1; this.bitPosition++; return bit; } readUnsignedInt(numBits) { let result = 0; for (let i = 0; i < numBits; i++) { result |= this.readBit() << i; } return result; } readSignedInt(numBits) { const value = this.readUnsignedInt(numBits); return BitReader.toSignedInt(value, numBits); } static toSignedInt(value, numBits) { const maxValue = (1 << numBits) - 1; const halfMax = 1 << (numBits - 1); if (value > halfMax) { return value - (maxValue + 1); } return value; } } exports.BitReader = BitReader;