bt-sensors-plugin-sk 1.0.2 → 1.1.0-beta.1
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 +337 -40
- package/README.md +3 -3
- package/bt_co.json +14520 -0
- package/index.js +385 -177
- package/package.json +4 -3
- package/sensor_classes/ATC.js +31 -22
- package/sensor_classes/BlackListedDevice.js +20 -0
- package/sensor_classes/Inkbird.js +51 -0
- package/sensor_classes/RuuviTag.js +121 -0
- package/sensor_classes/UNKNOWN.js +12 -0
- package/sensor_classes/Victron/VictronConstants.js +328 -0
- package/sensor_classes/Victron/VictronSensor.js +106 -0
- package/sensor_classes/VictronACCharger.js +64 -0
- package/sensor_classes/VictronBatteryMonitor.js +160 -0
- package/sensor_classes/VictronDCDCConverter.js +26 -0
- package/sensor_classes/VictronDCEnergyMeter.js +68 -0
- package/sensor_classes/VictronGXDevice.js +43 -0
- package/sensor_classes/VictronInverter.js +44 -0
- package/sensor_classes/VictronInverterRS.js +44 -0
- package/sensor_classes/VictronLynxSmartBMS.js +50 -0
- package/sensor_classes/VictronOrionXS.js +31 -0
- package/sensor_classes/VictronSmartBatteryProtect.js +49 -0
- package/sensor_classes/VictronSmartLithium.js +66 -0
- package/sensor_classes/VictronSolarCharger.js +29 -0
- package/sensor_classes/VictronVEBus.js +47 -0
- package/sensor_classes/XiaomiMiBeacon.js +232 -0
- package/test.js +32 -0
- package/sensor_classes/LYWSD03MMC.js +0 -39
- package/sensor_classes/SmartShunt.js +0 -300
- package/sensor_classes/SmartShunt_GATT.js +0 -64
- package/sensor_classes/TPS.js +0 -49
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Smart Battery Protect
|
|
3
|
+
Start
|
|
4
|
+
bit
|
|
5
|
+
Nr of
|
|
6
|
+
bits Meaning Units Range NA
|
|
7
|
+
value Remark
|
|
8
|
+
8 8 Device state 0 .. 0xFE 0xFF VE_REG_DEVICE_STATE
|
|
9
|
+
16 8 Output state 0 .. 0xFE 0xFF VE_REG_DC_OUTPUT_STATUS
|
|
10
|
+
24 8 Error code 0 .. 0xFE 0xFF VE_REG_CHR_ERROR_CODE
|
|
11
|
+
32 16 Alarm reason 0 .. 0xFFFF - VE_REG_ALARM_REASON
|
|
12
|
+
48 16 Warning reason 0 .. 0xFFFF - VE_REG_WARNING_REASON
|
|
13
|
+
64 16 Input voltage 0.01 V 327.68 .. 327.66 V 0x7FFF VE_REG_DC_CHANNEL1_VOLTAGE
|
|
14
|
+
80 16 Output voltage 0.01 V 0 .. 655.34 V 0xFFFF VE_REG_DC_OUTPUT_VOLTAGE
|
|
15
|
+
96 32 Off reason 0 .. 0xFFFFFFFF - VE_REG_DEVICE_OFF_REASON_2
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const VictronSensor = require ("./Victron/VictronSensor.js")
|
|
19
|
+
const VC = require("./Victron/VictronConstants.js")
|
|
20
|
+
class VictronSmartBatteryProtect extends VictronSensor{
|
|
21
|
+
|
|
22
|
+
static async identify(device){
|
|
23
|
+
return await this.identifyMode(device, 0x09)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static {
|
|
27
|
+
this.metadata = new Map(super.getMetadata())
|
|
28
|
+
|
|
29
|
+
this.addMetadatum('deviceState','', 'device state',
|
|
30
|
+
(buff)=>{return VC.OperationMode.get(buff.readUInt8(1))})
|
|
31
|
+
this.addMetadatum('outputStatus','', 'output status', //TODO
|
|
32
|
+
(buff)=>{return (buff.readUInt8(2))})
|
|
33
|
+
|
|
34
|
+
this.addMetadatum('chargerError','', 'charger error',
|
|
35
|
+
(buff)=>{return VC.ChargerError.get(buff.readUInt8(3))})
|
|
36
|
+
this.addMetadatum('alarmReason','', 'alarm reason',
|
|
37
|
+
(buff)=>{return VC.AlarmReason.get(buff.readUInt16LE(4))})
|
|
38
|
+
this.addMetadatum('warningReason','', 'warning reason', //TODO
|
|
39
|
+
(buff)=>{return (buff.readUInt16LE(5))})
|
|
40
|
+
this.addMetadatum('channel1Voltage','V', 'channel one voltage',
|
|
41
|
+
(buff)=>{return this.NaNif(buff.readInt16LE(7),0x7FFF)/100})
|
|
42
|
+
this.addMetadatum('outputVoltage','V', 'output voltage',
|
|
43
|
+
(buff)=>{return this.NaNif(buff.readUInt16LE(9),0xFFFF)/100})
|
|
44
|
+
this.addMetadatum('offReason','', 'off reason',
|
|
45
|
+
(buff)=>{return VC.OffReasons.get(buff.readUInt16LE(11))}) //TODO
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
module.exports=VictronSmartBatteryProtect
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
SmartLithium (0x05)
|
|
3
|
+
Start Bit Nr of Bits Meaning Units Range NA Value Remark
|
|
4
|
+
32 32 BMS flags 0..0xFFFFFFFF VE_REG_BMS_FLAGs
|
|
5
|
+
64 16 SmartLithium error 0..0xFFFF VE_REG_SMART_LITHIUM_ERROR_FLAGS
|
|
6
|
+
80 7 Cell 1 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
7
|
+
87 7 Cell 2 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
8
|
+
94 7 Cell 3 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
9
|
+
101 7 Cell 4 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
10
|
+
108 7 Cell 5 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
11
|
+
115 7 Cell 6 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
12
|
+
122 7 Cell 7 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
13
|
+
129 7 Cell 8 0.01V 2.60..3.86 V 0x7F VE_REG_BATTERY_CELL_VOLTAGE*
|
|
14
|
+
136 12 Battery voltage 0.01V 0..40.94 V 0x0FFF VE_REG_DC_CHANNEL1_VOLTAGE
|
|
15
|
+
148 4 Balancer status 0..15 0x0F VE_REG_BALANCER_STATUS
|
|
16
|
+
152 7 Battery temperature 1°C -40..86 °C 0x7F VE_REG_BAT_TEMPERATURE Temperature = Record value - 40
|
|
17
|
+
159 1 Unused
|
|
18
|
+
VE_REG_BATTERY_CELL_VOLTAGE 0x00 ( 0) when cell voltage < 2.61V 0x01 ( 1) when cell voltage == 2.61V 0x7D (125) when cell voltage == 3.85V 0x7E (126) when cell voltage > 3.85 0x7F (127) when cell voltage is not available / unknown
|
|
19
|
+
*/const VictronSensor = require ("./Victron/VictronSensor.js")
|
|
20
|
+
const VC = require("./Victron/VictronConstants.js")
|
|
21
|
+
|
|
22
|
+
class VictronSmartLithium extends VictronSensor{
|
|
23
|
+
|
|
24
|
+
static async identify(device){
|
|
25
|
+
return await this.identifyMode(device, 0x05)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static {
|
|
29
|
+
|
|
30
|
+
function _toCellVoltage(val){
|
|
31
|
+
return val==0x7F?NaN:2.6+(val/100)
|
|
32
|
+
}
|
|
33
|
+
this.metadata = new Map(super.getMetadata())
|
|
34
|
+
|
|
35
|
+
this.addMetadatum('bmsFlags','', 'BMS Flags',
|
|
36
|
+
(buff)=>{return buff.readUInt32BE(0)})
|
|
37
|
+
/
|
|
38
|
+
this.addMetadatum('smartLithiumErrors','', 'Smart Lithium Errors Flags',
|
|
39
|
+
(buff)=>{return buff.readUInt16BE(3)})
|
|
40
|
+
this.addMetadatum('cell1Voltage','V', 'cell #1 voltage',
|
|
41
|
+
(buff)=>{return _toCellVoltage((buff.readUInt8(5))>>1)})
|
|
42
|
+
this.addMetadatum('cell2Voltage','V', 'cell #2 voltage',
|
|
43
|
+
(buff)=>{return _toCellVoltage((buff.subarray(5,7).readUInt16BE())&0x01fe>>2)})
|
|
44
|
+
this.addMetadatum('cell3Voltage','V', 'cell #3 voltage',
|
|
45
|
+
(buff)=>{return _toCellVoltage(((buff.subarray(6,8).readUInt16BE())&0x03fe)>>3)})
|
|
46
|
+
this.addMetadatum('cell4Voltage','V', 'cell #4 voltage',
|
|
47
|
+
(buff)=>{return _toCellVoltage(((buff.subarray(7,9).readUInt16BE())&0x07fe)>>4)})
|
|
48
|
+
this.addMetadatum('cell5Voltage','V', 'cell #5 voltage',
|
|
49
|
+
(buff)=>{return _toCellVoltage(((buff.subarray(8,10).readUInt16BE())&0x0ffe)>>5)})
|
|
50
|
+
this.addMetadatum('cell6Voltage','V', 'cell #6 voltage',
|
|
51
|
+
(buff)=>{return _toCellVoltage(((buff.subarray(9,11).readUInt16BE())&0x1ffe)>>6)})
|
|
52
|
+
this.addMetadatum('cell7Voltage','V', 'cell #7 voltage',
|
|
53
|
+
(buff)=>{return _toCellVoltage(((buff.subarray(10,12).readUInt16BE())&0x3ffe)>>7)})
|
|
54
|
+
this.addMetadatum('cell8Voltage','V', 'cell #8 voltage',
|
|
55
|
+
(buff)=>{return _toCellVoltage((buff.readUInt8(11))&0x7f)})
|
|
56
|
+
this.addMetadatum('batteryVoltage','V', 'battery voltage',
|
|
57
|
+
(buff)=>{return this.NaNif((buff.readUInt16BE(14)>>4),0xFFF)/100})
|
|
58
|
+
this.addMetadatum('balancerStatus','', 'balancer status', //TODO
|
|
59
|
+
(buff)=>{return this.NaNif((buff.readUInt16BE(14)&0xf),0xF)})
|
|
60
|
+
this.addMetadatum('batteryTemp','K', 'battery temperature',
|
|
61
|
+
(buff)=>{return this.NaNif((buff.readInt8(15)>>1),0x7F)+233.15})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
module.exports=VictronSmartLithium
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const VictronSensor = require ("./Victron/VictronSensor.js")
|
|
2
|
+
const VC = require("./Victron/VictronConstants.js")
|
|
3
|
+
class VictronSolarCharger extends VictronSensor{
|
|
4
|
+
|
|
5
|
+
static async identify(device){
|
|
6
|
+
return await this.identifyMode(device, 0x01)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static {
|
|
10
|
+
this.metadata = new Map(super.getMetadata())
|
|
11
|
+
|
|
12
|
+
this.addMetadatum('chargeState','', 'charge state',
|
|
13
|
+
(buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
|
|
14
|
+
this.addMetadatum('chargerError','', 'charger error',
|
|
15
|
+
(buff)=>{return VC.ChargerError.get(buff.readUInt8(1))})
|
|
16
|
+
this.addMetadatum('voltage','V', 'charger battery voltage',
|
|
17
|
+
(buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/100})
|
|
18
|
+
this.addMetadatum('current','A','charger battery current',
|
|
19
|
+
(buff)=>{return this.NaNif(buff.readInt16LE(4),0x7FFF)/10})
|
|
20
|
+
this.addMetadatum('yield','Wh', 'yield today in Watt-hours',
|
|
21
|
+
(buff)=>{return this.NaNif(buff.readUInt16LE(6),0xFFFF)*10})
|
|
22
|
+
this.addMetadatum('solarPower','W', 'solar power',
|
|
23
|
+
(buff)=>{return this.NaNif(buff.readUInt16LE(8),0xFFFF)})
|
|
24
|
+
this.addMetadatum('externalDeviceLoad','A', 'external device load',
|
|
25
|
+
(buff)=>{return this.NaNif(buff.readUInt16BE(10)>>7,0x1FF)})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
module.exports=VictronSolarCharger
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const VictronSensor = require ("./Victron/VictronSensor.js")
|
|
2
|
+
const VC = require("./Victron/VictronConstants.js")
|
|
3
|
+
const int24 = require('int24')
|
|
4
|
+
AC_IN_STATE=["AC in 1","AC in 2","NOT CONNECTED", "NA"]
|
|
5
|
+
ALARM_STATE=["None","warning", "alarm","NA"]
|
|
6
|
+
class VictronVEBus extends VictronSensor{
|
|
7
|
+
|
|
8
|
+
static async identify(device){
|
|
9
|
+
return await this.identifyMode(device, 0x0C)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static {
|
|
13
|
+
this.metadata = new Map(super.getMetadata())
|
|
14
|
+
|
|
15
|
+
this.addMetadatum('chargeState','', 'charge state',
|
|
16
|
+
(buff)=>{return VC.OperationMode.get(buff.readUInt8(0))})
|
|
17
|
+
|
|
18
|
+
this.addMetadatum('veBusError','', 'VE bus error',
|
|
19
|
+
(buff)=>{return buff.readUInt8(1)}) //TODO
|
|
20
|
+
|
|
21
|
+
this.addMetadatum('current','A','charger battery current',
|
|
22
|
+
(buff)=>{return this.NaNif(buff.readInt16LE(2),0x7FFF)/10})
|
|
23
|
+
|
|
24
|
+
this.addMetadatum('voltage','V', 'charger battery voltage',
|
|
25
|
+
(buff)=>{return this.NaNif(buff.readUInt16LE(4) & 0x3FFF,0x3FFF)/100})
|
|
26
|
+
|
|
27
|
+
this.addMetadatum('acInState','', 'AC in state',
|
|
28
|
+
(buff)=>{return AC_IN_STATE[readUInt8(5)>>6]})
|
|
29
|
+
|
|
30
|
+
this.addMetadatum('acInPower','W','AC power IN in watts',
|
|
31
|
+
(buff)=>{return this.NaNif(int24.readInt24LE(buff,6)>>5,0x3FFFF)*(raw & 0x40000)?-1:1})
|
|
32
|
+
|
|
33
|
+
this.addMetadatum('acOutPower','W','AC power OUT in watts',
|
|
34
|
+
(buff)=>{return this.NaNif((int24.readInt24LE(buff,8) & 0x1FFFF) >> 2,0x3FFFF) * (raw & 0b1)?-1:1})
|
|
35
|
+
|
|
36
|
+
this.addMetadatum('alarm','','alarm state 0=no alarm, 1=warning, 2=alarm',
|
|
37
|
+
(buff)=>{return ALARM_STATE[buff.readUInt8(10)&0x3] })
|
|
38
|
+
|
|
39
|
+
this.addMetadatum('batteryTemperature','K', 'battery temperature',
|
|
40
|
+
(buff)=>{return this.NaNif((buff.readInt8(10)&0x1FF)>>1,0x7F) +233.15})
|
|
41
|
+
|
|
42
|
+
this.addMetadatum('soc', 'ratio', 'state of charge',
|
|
43
|
+
(buff)=>{return this.NaNif((buff.readInt16BE(10)&0x1FF)>>2,0x7F)/100})
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
module.exports=VictronVEBus
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
const { throws } = require("assert");
|
|
2
|
+
const BTSensor = require("../BTSensor");
|
|
3
|
+
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const { isGeneratorFunction } = require("util/types");
|
|
6
|
+
|
|
7
|
+
const DEVICE_TYPES = new Map([
|
|
8
|
+
[0x0C3C, { name: "Alarm Clock", model: "CGC1" }],
|
|
9
|
+
[0x0576, { name: "3-in-1 Alarm Clock", model: "CGD1" }],
|
|
10
|
+
[0x066F, { name: "Temperature/Humidity Sensor", model: "CGDK2" }],
|
|
11
|
+
[0x0347, { name: "Temperature/Humidity Sensor", model: "CGG1" }],
|
|
12
|
+
[0x0B48, { name: "Temperature/Humidity Sensor", model: "CGG1-ENCRYPTED" }],
|
|
13
|
+
[0x03D6, { name: "Door/Window Sensor", model: "CGH1" }],
|
|
14
|
+
[0x0A83, { name: "Motion/Light Sensor", model: "CGPR1" }],
|
|
15
|
+
[0x03BC, { name: "Grow Care Garden", model: "GCLS002" }],
|
|
16
|
+
[0x0098, { name: "Plant Sensor", model: "HHCCJCY01" }],
|
|
17
|
+
[0x015D, { name: "Smart Flower Pot", model: "HHCCPOT002" }],
|
|
18
|
+
[0x02DF, { name: "Formaldehyde Sensor", model: "JQJCY01YM" }],
|
|
19
|
+
[0x0997, { name: "Smoke Detector", model: "JTYJGD03MI" }],
|
|
20
|
+
[0x1568, { name: "Switch (single button)", model: "K9B-1BTN" }],
|
|
21
|
+
[0x1569, { name: "Switch (double button)", model: "K9B-2BTN" }],
|
|
22
|
+
[0x0DFD, { name: "Switch (triple button)", model: "K9B-3BTN" }],
|
|
23
|
+
[0x1C10, { name: "Switch (single button)", model: "K9BB-1BTN" }],
|
|
24
|
+
[0x1889, { name: "Door/Window Sensor", model: "MS1BB(MI)" }],
|
|
25
|
+
[0x2AEB, { name: "Motion Sensor", model: "HS1BB(MI)" }],
|
|
26
|
+
[0x3F0F, { name: "Flood and Rain Sensor", model: "RS1BB(MI)" }],
|
|
27
|
+
[0x01AA, { name: "Temperature/Humidity Sensor", model: "LYWSDCGQ" }],
|
|
28
|
+
[0x045B, { name: "Temperature/Humidity Sensor", model: "LYWSD02" }],
|
|
29
|
+
[0x16E4, { name: "Temperature/Humidity Sensor", model: "LYWSD02MMC" }],
|
|
30
|
+
[0x2542, { name: "Temperature/Humidity Sensor", model: "LYWSD02MMC" }],
|
|
31
|
+
[0x055B, { name: "Temperature/Humidity Sensor", model: "LYWSD03MMC" }],
|
|
32
|
+
[0x2832, { name: "Temperature/Humidity Sensor", model: "MJWSD05MMC" }],
|
|
33
|
+
[0x098B, { name: "Door/Window Sensor", model: "MCCGQ02HL" }],
|
|
34
|
+
[0x06D3, { name: "Alarm Clock", model: "MHO-C303" }],
|
|
35
|
+
[0x0387, { name: "Temperature/Humidity Sensor", model: "MHO-C401" }],
|
|
36
|
+
[0x07F6, { name: "Nightlight", model: "MJYD02YL" }],
|
|
37
|
+
[0x04E9, { name: "Door Lock", model: "MJZNMSQ01YD" }],
|
|
38
|
+
[0x00DB, { name: "Baby Thermometer", model: "MMC-T201-1" }],
|
|
39
|
+
[0x0391, { name: "Body Thermometer", model: "MMC-W505" }],
|
|
40
|
+
[0x03DD, { name: "Nightlight", model: "MUE4094RT" }],
|
|
41
|
+
[0x0489, { name: "Smart Toothbrush", model: "M1S-T500" }],
|
|
42
|
+
[0x0806, { name: "Smart Toothbrush", model: "T700" }],
|
|
43
|
+
[0x1790, { name: "Smart Toothbrush", model: "T700" }],
|
|
44
|
+
[0x0A8D, { name: "Motion Sensor", model: "RTCGQ02LM" }],
|
|
45
|
+
[0x3531, { name: "Motion Sensor", model: "XMPIRO2SXS" }],
|
|
46
|
+
[0x4C60, { name: "Motion Sensor", model: "XMPIRO2GSXS" }],
|
|
47
|
+
[0x4683, { name: "Occupancy Sensor", model: "XMOSB01XS" }],
|
|
48
|
+
[0x0863, { name: "Flood Detector", model: "SJWS01LM" }],
|
|
49
|
+
[0x045C, { name: "Smart Kettle", model: "V-SK152" }],
|
|
50
|
+
[0x040A, { name: "Mosquito Repellent", model: "WX08ZM" }],
|
|
51
|
+
[0x04E1, { name: "Magic Cube", model: "XMMF01JQD" }],
|
|
52
|
+
[0x1203, { name: "Thermometer", model: "XMWSDJ04MMC" }],
|
|
53
|
+
[0x1949, { name: "Switch (double button)", model: "XMWXKG01YL" }],
|
|
54
|
+
[0x2387, { name: "Button", model: "XMWXKG01LM" }],
|
|
55
|
+
[0x098C, { name: "Door Lock", model: "XMZNMST02YD" }],
|
|
56
|
+
[0x0784, { name: "Door Lock", model: "XMZNMS04LM" }],
|
|
57
|
+
[0x0E39, { name: "Door Lock", model: "XMZNMS08LM" }],
|
|
58
|
+
[0x07BF, { name: "Wireless Switch", model: "YLAI003" }],
|
|
59
|
+
[0x38BB, { name: "Wireless Switch", model: "PTX_YK1_QMIMB" }],
|
|
60
|
+
[0x0153, { name: "Remote Control", model: "YLYK01YL" }],
|
|
61
|
+
[0x068E, { name: "Fan Remote Control", model: "YLYK01YL-FANCL" }],
|
|
62
|
+
[0x04E6, { name: "Ventilator Fan Remote Control", model: "YLYK01YL-VENFAN" }],
|
|
63
|
+
[0x03BF, { name: "Bathroom Heater Remote", model: "YLYB01YL-BHFRC" }],
|
|
64
|
+
[0x03B6, { name: "Dimmer Switch", model: "YLKG07YL/YLKG08YL" }],
|
|
65
|
+
[0x0083, { name: "Smart Kettle", model: "YM-K1501" }],
|
|
66
|
+
[0x0113, { name: "Smart Kettle", model: "YM-K1501EU" }],
|
|
67
|
+
[0x069E, { name: "Door Lock", model: "ZNMS16LM" }],
|
|
68
|
+
[0x069F, { name: "Door Lock", model: "ZNMS17LM" }],
|
|
69
|
+
[0x0380, { name: "Door Lock", model: "DSL-C08" }],
|
|
70
|
+
[0x11C2, { name: "Door Lock", model: "Lockin-SV40" }],
|
|
71
|
+
[0x0DE7, { name: "Odor Eliminator", model: "SU001-T" }]
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
class XiaomiMiBeacon extends BTSensor{
|
|
75
|
+
|
|
76
|
+
constructor(device, config, gattConfig){
|
|
77
|
+
super(device, config, gattConfig)
|
|
78
|
+
this.encryptionKey = config?.encryptionKey
|
|
79
|
+
}
|
|
80
|
+
static SERVICE_MIBEACON = "0000fe95-0000-1000-8000-00805f9b34fb"
|
|
81
|
+
|
|
82
|
+
static async identify(device){
|
|
83
|
+
try{
|
|
84
|
+
const sd = await this.getDeviceProp(device, 'ServiceData')
|
|
85
|
+
if (sd == null || sd.length==0) return null
|
|
86
|
+
const keys = Object.keys(sd)
|
|
87
|
+
if (keys[0]==this.SERVICE_MIBEACON) return this
|
|
88
|
+
} catch (e){
|
|
89
|
+
console.log(e)
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
static {
|
|
95
|
+
this.metadata = new Map(super.getMetadata())
|
|
96
|
+
//3985f4ebc032f276cc316f1f6ecea085
|
|
97
|
+
//8a1dadfa832fef54e9c1d190
|
|
98
|
+
|
|
99
|
+
const md = this.addMetadatum("encryptionKey", "", "encryptionKey (AKA bindKey) for decryption")
|
|
100
|
+
md.isParam=true
|
|
101
|
+
this.addMetadatum('temp','K', 'temperature',
|
|
102
|
+
(buff,offset)=>{return ((buff.readInt16LE(offset))/100) + 273.1})
|
|
103
|
+
this.addMetadatum('humidity','ratio', 'humidity',
|
|
104
|
+
(buff,offset)=>{return ((buff.readUInt8(offset))/100)})
|
|
105
|
+
this.addMetadatum('voltage', 'V', 'sensor battery voltage',
|
|
106
|
+
(buff,offset)=>{return ((buff.readUInt16LE(offset))/1000)})
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
GATTwarning = "WARNING: Xiaomi GATT connections are known to be unreliable on Debian distributions with Bluez 5.55 and up (earlier BlueZ versions are untested). Using GATT on the Xiaomi may put the system Bluetooth stack into an inconsistent state disrupting and disabling other plugin Bluetooth connections. If by some chance you're successful using GATT with the Xiaomi, let the plugin developer know your configuration. Refer to the plugin documentation for getting the Xiamoi bindKey for non-GATT connections and more information on Xiaomi GATT issues."
|
|
110
|
+
|
|
111
|
+
emitValues(buffer){
|
|
112
|
+
this.emitData("temp", buffer, 0)
|
|
113
|
+
this.emitData("humidity", buffer,2)
|
|
114
|
+
this.emitData("voltage",buffer,3);
|
|
115
|
+
}
|
|
116
|
+
getManufacturer(){
|
|
117
|
+
return "Xiaomi Inc."
|
|
118
|
+
}
|
|
119
|
+
getGATTDescription() {
|
|
120
|
+
return this.GATTwarning
|
|
121
|
+
}
|
|
122
|
+
initGATT(){
|
|
123
|
+
this.debug(this.GATTwarning.toUpperCase())
|
|
124
|
+
return new Promise((resolve,reject )=>{
|
|
125
|
+
this.device.connect().then(async ()=>{
|
|
126
|
+
if (!this.gattServer) {
|
|
127
|
+
this.gattServer = await this.device.gatt()
|
|
128
|
+
this.gattService = await this.gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
|
|
129
|
+
this.gattCharacteristic = await this.gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")
|
|
130
|
+
}
|
|
131
|
+
resolve(this)
|
|
132
|
+
})
|
|
133
|
+
.catch((e)=>{
|
|
134
|
+
reject(e.message)
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
emitGATT(){
|
|
140
|
+
this.gattCharacteristic.readValue()
|
|
141
|
+
.then((buffer)=>this.emitValues(buffer))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async initGATTNotifications() {
|
|
145
|
+
Promise.resolve(this.gattCharacteristic.startNotifications().then(()=>{
|
|
146
|
+
this.gattCharacteristic.on('valuechanged', buffer => {
|
|
147
|
+
this.emitValues(buffer)
|
|
148
|
+
})
|
|
149
|
+
}))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
decryptV2and3(data){
|
|
153
|
+
const encryptedPayload = data.subarray(-4);
|
|
154
|
+
const xiaomi_mac = data.subarray(5,11)
|
|
155
|
+
const nonce = Buffer.concat([data.subarray(0, 5), data.subarray(-4,-1), xiaomi_mac.subarray(-1)]);
|
|
156
|
+
const cipher = crypto.createDecipheriv('aes-128-ccm', Buffer.from(this.encryptionKey,"hex"), nonce, { authTagLength: 4});
|
|
157
|
+
cipher.setAAD(Buffer.from('11', 'hex'), { plaintextLength: encryptedPayload.length });
|
|
158
|
+
|
|
159
|
+
return cipher.update(encryptedPayload)
|
|
160
|
+
}
|
|
161
|
+
decryptV4and5(data){
|
|
162
|
+
const encryptedPayload = data.subarray(11,-7);
|
|
163
|
+
const xiaomi_mac = data.subarray(5,11)
|
|
164
|
+
const nonce = Buffer.concat([xiaomi_mac, data.subarray(2, 5), data.subarray(-7,-4)]);
|
|
165
|
+
const cipher = crypto.createDecipheriv('aes-128-ccm', Buffer.from(this.encryptionKey,"hex"), nonce, { authTagLength: 4});
|
|
166
|
+
cipher.setAAD(Buffer.from('11', 'hex'), { plaintextLength: encryptedPayload.length });
|
|
167
|
+
cipher.setAuthTag(data.subarray(-4))
|
|
168
|
+
return cipher.update(encryptedPayload)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
canUseGATT(){
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
propertiesChanged(props){
|
|
176
|
+
super.propertiesChanged(props)
|
|
177
|
+
if (this.usingGATT()) return
|
|
178
|
+
const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
|
|
179
|
+
var dec
|
|
180
|
+
if (this.encryptionVersion >= 4) {
|
|
181
|
+
dec = this.decryptV4and5(data)
|
|
182
|
+
} else {
|
|
183
|
+
if(this.encryptionVersion>=2){
|
|
184
|
+
dec=this.decryptV2and3(data)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (dec.length==0)
|
|
188
|
+
throw new Error(`${this.getNameAndAddress()} received empty decrypted packet. Check that the bind/encryption key in config is correct.`)
|
|
189
|
+
|
|
190
|
+
switch(dec[0]){
|
|
191
|
+
case 0x04:
|
|
192
|
+
this.emit("temp",(dec.readInt16LE(3)/10)+273.15)
|
|
193
|
+
break
|
|
194
|
+
case 0x06:
|
|
195
|
+
this.emit("humidity",(dec.readInt16LE(3)/10))
|
|
196
|
+
break
|
|
197
|
+
default:
|
|
198
|
+
throw new Error(`${this.getNameAndAddress()} unable to parse decrypted service data (${dec})`)
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async init(){
|
|
204
|
+
await super.init()
|
|
205
|
+
const data = this.getServiceData(this.constructor.SERVICE_MIBEACON)
|
|
206
|
+
const frameControl = data[0] + (data[1] << 8)
|
|
207
|
+
this.deviceID = data[2] + (data[3] << 8)
|
|
208
|
+
this.isEncrypted = (frameControl >> 3) & 1
|
|
209
|
+
this.encryptionVersion = frameControl >> 12
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getName(){
|
|
213
|
+
const dt = DEVICE_TYPES.get(this.deviceID)
|
|
214
|
+
return this?.name??`${dt.name} ${dt.model}`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async disconnectGattCharacteristic(){
|
|
218
|
+
if (this.gattCharacteristic && await this.gattCharacteristic.isNotifying()) {
|
|
219
|
+
await this.gattCharacteristic.stopNotifications()
|
|
220
|
+
this.gattCharacteristic=null
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async disconnect(){
|
|
224
|
+
super.disconnect()
|
|
225
|
+
await this.disconnectGattCharacteristic()
|
|
226
|
+
|
|
227
|
+
if (await this.device.isConnected()){
|
|
228
|
+
await this.device.disconnect()
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
module.exports=XiaomiMiBeacon
|
package/test.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const {createBluetooth} = require('node-ble')
|
|
2
|
+
const {bluetooth, destroy} = createBluetooth()
|
|
3
|
+
const { argv } = require('node:process');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async function main(){
|
|
7
|
+
|
|
8
|
+
const adapter = await bluetooth.defaultAdapter()
|
|
9
|
+
|
|
10
|
+
try{await adapter.startDiscovery()} catch{}
|
|
11
|
+
const device = await adapter.waitDevice(argv[2])
|
|
12
|
+
var trials=0
|
|
13
|
+
await device.connect()
|
|
14
|
+
const gatt = await device.gatt()
|
|
15
|
+
await device.disconnect()
|
|
16
|
+
|
|
17
|
+
async function search(){
|
|
18
|
+
await device.connect()
|
|
19
|
+
|
|
20
|
+
console.log(`GATT trial #${++trials}...`)
|
|
21
|
+
const s = await gatt.getPrimaryService("65970000-4bda-4c1e-af4b-551c4cf74769")
|
|
22
|
+
const c = await s.getCharacteristic('6597ffff-4bda-4c1e-af4b-551c4cf74769')
|
|
23
|
+
await c.readValue()
|
|
24
|
+
await device.disconnect()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setInterval(() => {
|
|
28
|
+
search()
|
|
29
|
+
},argv[3]);
|
|
30
|
+
search()
|
|
31
|
+
}
|
|
32
|
+
main()
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const BTSensor = require("../BTSensor");
|
|
2
|
-
|
|
3
|
-
class LYWSD03MMC extends BTSensor{
|
|
4
|
-
|
|
5
|
-
static needsScannerOn(){
|
|
6
|
-
return false
|
|
7
|
-
}
|
|
8
|
-
static metadata = new Map()
|
|
9
|
-
.set('temp',{unit:'K', description: 'temperature'})
|
|
10
|
-
.set('humidity',{unit:'ratio', description: 'humidity'})
|
|
11
|
-
.set('voltage',{unit:'V', description: 'sensor battery voltage'})
|
|
12
|
-
|
|
13
|
-
constructor(device){
|
|
14
|
-
super(device)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
emitValues(buffer){
|
|
18
|
-
this.emit("temp",((buffer.readInt16LE(0))/100) + 273.1);
|
|
19
|
-
this.emit("humidity",buffer.readUInt8(2)/100);
|
|
20
|
-
this.emit("voltage",buffer.readUInt16LE(3)/1000);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async connect() {
|
|
24
|
-
await this.device.connect()
|
|
25
|
-
var gattServer = await this.device.gatt()
|
|
26
|
-
var gattService = await gattServer.getPrimaryService("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")
|
|
27
|
-
var gattCharacteristic = await gattService.getCharacteristic("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")
|
|
28
|
-
this.emitValues(await gattCharacteristic.readValue())
|
|
29
|
-
await gattCharacteristic.startNotifications();
|
|
30
|
-
gattCharacteristic.on('valuechanged', buffer => {
|
|
31
|
-
this.emitValues(buffer)
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
async disconnect(){
|
|
35
|
-
super.disconnect()
|
|
36
|
-
await this.device.disconnect()
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
module.exports=LYWSD03MMC
|