bt-sensors-plugin-sk 1.3.2 → 1.3.4-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI-DEV.md +784 -0
- package/BTSensor.js +10 -7
- package/README.md +17 -0
- package/index.js +36 -14
- package/package.json +2 -2
- package/pairing.md +147 -0
- package/sensor_classes/HumsienkBMS.js +385 -0
- package/sensor_classes/JBDBMS.js +1 -0
- package/sensor_classes/JikongBMS.js +1 -1
- package/sensor_classes/Renogy/RenogySensor.js +15 -6
- package/sensor_classes/RenogyBattery.js +28 -14
- package/sensor_classes/RenogyInverter.js +17 -3
- package/sensor_classes/RenogyRoverClient.js +9 -18
- package/sensor_classes/ShenzhenLiOnBMS.js +3 -2
- package/sensor_classes/VictronBatteryMonitor.js +1 -4
- package/sensor_classes/VictronDCEnergyMeter.js +0 -1
- package/sensor_classes/VictronInverter.js +0 -1
- package/sensor_classes/VictronInverterRS.js +0 -1
- package/sensor_classes/VictronSmartBatteryProtect.js +2 -3
- package/sensor_classes/XiaomiMiBeacon.js +52 -24
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
const BTSensor = require("../BTSensor");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HSC14F Battery Management System Sensor Class
|
|
5
|
+
*
|
|
6
|
+
* Manufacturer: BMC (HumsiENK branded)
|
|
7
|
+
* Protocol: Custom BLE protocol using AA prefix commands
|
|
8
|
+
*
|
|
9
|
+
* Discovered protocol details:
|
|
10
|
+
* - TX Handle: 0x000c (write commands to battery)
|
|
11
|
+
* - RX Handle: 0x000e (receive notifications from battery)
|
|
12
|
+
* - Command format: aa [CMD] 00 [CMD] 00
|
|
13
|
+
* - Multi-part responses (some commands send 2-3 notifications)
|
|
14
|
+
*
|
|
15
|
+
* Key Commands:
|
|
16
|
+
* - 0x00: Handshake
|
|
17
|
+
* - 0x21: Real-time battery data (voltage, current, SOC, temps) - PRIMARY
|
|
18
|
+
* - 0x22: Individual cell voltages
|
|
19
|
+
* - 0x23: Battery status/warnings
|
|
20
|
+
* - 0x20: Configuration data
|
|
21
|
+
* - 0x10: Manufacturer name
|
|
22
|
+
* - 0x11: Model number
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class HumsienkBMS extends BTSensor {
|
|
26
|
+
static Domain = BTSensor.SensorDomains.electrical;
|
|
27
|
+
|
|
28
|
+
// Discovered actual UUIDs from device
|
|
29
|
+
static TX_RX_SERVICE = "00000001-0000-1000-8000-00805f9b34fb";
|
|
30
|
+
static WRITE_CHAR_UUID = "00000002-0000-1000-8000-00805f9b34fb";
|
|
31
|
+
static NOTIFY_CHAR_UUID = "00000003-0000-1000-8000-00805f9b34fb";
|
|
32
|
+
|
|
33
|
+
static identify(device) {
|
|
34
|
+
// HSC14F batteries advertise with this service UUID
|
|
35
|
+
// Further identification would require GATT connection to read manufacturer/model
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static ImageFile = "JBDBMS.webp"; // Using similar BMS image for now
|
|
40
|
+
static Manufacturer = "BMC (HumsiENK)";
|
|
41
|
+
static Description = "HSC14F LiFePO4 Battery Management System";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create HSC14F command
|
|
45
|
+
* Format: aa [CMD] 00 [CMD] 00
|
|
46
|
+
*/
|
|
47
|
+
hsc14fCommand(command) {
|
|
48
|
+
return [0xaa, command, 0x00, command, 0x00];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Send command to battery
|
|
53
|
+
* HSC14F requires Write Request (0x12) not Write Command (0x52)
|
|
54
|
+
*/
|
|
55
|
+
async sendCommand(command) {
|
|
56
|
+
this.debug(`Sending command 0x${command.toString(16)} to ${this.getName()}`);
|
|
57
|
+
return await this.txChar.writeValue(
|
|
58
|
+
Buffer.from(this.hsc14fCommand(command))
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async initSchema() {
|
|
63
|
+
super.initSchema();
|
|
64
|
+
this.addDefaultParam("batteryID");
|
|
65
|
+
|
|
66
|
+
// Number of cells parameter - configurable (default 4 for LiFePO4)
|
|
67
|
+
if (this.numberOfCells === undefined) {
|
|
68
|
+
this.numberOfCells = 4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.addParameter("numberOfCells", {
|
|
72
|
+
title: "Number of cells",
|
|
73
|
+
description: "Number of cells in the battery (typically 4 for 12V LiFePO4)",
|
|
74
|
+
type: "number",
|
|
75
|
+
isRequired: true,
|
|
76
|
+
default: this.numberOfCells,
|
|
77
|
+
minimum: 1,
|
|
78
|
+
maximum: 16,
|
|
79
|
+
multipleOf: 1
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Voltage
|
|
83
|
+
this.addDefaultPath("voltage", "electrical.batteries.voltage").read = (
|
|
84
|
+
buffer
|
|
85
|
+
) => {
|
|
86
|
+
// Bytes 3-4: voltage in mV, little-endian
|
|
87
|
+
return buffer.readUInt16LE(3) / 1000;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Current - CORRECTED based on buffer analysis
|
|
91
|
+
this.addDefaultPath("current", "electrical.batteries.current").read = (
|
|
92
|
+
buffer
|
|
93
|
+
) => {
|
|
94
|
+
// Bytes 7-8: current in milliamps, signed little-endian
|
|
95
|
+
// Negative = charging, Positive = discharging
|
|
96
|
+
return buffer.readInt16LE(7) / 1000;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// State of Charge
|
|
100
|
+
this.addDefaultPath(
|
|
101
|
+
"SOC",
|
|
102
|
+
"electrical.batteries.capacity.stateOfCharge"
|
|
103
|
+
).read = (buffer) => {
|
|
104
|
+
// Byte 11: SOC percentage (0-100)
|
|
105
|
+
return buffer.readUInt8(11) / 100;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Temperature 1 (ENV temperature) - CORRECTED byte position
|
|
109
|
+
this.addMetadatum("temp1", "K", "Battery Environment Temperature", (buffer) => {
|
|
110
|
+
// Byte 27: Environment temperature in °C
|
|
111
|
+
const tempC = buffer.readUInt8(27);
|
|
112
|
+
return 273.15 + tempC; // Convert to Kelvin
|
|
113
|
+
}).default = "electrical.batteries.{batteryID}.temperature";
|
|
114
|
+
|
|
115
|
+
// Temperature 2 (MOS temperature) - CORRECTED byte position
|
|
116
|
+
this.addMetadatum("temp2", "K", "Battery MOS Temperature", (buffer) => {
|
|
117
|
+
// Byte 28: MOS (MOSFET) temperature in °C
|
|
118
|
+
const tempC = buffer.readUInt8(28);
|
|
119
|
+
return 273.15 + tempC;
|
|
120
|
+
}).default = "electrical.batteries.{batteryID}.mosfetTemperature";
|
|
121
|
+
|
|
122
|
+
// Temperature 3 (Sensor) - CORRECTED byte position
|
|
123
|
+
this.addMetadatum("temp3", "K", "Battery Sensor Temperature", (buffer) => {
|
|
124
|
+
// Byte 29: Additional temperature sensor in °C
|
|
125
|
+
const tempC = buffer.readUInt8(29);
|
|
126
|
+
return 273.15 + tempC;
|
|
127
|
+
}).default = "electrical.batteries.{batteryID}.sensorTemperature";
|
|
128
|
+
|
|
129
|
+
// Manufacturer (from command 0x10)
|
|
130
|
+
this.addMetadatum(
|
|
131
|
+
"manufacturer",
|
|
132
|
+
"",
|
|
133
|
+
"Battery Manufacturer",
|
|
134
|
+
(buffer) => {
|
|
135
|
+
// Response: aa 10 03 42 4d 43 ...
|
|
136
|
+
// ASCII bytes starting at position 3
|
|
137
|
+
const len = buffer.readUInt8(2);
|
|
138
|
+
return buffer.toString("ascii", 3, 3 + len);
|
|
139
|
+
}
|
|
140
|
+
).default = "electrical.batteries.{batteryID}.manufacturer";
|
|
141
|
+
|
|
142
|
+
// Model (from command 0x11)
|
|
143
|
+
this.addMetadatum("model", "", "Battery Model", (buffer) => {
|
|
144
|
+
// Response: aa 11 0a 42 4d 43 2d 30 34 53 30 30 31 ...
|
|
145
|
+
const len = buffer.readUInt8(2);
|
|
146
|
+
return buffer.toString("ascii", 3, 3 + len);
|
|
147
|
+
}).default = "electrical.batteries.{batteryID}.model";
|
|
148
|
+
|
|
149
|
+
// Cell voltages (from command 0x22)
|
|
150
|
+
// Number of cells is configurable via numberOfCells parameter
|
|
151
|
+
for (let i = 0; i < this.numberOfCells; i++) {
|
|
152
|
+
this.addMetadatum(
|
|
153
|
+
`cell${i}Voltage`,
|
|
154
|
+
"V",
|
|
155
|
+
`Cell ${i + 1} voltage`,
|
|
156
|
+
(buffer) => {
|
|
157
|
+
// Cell voltages: aa 22 30 6a 0d 58 0d 8f 0d 34 0d ...
|
|
158
|
+
// Starting at byte 3, each cell is 2 bytes little-endian in mV
|
|
159
|
+
return buffer.readUInt16LE(3 + i * 2) / 1000;
|
|
160
|
+
}
|
|
161
|
+
).default = `electrical.batteries.{batteryID}.cell${i}.voltage`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
hasGATT() {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
usingGATT() {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async initGATTNotifications() {
|
|
174
|
+
// Don't use notifications for polling mode
|
|
175
|
+
// The parent class initGATTInterval will handle periodic connections
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async emitGATT() {
|
|
179
|
+
try {
|
|
180
|
+
await this.getAndEmitBatteryInfo();
|
|
181
|
+
} catch (e) {
|
|
182
|
+
this.debug(`Failed to emit battery info for ${this.getName()}: ${e}`);
|
|
183
|
+
throw e; // Re-throw so parent can handle reconnection
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get cell voltages after a short delay
|
|
187
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await this.getAndEmitCellVoltages();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
this.debug(`Failed to emit cell voltages for ${this.getName()}: ${e}`);
|
|
193
|
+
throw e; // Re-throw so parent can handle reconnection
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get buffer response from battery command
|
|
199
|
+
* HSC14F sends multi-part responses for some commands
|
|
200
|
+
*/
|
|
201
|
+
async getBuffer(command) {
|
|
202
|
+
let result = Buffer.alloc(256);
|
|
203
|
+
let offset = 0;
|
|
204
|
+
let lastPacketTime = Date.now();
|
|
205
|
+
|
|
206
|
+
// Set up listener first
|
|
207
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
208
|
+
const timer = setTimeout(() => {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
if (completionTimer) clearTimeout(completionTimer);
|
|
211
|
+
this.rxChar.removeAllListeners("valuechanged");
|
|
212
|
+
reject(
|
|
213
|
+
new Error(
|
|
214
|
+
`Response timed out (+10s) from HSC14F device ${this.getName()}.`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
}, 10000);
|
|
218
|
+
|
|
219
|
+
let completionTimer = null;
|
|
220
|
+
|
|
221
|
+
const valChanged = (buffer) => {
|
|
222
|
+
// HSC14F responses start with 0xaa followed by command byte
|
|
223
|
+
if (offset === 0 && (buffer[0] !== 0xaa || buffer[1] !== command)) {
|
|
224
|
+
this.debug(
|
|
225
|
+
`Invalid buffer from ${this.getName()}, expected command 0x${command.toString(
|
|
226
|
+
16
|
|
227
|
+
)}, got 0x${buffer[0].toString(16)} 0x${buffer[1].toString(16)}`
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
buffer.copy(result, offset);
|
|
233
|
+
offset += buffer.length;
|
|
234
|
+
lastPacketTime = Date.now();
|
|
235
|
+
|
|
236
|
+
// Clear any existing completion timer
|
|
237
|
+
if (completionTimer) clearTimeout(completionTimer);
|
|
238
|
+
|
|
239
|
+
// Wait 200ms after last packet to consider response complete
|
|
240
|
+
// This allows multi-packet responses to assemble properly
|
|
241
|
+
completionTimer = setTimeout(() => {
|
|
242
|
+
result = Uint8Array.prototype.slice.call(result, 0, offset);
|
|
243
|
+
this.rxChar.removeAllListeners("valuechanged");
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
resolve(result);
|
|
246
|
+
}, 200);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Set up listener BEFORE sending command
|
|
250
|
+
this.rxChar.on("valuechanged", valChanged);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Small delay to ensure listener is attached
|
|
254
|
+
await new Promise(r => setTimeout(r, 100));
|
|
255
|
+
|
|
256
|
+
// Send the command
|
|
257
|
+
try {
|
|
258
|
+
await this.sendCommand(command);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
this.rxChar.removeAllListeners("valuechanged");
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Wait for response
|
|
265
|
+
return responsePromise;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async initGATTConnection(isReconnecting = false) {
|
|
269
|
+
await super.initGATTConnection(isReconnecting);
|
|
270
|
+
|
|
271
|
+
// Set up GATT characteristics
|
|
272
|
+
const gattServer = await this.device.gatt();
|
|
273
|
+
const txRxService = await gattServer.getPrimaryService(
|
|
274
|
+
this.constructor.TX_RX_SERVICE
|
|
275
|
+
);
|
|
276
|
+
this.rxChar = await txRxService.getCharacteristic(
|
|
277
|
+
this.constructor.NOTIFY_CHAR_UUID
|
|
278
|
+
);
|
|
279
|
+
this.txChar = await txRxService.getCharacteristic(
|
|
280
|
+
this.constructor.WRITE_CHAR_UUID
|
|
281
|
+
);
|
|
282
|
+
await this.rxChar.startNotifications();
|
|
283
|
+
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get and emit main battery data (voltage, current, SOC, temp)
|
|
289
|
+
* Uses command 0x21
|
|
290
|
+
*/
|
|
291
|
+
async getAndEmitBatteryInfo() {
|
|
292
|
+
return this.getBuffer(0x21).then((buffer) => {
|
|
293
|
+
// Debug logging to verify buffer received
|
|
294
|
+
this.debug(`Command 0x21 response: ${buffer.length} bytes, hex: ${buffer.slice(0, 30).toString('hex')}`);
|
|
295
|
+
|
|
296
|
+
["voltage", "current", "SOC", "temp1", "temp2", "temp3"].forEach((tag) => {
|
|
297
|
+
this.emitData(tag, buffer);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get and emit individual cell voltages
|
|
304
|
+
* Uses command 0x22
|
|
305
|
+
*/
|
|
306
|
+
async getAndEmitCellVoltages() {
|
|
307
|
+
return this.getBuffer(0x22).then((buffer) => {
|
|
308
|
+
// Debug logging to verify buffer received
|
|
309
|
+
this.debug(`Command 0x22 response: ${buffer.length} bytes, hex: ${buffer.slice(0, 30).toString('hex')}`);
|
|
310
|
+
|
|
311
|
+
for (let i = 0; i < this.numberOfCells; i++) {
|
|
312
|
+
this.emitData(`cell${i}Voltage`, buffer);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async initGATTInterval() {
|
|
318
|
+
// Get static info once (manufacturer, model) at first connection
|
|
319
|
+
try {
|
|
320
|
+
const mfgBuffer = await this.getBuffer(0x10);
|
|
321
|
+
this.emitData("manufacturer", mfgBuffer);
|
|
322
|
+
|
|
323
|
+
const modelBuffer = await this.getBuffer(0x11);
|
|
324
|
+
this.emitData("model", modelBuffer);
|
|
325
|
+
} catch (e) {
|
|
326
|
+
this.debug(`Failed to get device info: ${e.message}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Get first poll data before disconnecting
|
|
330
|
+
try {
|
|
331
|
+
await this.emitGATT();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
this.debug(`Initial poll failed: ${error.message}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Disconnect after initial data collection
|
|
337
|
+
await this.deactivateGATT().catch((e) => {
|
|
338
|
+
this.debug(`Error deactivating GATT Connection: ${e.message}`);
|
|
339
|
+
});
|
|
340
|
+
this.setState("WAITING");
|
|
341
|
+
|
|
342
|
+
// Set up polling interval - reconnect, poll, disconnect
|
|
343
|
+
this.intervalID = setInterval(async () => {
|
|
344
|
+
try {
|
|
345
|
+
this.setState("CONNECTING");
|
|
346
|
+
await this.initGATTConnection(true);
|
|
347
|
+
await this.emitGATT();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
// Check if device has been removed from BlueZ cache
|
|
350
|
+
if (error.message && error.message.includes("interface not found in proxy object")) {
|
|
351
|
+
this.debug(`Device removed from BlueZ cache. Clearing stale connection state.`);
|
|
352
|
+
this.setError(`Device out of range or removed from Bluetooth cache. Waiting for rediscovery...`);
|
|
353
|
+
|
|
354
|
+
// Clear the interval to stop futile reconnection attempts
|
|
355
|
+
if (this.intervalID) {
|
|
356
|
+
clearInterval(this.intervalID);
|
|
357
|
+
this.intervalID = null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Set state to indicate waiting for rediscovery
|
|
361
|
+
this.setState("OUT_OF_RANGE");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.debug(error);
|
|
366
|
+
this.setError(`Unable to emit values for device: ${error.message}`);
|
|
367
|
+
} finally {
|
|
368
|
+
await this.deactivateGATT().catch((e) => {
|
|
369
|
+
// Suppress errors when device is already removed from BlueZ
|
|
370
|
+
if (!e.message || !e.message.includes("interface not found")) {
|
|
371
|
+
this.debug(`Error deactivating GATT Connection: ${e.message}`);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
this.setState("WAITING");
|
|
375
|
+
}
|
|
376
|
+
}, this.pollFreq * 1000);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async deactivateGATT() {
|
|
380
|
+
await this.stopGATTNotifications(this.rxChar);
|
|
381
|
+
await super.deactivateGATT();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = HumsienkBMS;
|
package/sensor_classes/JBDBMS.js
CHANGED
|
@@ -257,7 +257,7 @@ class JikongBMS extends BTSensor {
|
|
|
257
257
|
|
|
258
258
|
this.addMetadatum("cycleCapacity", "number", "Cycle capacity", (buffer) => {
|
|
259
259
|
return buffer.readUInt32LE(154 + this.offset * 2) / 1000;
|
|
260
|
-
}).default = "electrical.batteries.{batteryID}.
|
|
260
|
+
}).default = "electrical.batteries.{batteryID}.cycleCapacity";
|
|
261
261
|
|
|
262
262
|
this.addMetadatum(
|
|
263
263
|
"balanceAction",
|
|
@@ -3,7 +3,7 @@ ported from https://github.com/cyrils/renogy-bt
|
|
|
3
3
|
*/
|
|
4
4
|
const BTSensor = require("../../BTSensor.js");
|
|
5
5
|
const VC = require('./RenogyConstants.js');
|
|
6
|
-
const crc16Modbus = require('./CRC.js')
|
|
6
|
+
const crc16Modbus = require('./CRC.js');
|
|
7
7
|
class RenogySensor extends BTSensor{
|
|
8
8
|
static Domain=BTSensor.SensorDomains.electrical
|
|
9
9
|
static ALIAS_PREFIX = 'BT-TH'
|
|
@@ -34,20 +34,27 @@ class RenogySensor extends BTSensor{
|
|
|
34
34
|
b.writeUInt16BE(words,4)
|
|
35
35
|
b.writeUInt16BE(crc16Modbus(b.subarray(0,6)),6)
|
|
36
36
|
|
|
37
|
-
await writeCharacteristic.
|
|
37
|
+
await writeCharacteristic.writeValueWithoutResponse(b, 0)
|
|
38
38
|
|
|
39
39
|
}
|
|
40
40
|
static identify(device){
|
|
41
41
|
return null
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
pollFreq=30
|
|
44
44
|
async initSchema(){
|
|
45
45
|
await super.initSchema()
|
|
46
|
-
|
|
46
|
+
this.getGATTParams().pollFreq.default=this.pollFreq
|
|
47
47
|
this.addParameter(
|
|
48
48
|
"deviceID",
|
|
49
49
|
{
|
|
50
|
-
title: 'ID of device'
|
|
50
|
+
title: 'ID of device',
|
|
51
|
+
description: 'only modify if device is in hub mode or daisy chained',
|
|
52
|
+
default:255,
|
|
53
|
+
type: 'number',
|
|
54
|
+
minimum: 0,
|
|
55
|
+
maximum: 255,
|
|
56
|
+
multipleOf:1,
|
|
57
|
+
isRequired: true
|
|
51
58
|
}
|
|
52
59
|
)
|
|
53
60
|
}
|
|
@@ -73,15 +80,17 @@ class RenogySensor extends BTSensor{
|
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
async sendReadFunctionRequest(writeReq, words){
|
|
76
|
-
this.constructor.sendReadFunctionRequest(
|
|
83
|
+
await this.constructor.sendReadFunctionRequest(
|
|
77
84
|
this.writeChar, this.getDeviceID(), writeReq, words)
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
initGATTInterval(){
|
|
88
|
+
this.emitGATT()
|
|
81
89
|
this.intervalID = setInterval(()=>{
|
|
82
90
|
this.emitGATT()
|
|
83
91
|
}, 1000*(this?.pollFreq??60) )
|
|
84
92
|
}
|
|
93
|
+
|
|
85
94
|
emitGATT(){
|
|
86
95
|
this.getAllEmitterFunctions().forEach(async (emitter)=>
|
|
87
96
|
await emitter()
|
|
@@ -12,9 +12,21 @@ class RenogyBattery extends RenogySensor {
|
|
|
12
12
|
this.getAndEmitCellTemperatures.bind(this),
|
|
13
13
|
this.getAndEmitCellVoltages.bind(this)]
|
|
14
14
|
}
|
|
15
|
+
numberOfCells=4
|
|
15
16
|
initSchema(){
|
|
16
|
-
|
|
17
|
-
this.
|
|
17
|
+
this.addDefaultParam("batteryID").default="house"
|
|
18
|
+
this.addParameter(
|
|
19
|
+
"numberOfCells",
|
|
20
|
+
{
|
|
21
|
+
title:"Number of cells",
|
|
22
|
+
type: "number",
|
|
23
|
+
isRequired: true,
|
|
24
|
+
default: this.numberOfCells,
|
|
25
|
+
minimum: 1,
|
|
26
|
+
maximum: 16,
|
|
27
|
+
multipleOf:1
|
|
28
|
+
}
|
|
29
|
+
)
|
|
18
30
|
this.addDefaultPath('current','electrical.batteries.current')
|
|
19
31
|
.read=(buffer)=>{return buffer.readInt16BE(3)/100}
|
|
20
32
|
|
|
@@ -41,31 +53,33 @@ class RenogyBattery extends RenogySensor {
|
|
|
41
53
|
async initGATTConnection() {
|
|
42
54
|
await super.initGATTConnection()
|
|
43
55
|
this.numberOfCells = await this.retrieveNumberOfCells()
|
|
44
|
-
this.deviceID = await this.retrieveDeviceID()
|
|
45
|
-
this.emit('numberOfCells', this.numberOfCells)
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
retrieveModelID(){
|
|
50
59
|
return new Promise( async ( resolve, reject )=>{
|
|
51
|
-
await this.sendReadFunctionRequest(0x1388,0x11)
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
await this.sendReadFunctionRequest(0x1402,0x08)
|
|
62
|
+
|
|
63
|
+
this.readChar.once('valuechanged', async (buffer) => {
|
|
64
|
+
if (buffer[2]!=0x10)
|
|
65
|
+
reject("Unknown error retrieving model ID") //???
|
|
66
|
+
const model = buffer.subarray(3,17).toString().trim()
|
|
67
|
+
resolve(model)
|
|
57
68
|
})
|
|
69
|
+
})
|
|
58
70
|
}
|
|
59
|
-
|
|
71
|
+
retrieveNumberOfCells(){
|
|
72
|
+
|
|
60
73
|
return new Promise( async ( resolve, reject )=>{
|
|
61
|
-
this.
|
|
74
|
+
await this.sendReadFunctionRequest(0x1388,0x11)
|
|
62
75
|
|
|
63
76
|
const valChanged = async (buffer) => {
|
|
64
|
-
resolve(
|
|
77
|
+
resolve(buffer.readUInt16(3))
|
|
65
78
|
}
|
|
66
79
|
this.readChar.once('valuechanged', valChanged )
|
|
67
80
|
})
|
|
68
81
|
}
|
|
82
|
+
|
|
69
83
|
|
|
70
84
|
getAndEmitBatteryInfo(){
|
|
71
85
|
return new Promise( async ( resolve, reject )=>{
|
|
@@ -54,10 +54,24 @@ class RenogyInverter extends RenogySensor {
|
|
|
54
54
|
})
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
retrieveModelID(){
|
|
58
|
+
return new Promise( async ( resolve, reject )=>{
|
|
59
|
+
|
|
60
|
+
await this.sendReadFunctionRequest(0x10d7,0x08)
|
|
61
|
+
|
|
62
|
+
this.readChar.once('valuechanged', async (buffer) => {
|
|
63
|
+
if (buffer[2]!=0x10)
|
|
64
|
+
reject("Unknown error retrieving model ID") //???
|
|
65
|
+
const model = buffer.subarray(3,17).toString().trim()
|
|
66
|
+
resolve(model)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
getAndEmitInverterStats(){
|
|
58
72
|
return new Promise( async ( resolve, reject )=>{
|
|
59
73
|
|
|
60
|
-
await this.sendReadFunctionRequest(0xfa0,
|
|
74
|
+
await this.sendReadFunctionRequest(0xfa0, 0xA)
|
|
61
75
|
|
|
62
76
|
this.readChar.once('valuechanged', (buffer) => {
|
|
63
77
|
["ueiVoltage","ueiCurrent", "voltage", "loadCurrent", "frequency","temperature"].forEach((tag)=>
|
|
@@ -71,7 +85,7 @@ class RenogyInverter extends RenogySensor {
|
|
|
71
85
|
getAndEmitSolarCharging(){
|
|
72
86
|
return new Promise( async ( resolve, reject )=>{
|
|
73
87
|
|
|
74
|
-
await this.sendReadFunctionRequest(0x10e9,
|
|
88
|
+
await this.sendReadFunctionRequest(0x10e9, 0x7)
|
|
75
89
|
|
|
76
90
|
this.readChar.once('valuechanged', (buffer) => {
|
|
77
91
|
["solarVoltage","solarCurrent", "solarPower", "solarChargingStatus", "solarChargingPower"].forEach((tag)=>
|
|
@@ -85,7 +99,7 @@ class RenogyInverter extends RenogySensor {
|
|
|
85
99
|
getAndEmitInverterLoad(){
|
|
86
100
|
return new Promise( async ( resolve, reject )=>{
|
|
87
101
|
|
|
88
|
-
await this.sendReadFunctionRequest(0x113a,
|
|
102
|
+
await this.sendReadFunctionRequest(0x113a, 0x6)
|
|
89
103
|
|
|
90
104
|
this.readChar.once('valuechanged', (buffer) => {
|
|
91
105
|
["loadPower", "chargingCurrent"].forEach((tag)=>
|
|
@@ -34,6 +34,10 @@ class RenogyRoverClient extends RenogySensor {
|
|
|
34
34
|
initSchema(){
|
|
35
35
|
//Buffer(73) [1, 3, 68, 32, 32, 82, 78, 71, 45, 67, 84, 82, 76, 45, 87, 78, 68, 51, 48, 7, 140, 0, 132, 0, 126, 0, 120, 0, 111, 0, 106, 100, 50, 0, 5, 0, 120, 0, 120, 0, 28, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 5, 0, 5, 2, 148, 0, 5, 206, 143, 34, 228, buffer: ArrayBuffer(8192), byteLength: 73, byteOffset: 6144, length: 73, Symbol(Symbol.toStringTag): 'Uint8Array']
|
|
36
36
|
super.initSchema()
|
|
37
|
+
|
|
38
|
+
this.addDefaultParam("id")
|
|
39
|
+
.default="solar"
|
|
40
|
+
|
|
37
41
|
this.addMetadatum('batteryType', '', "battery type")
|
|
38
42
|
.default="electrical.chargers.{id}.battery.type"
|
|
39
43
|
this.addMetadatum('batteryPercentage', 'ratio', "battery percentage",
|
|
@@ -118,22 +122,12 @@ class RenogyRoverClient extends RenogySensor {
|
|
|
118
122
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
return new Promise( async ( resolve, reject )=>{
|
|
123
|
-
this.sendReadFunctionRequest(0x1A, 0x1)
|
|
124
|
-
|
|
125
|
-
const valChanged = async (buffer) => {
|
|
126
|
-
resolve((buffer.readUInt8(4)))
|
|
127
|
-
}
|
|
128
|
-
this.readChar.once('valuechanged', valChanged )
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
|
|
125
|
+
|
|
132
126
|
retrieveBatteryType(){
|
|
133
127
|
return new Promise( async ( resolve, reject )=>{
|
|
134
128
|
//Buffer(7) [255, 3, 2, 0, 1, 80, 80, buffer: ArrayBuffer(8192), byteLength: 7, byteOffset: 864, length: 7, Symbol(Symbol.toStringTag): 'Uint8Array']
|
|
135
129
|
|
|
136
|
-
this.sendReadFunctionRequest(0xe004, 0x01)
|
|
130
|
+
await this.sendReadFunctionRequest(0xe004, 0x01)
|
|
137
131
|
|
|
138
132
|
const valChanged = async (buffer) => {
|
|
139
133
|
resolve(RC.BATTERY_TYPE[(buffer.readUInt8(4))])
|
|
@@ -157,13 +151,10 @@ class RenogyRoverClient extends RenogySensor {
|
|
|
157
151
|
}
|
|
158
152
|
async initGATTConnection() {
|
|
159
153
|
await super.initGATTConnection()
|
|
160
|
-
if (!this.deviceID)
|
|
161
|
-
this.deviceID = await this.retrieveDeviceID()
|
|
162
|
-
this.modelID=await this.retrieveModelID()
|
|
163
154
|
|
|
155
|
+
this.modelID=await this.retrieveModelID()
|
|
164
156
|
this.batteryType = await this.retrieveBatteryType()
|
|
165
|
-
this.emit('batteryType', this.batteryType)
|
|
166
|
-
|
|
157
|
+
this.emit('batteryType', this.batteryType)
|
|
167
158
|
|
|
168
159
|
}
|
|
169
160
|
|
|
@@ -175,7 +166,7 @@ class RenogyRoverClient extends RenogySensor {
|
|
|
175
166
|
async getAndEmitChargeInfo(){
|
|
176
167
|
return new Promise( async ( resolve, reject )=>{
|
|
177
168
|
try {
|
|
178
|
-
this.sendReadFunctionRequest(0x100, 0x22)
|
|
169
|
+
await this.sendReadFunctionRequest(0x100, 0x22)
|
|
179
170
|
|
|
180
171
|
this.readChar.once('valuechanged', buffer => {
|
|
181
172
|
this.emitValuesFrom(buffer)
|
|
@@ -26,7 +26,7 @@ class VictronBatteryMonitor extends VictronSensor{
|
|
|
26
26
|
d.debug.bind(d)
|
|
27
27
|
d.initSchema()
|
|
28
28
|
Object.keys(d.getPaths()).forEach((tag)=>{
|
|
29
|
-
d.on(tag,(v)=>console.log(`${tag}=${v}`))
|
|
29
|
+
d.on(tag,(v)=>console.log(`${tag}=${JSON.stringify(v)}`))
|
|
30
30
|
})
|
|
31
31
|
if (key)
|
|
32
32
|
b = d.decrypt(b)
|
|
@@ -62,7 +62,6 @@ class VictronBatteryMonitor extends VictronSensor{
|
|
|
62
62
|
const alarmMD = this.addMetadatum('alarm','', 'alarm',
|
|
63
63
|
(buff,offset=0)=>{return buff.readInt16LE(offset)})
|
|
64
64
|
alarmMD.default='electrical.batteries.{batteryID}.alarm'
|
|
65
|
-
alarmMD.notify=true
|
|
66
65
|
|
|
67
66
|
this.addMetadatum( 'consumed','Ah', 'amp-hours consumed',
|
|
68
67
|
(buff,offset=0)=>{return buff.readInt32LE(offset)/10},
|
|
@@ -78,8 +77,6 @@ class VictronBatteryMonitor extends VictronSensor{
|
|
|
78
77
|
.read=(buff,offset=0)=>{return this.NaNif(buff.readUInt16LE(offset),0xFFFF)*60}
|
|
79
78
|
this.getPath("ttg").gatt='65970ffe-4bda-4c1e-af4b-551c4cf74769';
|
|
80
79
|
|
|
81
|
-
this.auxMode=VC.AuxMode.STARTER_VOLTAGE
|
|
82
|
-
|
|
83
80
|
if (this.auxMode==undefined){
|
|
84
81
|
const md=await this.constructor.getDataPacket(this.device, this.getManufacturerData(this.constructor.ManufacturerID))
|
|
85
82
|
try {
|
|
@@ -55,7 +55,6 @@ class VictronDCEnergyMeter extends VictronSensor{
|
|
|
55
55
|
this.addMetadatum('alarm','', 'alarm',
|
|
56
56
|
(buff)=>{return buff.readUInt16LE(4)})
|
|
57
57
|
.default="electrical.meters.{id}.alarm"
|
|
58
|
-
this.getPath("alarm").notify=true
|
|
59
58
|
this.addMetadatum('current','A', 'current')
|
|
60
59
|
.default="electrical.meters.{id}.current"
|
|
61
60
|
|