bt-sensors-plugin-sk 1.3.8-beta2 → 1.3.8-beta4

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/BTSensor.js CHANGED
@@ -613,6 +613,7 @@ class BTSensor extends EventEmitter {
613
613
  )
614
614
  try {
615
615
  await this.device.helper.callMethod('Connect')
616
+ await new Promise(resolve => setTimeout(resolve, 2000));
616
617
  } catch (e) {
617
618
  this.debug(e)
618
619
  throw new Error(e.message)
@@ -1184,7 +1185,7 @@ class BTSensor extends EventEmitter {
1184
1185
  path: `notifications.sensors.${this.macAndName()}`,
1185
1186
  value: {
1186
1187
  state: "warn",
1187
- message: "Unable to communicate with sensor",
1188
+ message: `Unable to communicate with BT sensor: ${this.macAndName()}` ,
1188
1189
  method: ["visual", "sound"]
1189
1190
  }
1190
1191
  }]
@@ -1219,7 +1220,7 @@ class BTSensor extends EventEmitter {
1219
1220
  path: `notifications.sensors.${this.macAndName()}`,
1220
1221
  value: {
1221
1222
  state: "warn",
1222
- message: `No contact with sensor for ${this.elapsedTimeSinceLastContact()} seconds`,
1223
+ message: `No contact with BT sensor ${this.macAndName()} for ${this.elapsedTimeSinceLastContact()} seconds`,
1223
1224
  method: ["visual", "sound"]
1224
1225
  }
1225
1226
  }]
@@ -1238,7 +1239,7 @@ class BTSensor extends EventEmitter {
1238
1239
  path: `notifications.sensors.${this.macAndName()}`,
1239
1240
  value: {
1240
1241
  state: "normal",
1241
- message: `Communication with sensor normal`,
1242
+ message: `Normal communication with BT sensor ${this.macAndName()}`,
1242
1243
  method: []
1243
1244
  }
1244
1245
  }]
package/README.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # Bluetooth Sensors for [Signal K](http://www.signalk.org)
2
2
 
3
3
  ## WHAT'S NEW
4
- # Version 1.3.8-beta2
4
+ # Version 1.3.8-beta2/3/4
5
+ - SensorPush fixes
6
+ - Error/state code and description for Victron devices per issue #124
7
+ - More descriptive notifications per issue #126
8
+ - V19 Jikong BMS support
9
+
10
+ ## New sensors
11
+
12
+ - Shelly/Ecowitt WS90 weather station
5
13
 
6
- - SensorPush fixes
7
14
 
8
15
  # Version 1.3.8-beta1
9
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bt-sensors-plugin-sk",
3
- "version": "1.3.8-beta2",
3
+ "version": "1.3.8-beta4",
4
4
  "description": "Bluetooth Sensors for Signalk - see https://www.npmjs.com/package/bt-sensors-plugin-sk#supported-sensors for a list of supported sensors",
5
5
  "main": "index.js",
6
6
  "dependencies": {
Binary file
@@ -31,8 +31,8 @@ class JikongBMS extends BTSensor {
31
31
  static validAcknowledgeHeader = 0xaa5590eb;
32
32
 
33
33
  static commandResponse = {
34
- 0x96: 0x02,
35
- 0x97: 0x03,
34
+ 0x96: [0x02],
35
+ 0x97: [0x01, 0x03],
36
36
  };
37
37
 
38
38
  static async test(datafile) {
@@ -350,57 +350,150 @@ class JikongBMS extends BTSensor {
350
350
 
351
351
  getBuffer(command) {
352
352
  return new Promise(async (resolve, reject) => {
353
- const r = await this.sendReadFunctionRequest(command);
354
- let datasize = 300;
355
- let result = Buffer.alloc(datasize);
356
- let offset = 0;
353
+ const datasize = 300;
354
+ const expectedResponses = []
355
+ .concat(this.constructor.commandResponse[command] ?? [])
356
+ .filter((frameType) => frameType !== undefined);
357
+ let stream = Buffer.alloc(0);
358
+ let timer;
359
+ let settled = false;
360
+
361
+ const removeValueChangedListener = () => {
362
+ if (typeof this.rxChar?.off === "function") {
363
+ this.rxChar.off("valuechanged", valChanged);
364
+ } else if (typeof this.rxChar?.removeListener === "function") {
365
+ this.rxChar.removeListener("valuechanged", valChanged);
366
+ }
367
+ };
357
368
 
358
- const timer = setTimeout(() => {
359
- this.rxChar.removeAllListeners();
369
+ const cleanup = () => {
370
+ removeValueChangedListener();
360
371
  clearTimeout(timer);
361
- reject(
362
- new Error(
363
- `Response timed out (+30s) getting results for command ${command} from JikongBMS device ${this.getName()}.`
364
- )
365
- );
366
- }, 30000);
372
+ };
367
373
 
368
- const valChanged = async (buffer) => {
369
- if (
370
- offset == 0 && //first packet
371
- (buffer.length < 5 ||
372
- buffer.readUInt32BE(0) !== this.constructor.validResponseHeader)
373
- ) {
374
- if (
375
- buffer.readUInt32BE(0) !== this.constructor.validAcknowledgeHeader
376
- )
374
+ const resolveBuffer = (buffer) => {
375
+ if (settled) {
376
+ return;
377
+ }
378
+ settled = true;
379
+ cleanup();
380
+ resolve(buffer);
381
+ };
382
+
383
+ const rejectBuffer = (error) => {
384
+ if (settled) {
385
+ return;
386
+ }
387
+ settled = true;
388
+ cleanup();
389
+ reject(error);
390
+ };
391
+
392
+ const findHeader = (buffer, start = 0) => {
393
+ for (let i = start; i <= buffer.length - 4; i++) {
394
+ const header = buffer.readUInt32BE(i);
395
+ if (header === this.constructor.validResponseHeader) {
396
+ return { index: i, header };
397
+ }
398
+ if (header === this.constructor.validAcknowledgeHeader) {
399
+ return { index: i, header };
400
+ }
401
+ }
402
+ return null;
403
+ };
404
+
405
+ const parseStream = () => {
406
+ while (stream.length >= 4) {
407
+ const headerMatch = findHeader(stream);
408
+
409
+ if (!headerMatch) {
410
+ // Preserve a possible partial 4-byte header between notifications.
411
+ stream = stream.slice(-3);
412
+ return;
413
+ }
414
+
415
+ if (headerMatch.index > 0) {
377
416
  this.debug(
378
- `Invalid buffer ${JSON.stringify(
379
- buffer
380
- )}from ${this.getName()}, not processing.`
417
+ `Discarding ${headerMatch.index} leading byte(s) before JK header for ${this.getName()}`
381
418
  );
382
- } else {
383
- buffer.copy(result, offset);
384
- if (offset + buffer.length == datasize) {
385
- if (result[4] == this.constructor.commandResponse[command]) {
386
- this.rxChar.removeAllListeners();
387
- clearTimeout(timer);
388
- this.debug(
389
- `Rec'd command in buffer ${JSON.stringify(
390
- result
391
- )} for ${this.getName()}`
392
- );
393
- resolve(result);
394
- } else {
395
- offset = 0;
396
- result = Buffer.alloc(datasize);
419
+ stream = stream.slice(headerMatch.index);
420
+ }
421
+
422
+ if (
423
+ stream.length >= 4 &&
424
+ stream.readUInt32BE(0) === this.constructor.validAcknowledgeHeader
425
+ ) {
426
+ // Old/new JK modules often append a short ack after the data frame.
427
+ if (stream.length <= 4) {
428
+ return;
397
429
  }
398
- } else {
399
- offset += buffer.length;
430
+ stream = stream.slice(4);
431
+ continue;
432
+ }
433
+
434
+ if (stream.length < datasize) {
435
+ return;
436
+ }
437
+
438
+ const candidate = stream.slice(0, datasize);
439
+ const computedCRC =
440
+ sumByteArray(candidate.subarray(0, datasize - 1)) & 0xff;
441
+ const remoteCRC = candidate[datasize - 1];
442
+
443
+ if (computedCRC !== remoteCRC) {
444
+ const nextHeader = findHeader(stream, 1);
445
+ const bytesToDrop = nextHeader ? nextHeader.index : 1;
446
+ this.debug(
447
+ `CRC mismatch for ${this.getName()} (${computedCRC} != ${remoteCRC}), dropping ${bytesToDrop} byte(s)`
448
+ );
449
+ stream = stream.slice(bytesToDrop);
450
+ continue;
451
+ }
452
+
453
+ if (
454
+ expectedResponses.length === 0 ||
455
+ expectedResponses.includes(candidate[4])
456
+ ) {
457
+ this.debug(
458
+ `Rec'd command in buffer ${JSON.stringify(
459
+ candidate
460
+ )} for ${this.getName()}`
461
+ );
462
+ resolveBuffer(candidate);
463
+ return;
400
464
  }
465
+
466
+ this.debug(
467
+ `Ignoring JK frame type ${candidate[4]} while waiting for ${expectedResponses.join(
468
+ ", "
469
+ )} from ${this.getName()}`
470
+ );
471
+ stream = stream.slice(datasize);
401
472
  }
402
473
  };
474
+
475
+ const valChanged = async (buffer) => {
476
+ if (settled) {
477
+ return;
478
+ }
479
+ if (!Buffer.isBuffer(buffer)) {
480
+ buffer = Buffer.from(buffer);
481
+ }
482
+ stream = Buffer.concat([stream, buffer]);
483
+ parseStream();
484
+ };
485
+
486
+ timer = setTimeout(() => {
487
+ rejectBuffer(
488
+ new Error(
489
+ `Response timed out (+30s) getting results for command ${command} from JikongBMS device ${this.getName()}.`
490
+ )
491
+ );
492
+ }, 30000);
493
+
494
+ // Listen before writing so the first JK notification chunk is not lost.
403
495
  this.rxChar.on("valuechanged", valChanged);
496
+ await this.sendReadFunctionRequest(command);
404
497
  });
405
498
  }
406
499
 
@@ -16,6 +16,7 @@ class SensorPush extends BTSensor{
16
16
  static ServiceUUID = "EF090000-11D6-42BA-93B8-9DD7EC090AB0"
17
17
  static Characteristics =
18
18
  {
19
+
19
20
  tx: "EF090003-11D6-42BA-93B8-9DD7EC090AA9",
20
21
  adv: "EF090005-11D6-42BA-93B8-9DD7EC090AA9",
21
22
  batt: "EF090007-11D6-42BA-93B8-9DD7EC090AA9",
@@ -32,16 +33,25 @@ class SensorPush extends BTSensor{
32
33
  }
33
34
 
34
35
  async emitGATT(){
36
+ await this.characteristics.temp.writeValue(Buffer.from([1,0,0,0]))
37
+
35
38
  this.emitData("temp", await this.characteristics.temp.readValue())
39
+
40
+ await this.characteristics.hum.writeValue(Buffer.from([1,0,0,0]))
36
41
  this.emitData("humidity", await this.characteristics.hum.readValue())
42
+
37
43
  this.emitData("batt", await this.characteristics.batt.readValue())
38
- this.emitData("pressure", await this.characteristics.bar.readValue())
44
+
45
+ if (this.characteristics.bar){
46
+ await this.characteristics.bar.writeValue(Buffer.from([1,0,0,0]))
47
+ this.emitData("pressure", await this.characteristics.bar.readValue())
48
+ }
39
49
  }
40
50
 
41
51
  initSchema(){
42
52
  super.initSchema()
43
53
  this.getGATTParams()["useGATT"].default=true
44
- this.getGATTParams()["pollFreq"].default=30
54
+ this.getGATTParams()["pollFreq"].default=60
45
55
  this._schema.properties.gattParams.required.push("pollFreq")
46
56
 
47
57
  this.addDefaultParam("zone")
@@ -84,7 +94,7 @@ class SensorPush extends BTSensor{
84
94
  .read=(buffer)=>{ return buffer.readUInt16LE()/1000}
85
95
 
86
96
  this.addDefaultPath("temp","environment.temperature")
87
- .read=(buffer)=>{ return buffer.readInt32LE()/100}
97
+ .read=(buffer)=>{ return 273.15+(buffer.readInt32LE()/100)}
88
98
 
89
99
  this.addDefaultPath("humidity","environment.humidity")
90
100
  .read=(buffer)=>{ return buffer.readInt32LE()/10000}
@@ -118,13 +128,20 @@ class SensorPush extends BTSensor{
118
128
  const service = await gattServer.getPrimaryService(this.constructor.ServiceUUID.toLowerCase())
119
129
  this.characteristics={}
120
130
  for (const c in this.constructor.Characteristics) {
121
- this.characteristics[c] = await service.getCharacteristic(this.constructor.Characteristics[c].toLowerCase())
131
+ const uuid = this.constructor.Characteristics[c].toLowerCase()
132
+ try{
133
+ this.characteristics[c] = await service.getCharacteristic(uuid)
134
+ } catch (e) {
135
+ this.debug(`characteristic ${c} with uuid ${uuid} not available.`)
136
+ }
122
137
  }
123
- if (this.tx)
138
+ if (this.tx && this.characteristics.tx)
124
139
  await writeInt8(this.characteristics.tx,this.tx)
125
- if (this.LED)
140
+
141
+ if (this.LED && this.characteristics.LED)
126
142
  await writeUInt8(this.characteristics.LED,this.LED)
127
- if (this.adv)
143
+
144
+ if (this.adv && this.characteristics.adv)
128
145
  await writeUInt16LE(this.characteristics.adv,Math.round((this.adv/625)*1000))
129
146
  }
130
147
  async initGATTNotifications() {
@@ -0,0 +1,231 @@
1
+ const AbstractBTHomeSensor = require("./BTHome/AbstractBTHomeSensor");
2
+
3
+ /**
4
+ * Sensor class representing the Shelly / Ecowitt WS90 weather station.
5
+ * The device publishes BTHome v2 service data on UUID 0xFCD2.
6
+ */
7
+ class ShellySBWS90CM extends AbstractBTHomeSensor {
8
+ static Domain = this.SensorDomains.environmental;
9
+ static SHORTENED_LOCAL_NAME = "SBWS-90CM";
10
+ static LOCAL_NAME = "SBWS-90CM";
11
+ static ImageFile = "shelly-ws90.jpeg";
12
+
13
+ getTextDescription() {
14
+ return `Shelly/Ecowitt WS90 weather station (BTHome v2 over BLE service UUID 0xFCD2).`;
15
+ }
16
+
17
+ getId() {
18
+ // Stable, persistent ID so SignalK can store configuration
19
+ return `ShellySBWS90CM:${this.getAddress()}`;
20
+ }
21
+
22
+ initSchema() {
23
+ super.initSchema();
24
+ this.addDefaultParam("zone", true).default = "outside";
25
+
26
+ this.addMetadatum("temperatureK", "K", "outside temperature", this.parseTemperatureK.bind(this)).default =
27
+ "environment.{zone}.temperature";
28
+
29
+ this.addMetadatum("humidityRatio", "ratio", "relative humidity", this.parseHumidityRatio.bind(this)).default =
30
+ "environment.{zone}.humidity";
31
+
32
+ this.addMetadatum("pressurePa", "Pa", "ambient pressure", this.parsePressurePa.bind(this)).default =
33
+ "environment.outside.pressure";
34
+
35
+ this.addMetadatum("illuminanceLux", "lux", "illuminance", this.parseIlluminanceLux.bind(this)).default =
36
+ "environment.{zone}.illuminance";
37
+
38
+ this.addMetadatum("uvIndex", "", "UV index", this.parseUVIndex.bind(this)).default = "environment.{zone}.uvIndex";
39
+
40
+ this.addMetadatum("isRaining", "boolean", "rain status", this.parseIsRaining.bind(this)).default =
41
+ "environment.{zone}.raining";
42
+
43
+ this.addMetadatum("precipitationMm", "mm", "precipitation", this.parsePrecipitationMm.bind(this)).default =
44
+ "environment.{zone}.precipitation";
45
+
46
+ this.addMetadatum("windSpeed", "m/s", "true wind speed", this.parseWindSpeed.bind(this)).default = "environment.wind.speedTrue";
47
+
48
+ this.addMetadatum("windGust", "m/s", "true wind gust", this.parseWindGust.bind(this)).default = "environment.wind.gust";
49
+
50
+ this.addMetadatum("windDirectionRad", "rad", "true wind direction", this.parseWindDirectionRad.bind(this)).default =
51
+ "environment.wind.directionTrue";
52
+
53
+ this.addMetadatum("batteryRatio", "ratio", "battery ratio", this.parseBatteryRatio.bind(this)).default =
54
+ "sensors.{macAndName}.battery";
55
+ }
56
+
57
+ parseBatteryRatio(decoded) {
58
+ if (typeof decoded.batteryRatio === "number") return decoded.batteryRatio;
59
+ return null;
60
+ }
61
+
62
+ parseTemperatureK(decoded) {
63
+ if (typeof decoded.temperatureK === "number") return decoded.temperatureK;
64
+ return null;
65
+ }
66
+
67
+ parseHumidityRatio(decoded) {
68
+ if (typeof decoded.humidityRatio === "number") return decoded.humidityRatio;
69
+ return null;
70
+ }
71
+
72
+ parsePressurePa(decoded) {
73
+ if (typeof decoded.pressurePa === "number") return decoded.pressurePa;
74
+ return null;
75
+ }
76
+
77
+ parseIlluminanceLux(decoded) {
78
+ if (typeof decoded.illuminanceLux === "number") return decoded.illuminanceLux;
79
+ return null;
80
+ }
81
+
82
+ parseUVIndex(decoded) {
83
+ if (typeof decoded.uvIndex === "number") return decoded.uvIndex;
84
+ return null;
85
+ }
86
+
87
+ parseIsRaining(decoded) {
88
+ if (typeof decoded.isRaining === "boolean") return decoded.isRaining;
89
+ return null;
90
+ }
91
+
92
+ parsePrecipitationMm(decoded) {
93
+ if (typeof decoded.precipitationMm === "number") return decoded.precipitationMm;
94
+ return null;
95
+ }
96
+
97
+ parseWindDirectionRad(decoded) {
98
+ if (typeof decoded.windDirectionRad === "number") return decoded.windDirectionRad;
99
+ return null;
100
+ }
101
+
102
+ parseWindSpeed(decoded) {
103
+ if (typeof decoded.windSpeed === "number") return decoded.windSpeed;
104
+ return null;
105
+ }
106
+
107
+ parseWindGust(decoded) {
108
+ if (typeof decoded.windGust === "number") return decoded.windGust;
109
+ return null;
110
+ }
111
+
112
+ readU16LE(buffer, index) {
113
+ return buffer.readUInt16LE(index);
114
+ }
115
+
116
+ readI16LE(buffer, index) {
117
+ return buffer.readInt16LE(index);
118
+ }
119
+
120
+ readU24LE(buffer, index) {
121
+ return buffer[index] | (buffer[index + 1] << 8) | (buffer[index + 2] << 16);
122
+ }
123
+
124
+ decodeWS90BTHomeData(buffer) {
125
+ if (!buffer || buffer.length < 1) return null;
126
+
127
+ const decoded = {};
128
+ const deviceInfo = buffer.readUInt8(0);
129
+ // BTHome v2 encryption flag: bit0 = 1 means encrypted payload
130
+ if ((deviceInfo & 0x01) !== 0) {
131
+ this.debug(`${this.getDisplayName()} BTHome payload is encrypted; skipping decode`);
132
+ return null;
133
+ }
134
+ let index = 1;
135
+
136
+ let windSpeedCount = 0;
137
+
138
+ while (index < buffer.length) {
139
+ const objectId = buffer[index++];
140
+ switch (objectId) {
141
+ case 0x00: // packet id uint8 (ignore)
142
+ if (index + 1 > buffer.length) return decoded;
143
+ index += 1;
144
+ break;
145
+ case 0x01: // battery % uint8 -> ratio
146
+ if (index + 1 > buffer.length) return decoded;
147
+ decoded.batteryRatio = Number.parseFloat((buffer.readUInt8(index) / 100).toFixed(2));
148
+ index += 1;
149
+ break;
150
+ case 0x45: // temperature C*0.1 int16 -> Kelvin
151
+ if (index + 2 > buffer.length) return decoded;
152
+ decoded.temperatureK = Number.parseFloat((273.15 + this.readI16LE(buffer, index) * 0.1).toFixed(2));
153
+ index += 2;
154
+ break;
155
+ case 0x2e: // humidity % uint8 -> ratio
156
+ if (index + 1 > buffer.length) return decoded;
157
+ decoded.humidityRatio = Number.parseFloat((buffer.readUInt8(index) / 100).toFixed(2));
158
+ index += 1;
159
+ break;
160
+ case 0x04: // pressure hPa*0.01 uint24 -> Pa
161
+ if (index + 3 > buffer.length) return decoded;
162
+ decoded.pressurePa = Number.parseFloat((this.readU24LE(buffer, index) * 0.01 * 100).toFixed(2));
163
+ index += 3;
164
+ break;
165
+ case 0x05: // illuminance lux*0.01 uint24 -> lux
166
+ if (index + 3 > buffer.length) return decoded;
167
+ decoded.illuminanceLux = Number.parseFloat((this.readU24LE(buffer, index) * 0.01).toFixed(2));
168
+ index += 3;
169
+ break;
170
+ case 0x46: // UV index *0.1 uint8
171
+ if (index + 1 > buffer.length) return decoded;
172
+ decoded.uvIndex = Number.parseFloat((buffer.readUInt8(index) * 0.1).toFixed(1));
173
+ index += 1;
174
+ break;
175
+ case 0x20: // rain status bool uint8
176
+ if (index + 1 > buffer.length) return decoded;
177
+ decoded.isRaining = buffer.readUInt8(index) !== 0;
178
+ index += 1;
179
+ break;
180
+ case 0x5f: // precipitation mm*0.1 uint16
181
+ if (index + 2 > buffer.length) return decoded;
182
+ decoded.precipitationMm = Number.parseFloat((this.readU16LE(buffer, index) * 0.1).toFixed(1));
183
+ index += 2;
184
+ break;
185
+ case 0x5e: // wind direction deg*0.01 uint16 -> radians
186
+ if (index + 2 > buffer.length) return decoded;
187
+ decoded.windDirectionRad = Number.parseFloat(((this.readU16LE(buffer, index) * 0.01 * Math.PI) / 180).toFixed(4));
188
+ index += 2;
189
+ break;
190
+ case 0x44: { // wind/gust speed m/s*0.01 uint16; appears twice
191
+ if (index + 2 > buffer.length) return decoded;
192
+ const value = Number.parseFloat((this.readU16LE(buffer, index) * 0.01).toFixed(2));
193
+ if (windSpeedCount === 0) decoded.windSpeed = value;
194
+ else if (windSpeedCount === 1) decoded.windGust = value;
195
+ windSpeedCount += 1;
196
+ index += 2;
197
+ break;
198
+ }
199
+ case 0x08: // optional dew point C*0.01 int16 (ignored)
200
+ if (index + 2 > buffer.length) return decoded;
201
+ index += 2;
202
+ break;
203
+ case 0x0c: // optional capacitor voltage V*0.001 uint16 (ignored)
204
+ if (index + 2 > buffer.length) return decoded;
205
+ index += 2;
206
+ break;
207
+ default:
208
+ this.debug(`${this.getDisplayName()} unsupported BTHome object 0x${objectId.toString(16)}`);
209
+ return decoded;
210
+ }
211
+ }
212
+
213
+ return decoded;
214
+ }
215
+
216
+ propertiesChanged(props) {
217
+ const raw = this.getServiceData(this.constructor.BTHOME_SERVICE_ID);
218
+ if (!raw) return;
219
+ const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
220
+ if (!this._ws90LoggedOnce) {
221
+ this._ws90LoggedOnce = true;
222
+ this.debug(`WS90 raw serviceData len=${buffer.length} hex=${buffer.toString("hex")}`);
223
+ }
224
+ const decoded = this.decodeWS90BTHomeData(buffer);
225
+ if (!decoded) return;
226
+
227
+ this.emitValuesFrom(decoded);
228
+ }
229
+ }
230
+
231
+ module.exports = ShellySBWS90CM;
@@ -83,6 +83,22 @@ const VictronIdentifier = require('./VictronIdentifier.js');
83
83
  alarm: `0x${alarm.toString(16).padStart(8,"0")}`,
84
84
  alarmstate: 'alert'})
85
85
  }
86
+
87
+ _getOperationMode(buff, offset=0){
88
+ const code = buff.readUInt8(offset)
89
+ return {
90
+ code: code,
91
+ message: VC.OperationMode.get(code)
92
+ }
93
+ }
94
+
95
+ _getChargerError(buff, offset=1){
96
+ const code = buff.readUInt8(offset)
97
+ return {
98
+ code: code,
99
+ message: VC.ChargerError.get(code)
100
+ }
101
+ }
86
102
  async init(){
87
103
  await super.init()
88
104
  this.addParameter(
@@ -29,10 +29,12 @@ class VictronACCharger extends VictronSensor{
29
29
  this.addDefaultParam("id")
30
30
 
31
31
  this.addMetadatum('state','', 'device state',
32
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
33
- .default= "electrical.chargers.{id}.state"
32
+ (buff)=>{return this._getOperationMode(buff)})
33
+ .default= "electrical.chargers.{id}.state"
34
+
34
35
  this.addMetadatum('chargerError','', 'charger error code',
35
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
36
+ (buff)=>{return this._getChargerError(buff)}
37
+ )
36
38
  .default= "electrical.chargers.{id}.error"
37
39
 
38
40
  this.addMetadatum('batt1','V', 'battery 1 voltage')
@@ -8,13 +8,13 @@ class VictronDCDCConverter extends VictronSensor{
8
8
  initSchema(){
9
9
  super.initSchema()
10
10
  this.addDefaultParam("id")
11
+ this.addMetadatum('state','', 'device state',
12
+ (buff)=>{return this._getOperationMode(buff)})
13
+ .default= "electrical.chargers.{id}.state"
11
14
 
12
- this.addMetadatum('deviceState','', 'device state',
13
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
14
- .default="electrical.chargers.{id}.state"
15
- this.addMetadatum('chargerError','', 'charger error',
16
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
17
- .default="electrical.chargers.{id}.error"
15
+ this.addMetadatum('chargerError','', 'charger error code',
16
+ (buff)=>{return this._getChargerError(buff)})
17
+ .default= "electrical.chargers.{id}.error"
18
18
 
19
19
  this.addMetadatum('inputVoltage','V', 'input voltage',
20
20
  (buff)=>{return this.NaNif(buff.readUInt16LE(2),0xFFFF)/100})
@@ -9,13 +9,13 @@ class VictronInverter extends VictronSensor{
9
9
  initSchema(){
10
10
  super.initSchema()
11
11
  this.addDefaultParam("id")
12
-
13
- this.addMetadatum('deviceState','', 'inverter device state',
14
- (buff)=>{return VC.OperationMode.get(buff.readIntU8(0))})
15
- .default="electrical.inverters.{id}.state"
12
+ this.addMetadatum('state','', 'inverter device state',
13
+ (buff)=>{return this._getOperationMode(buff)}
14
+ )
15
+ .default="electrical.inverters.{id}.state"
16
16
 
17
17
  const md = this.addMetadatum('alarmReason','', 'reason for alarm',
18
- (buff)=>{return buff.readIntU16LE(1)})
18
+ (buff)=>{return buff.readUInt16LE(1)})
19
19
  .default="electrical.inverters.{id}.alarm"
20
20
 
21
21
  this.addDefaultPath('dcVoltage','electrical.inverters.dc.voltage')
@@ -10,14 +10,16 @@ class VictronInverterRS extends VictronSensor{
10
10
 
11
11
  initSchema() {
12
12
  super.initSchema()
13
- this.addDefaultParam("id")
14
- this.addMetadatum('deviceState','', 'inverter device state',
15
- (buff)=>{return VC.OperationMode.get(buff.readIntU8(0))})
16
- .default='electrical.inverters.{id}.state'
17
-
18
- const md = this.addMetadatum('chargerError','', 'charger error',
19
- (buff)=>{return VC.ChargerError(buff.readIntU8(1))})
20
- md.default='electrical.inverters.{id}.error'
13
+ this.addDefaultParam("id")
14
+ this.addMetadatum('state','', 'device state',
15
+ (buff)=>{return this._getOperationMode(buff)}
16
+ )
17
+ .default='electrical.inverters.{id}.state'
18
+
19
+ this.addMetadatum('chargerError','', 'charger error code',
20
+ (buff)=>{return this._getChargerError(buff)}
21
+ )
22
+ .default='electrical.inverters.{id}.error'
21
23
 
22
24
  this.addMetadatum('batteryVoltage','V', 'battery voltage',
23
25
  (buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/100})
@@ -10,11 +10,11 @@ class VictronOrionXS extends VictronSensor{
10
10
  super.initSchema()
11
11
  this.addDefaultParam("id")
12
12
  this.addMetadatum('deviceState','', 'device state',
13
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
13
+ (buff)=>{return this._getOperationMode(buff)})
14
14
  .default="electrical.chargers.{id}.state"
15
-
16
- this.addMetadatum('chargerError','', 'charger error',
17
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
15
+
16
+ this.addMetadatum('chargerError','', 'charger error code',
17
+ (buff)=>{return this._getChargerError(buff)})
18
18
  .default="electrical.chargers.{id}.error"
19
19
 
20
20
  this.addMetadatum('outputVoltage','V', 'output voltage',
@@ -24,13 +24,15 @@ class VictronSmartBatteryProtect extends VictronSensor{
24
24
  initSchema(){
25
25
  super.initSchema()
26
26
  this.addDefaultParam("id")
27
+
27
28
  this.addMetadatum('deviceState','', 'device state',
28
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(1))})
29
+ (buff)=>{return this._getOperationMode(buff)})
30
+
29
31
  this.addMetadatum('outputStatus','', 'output status', //TODO
30
32
  (buff)=>{return (buff.readUInt8(2))})
31
33
 
32
- this.addMetadatum('chargerError','', 'charger error',
33
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(3))})
34
+ this.addMetadatum('chargerError','', 'charger error',
35
+ (buff)=>{return this._getChargerError(buff,3)})
34
36
  this.addMetadatum('alarmReason','', 'alarm reason',
35
37
  (buff)=>{return buff.readUInt16LE(4)})
36
38
  this.addMetadatum('warningReason','', 'warning reason', //TODO
@@ -9,11 +9,17 @@ class VictronSolarCharger extends VictronSensor{
9
9
  this.addDefaultParam("id")
10
10
 
11
11
  this.addMetadatum('chargeState','', 'charge state',
12
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
12
+ (buff)=>{
13
+ const code = buff.readUInt8(0)
14
+ return {
15
+ code: code,
16
+ message: VC.OperationMode.get(code)
17
+ }
18
+ })
13
19
  .default="electrical.solar.{id}.state"
14
20
 
15
21
  this.addMetadatum('chargerError','', 'charger error',
16
- (buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
22
+ (buff)=>{return this._getChargerError(buff)})
17
23
  .default="electrical.solar.{id}.error"
18
24
 
19
25
  this.addMetadatum('voltage','V', 'charger battery voltage',
@@ -9,7 +9,7 @@ class VictronVEBus extends VictronSensor{
9
9
  initSchema(){
10
10
  super.initSchema()
11
11
  this.addMetadatum('chargeState','', 'charge state',
12
- (buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
12
+ (buff)=>{ return this._getOperationMode(buff)})
13
13
 
14
14
  this.addMetadatum('veBusError','', 'VE bus error',
15
15
  (buff)=>{return buff.readUInt8(1)}) //TODO