@rhizomatics/signalk-ble-sensors-plugin 1.0.0 → 1.0.2
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/CHANGELOG.md +11 -0
- package/README.md +18 -12
- package/index.js +11 -3
- package/lib/decoders/switchbot-meter-plus.js +5 -6
- package/lib/decoders/xiaomi-atc.js +34 -22
- package/lib/path-mapper.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
# [1.0.2]
|
|
2
|
+
|
|
3
|
+
- SwitchBot Meter Plus: temperature was wrong when the meter's display was set to Fahrenheit (e.g. 22.7°C published as -5.2°C). The broadcast value is always Celsius; the flag only says which unit the display shows
|
|
4
|
+
- Xiaomi ATC: sensors using the `atc1441` advertising format were decoded with the pvvx layout and published nonsense temperatures. Both formats are now decoded, and BTHome payloads (not supported yet) are no longer misread as either
|
|
5
|
+
- **Path change:** humidity is now published to `environment.<zone>.relativeHumidity`, the SignalK spec path (`humidity` is deprecated in the spec). Update any dashboard or alarm that reads `environment.<zone>.humidity`
|
|
6
|
+
- Zone examples in the config screen and README now follow the spec's `inside.<zone>` / `outside` layout. Existing zones are unchanged
|
|
7
|
+
|
|
8
|
+
# [1.0.1]
|
|
9
|
+
|
|
10
|
+
No code change, re-release only for automated build.
|
|
11
|
+
|
|
1
12
|
# [1.0.0]
|
|
2
13
|
|
|
3
14
|
- Package moved to [Rhizomatics](https://www.rhizomatics.org.uk/sailing/)
|
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# signalk-ble-sensors-plugin
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@rhizomatics/signalk-ble-sensors-plugin)
|
|
4
|
+
[](https://www.npmjs.com/package/@rhizomatics/signalk-ble-sensors-plugin)
|
|
5
|
+
[](https://github.com)
|
|
6
|
+
[](https://github.com/rhizomatics/signalk-ble-sensors-plugin./blob/main/LICENSE)
|
|
7
|
+
[](https://boat-tech-directory.rhizomatics.org.uk)
|
|
8
|
+
|
|
3
9
|
This is a limited replacement for `bt-sensors-plugin-sk` that has SignalK BLE Manager support, with only a few sensors known to work and passive only BLE integration. It may work for other sensors not listed here, and PRs welcome to add or correct support.
|
|
4
10
|
|
|
5
11
|
Passive BLE sensor listening for [SignalK](https://signalk.org/) — reads temperature, humidity and battery data broadcast by cheap, popular BLE sensors and republishes it to SignalK paths, without ever opening a Bluetooth connection to the device. Relies only on _BLE Manager_, so requires SignalK v2.31 or above with v2.34+ recommended.
|
|
@@ -29,7 +35,7 @@ For boats that have IP68 RuuviTag Pro outside and RuuviTag inside, it can be han
|
|
|
29
35
|
| Xiaomi LYWSD03MMC (pvvx/atc1441 custom firmware) | Best-effort — not actively used, contributions welcome |
|
|
30
36
|
| Govee H5074 / H5075 | Best-effort — not actively used, contributions welcome |
|
|
31
37
|
|
|
32
|
-
Stock Xiaomi firmware (encrypted, not the ATC/pvvx custom firmware above) isn't supported. Sensors that need a GATT connection to read (Victron, Renogy, most BMS chips, etc.) are out of scope for this plugin. Many of these already have plugins, such as [signalk-bluetti-plugin](https://github.com/rhizomatics/signalk-bluetti-plugin) or [signalk-victron-ble](https://github.com/stefanor/signalk-victron-ble).
|
|
38
|
+
Stock Xiaomi firmware (encrypted, not the ATC/pvvx custom firmware above) isn't supported. On the custom firmware, choose the `custom` (pvvx) or `atc1441` advertising format; `BTHome` isn't decoded yet. Sensors that need a GATT connection to read (Victron, Renogy, most BMS chips, etc.) are out of scope for this plugin. Many of these already have plugins, such as [signalk-bluetti-plugin](https://github.com/rhizomatics/signalk-bluetti-plugin) or [signalk-victron-ble](https://github.com/stefanor/signalk-victron-ble).
|
|
33
39
|
|
|
34
40
|
## Configuring a sensor
|
|
35
41
|
|
|
@@ -37,7 +43,7 @@ Stock Xiaomi firmware (encrypted, not the ATC/pvvx custom firmware above) isn't
|
|
|
37
43
|
2. Make sure the sensor is powered on and within Bluetooth range for a minute or two.
|
|
38
44
|
3. Open the plugin's configuration page and click **+ Add Item** under "Registered sensors".
|
|
39
45
|
4. Pick the sensor from the **Device** dropdown — it lists every recognised sensor the plugin has seen, labelled with its advertised name, sensor type, and MAC address. If it's not there yet, close the page, wait a bit longer, and reopen it.
|
|
40
|
-
5. Fill in a **Zone / location**
|
|
46
|
+
5. Fill in a **Zone / location** — this becomes part of the SignalK path (`environment.<zone>.temperature`, etc.). The SignalK spec puts indoor zones under `inside`, so use e.g. `inside.mainCabin`, `inside.refrigerator` or `inside.engineRoom` (giving `environment.inside.mainCabin.temperature`), and `outside` for the outside air — and optionally a **Name override**.
|
|
41
47
|
6. Save. Readings should start appearing within a few seconds.
|
|
42
48
|
|
|
43
49
|
If a sensor measures pressure (currently RuuviTag only), it can optionally also be picked as the **Outside pressure source**
|
|
@@ -47,16 +53,16 @@ sensor still needs to be added under "Registered sensors" as usual; picking it h
|
|
|
47
53
|
|
|
48
54
|
## Published paths
|
|
49
55
|
|
|
50
|
-
| Value | Path
|
|
51
|
-
| ---------------- |
|
|
52
|
-
| Temperature | `environment.<zone>.temperature`
|
|
53
|
-
| Humidity | `environment.<zone>.
|
|
54
|
-
| Pressure | `environment.<zone>.pressure`
|
|
55
|
-
| Battery strength | `sensors.<name>.battery.strength`
|
|
56
|
-
| Battery voltage | `sensors.<name>.battery.voltage`
|
|
57
|
-
| Signal strength | `sensors.<name>.RSSI`
|
|
58
|
-
| Reachable | `sensors.<name>.reachable`
|
|
59
|
-
| Motion counter | `sensors.<name>.motionCounter`
|
|
56
|
+
| Value | Path | Unit |
|
|
57
|
+
| ---------------- | ------------------------------------- | ----------------------------------- |
|
|
58
|
+
| Temperature | `environment.<zone>.temperature` | K |
|
|
59
|
+
| Humidity | `environment.<zone>.relativeHumidity` | ratio (0-1) |
|
|
60
|
+
| Pressure | `environment.<zone>.pressure` | Pa |
|
|
61
|
+
| Battery strength | `sensors.<name>.battery.strength` | ratio (0-1), with low-battery zones |
|
|
62
|
+
| Battery voltage | `sensors.<name>.battery.voltage` | V |
|
|
63
|
+
| Signal strength | `sensors.<name>.RSSI` | dB |
|
|
64
|
+
| Reachable | `sensors.<name>.reachable` | boolean |
|
|
65
|
+
| Motion counter | `sensors.<name>.motionCounter` | count |
|
|
60
66
|
|
|
61
67
|
`<name>` is the sensor's name, lowercased and sanitized — give each registered sensor a distinct name, since
|
|
62
68
|
two sensors with the same name will collide on these paths. Not every sensor publishes every path above — only the values its
|
package/index.js
CHANGED
|
@@ -140,14 +140,22 @@ function buildSchema(recognized, configured = [], outsidePressureSourceMac = nul
|
|
|
140
140
|
type: "string",
|
|
141
141
|
title: "Name override",
|
|
142
142
|
description:
|
|
143
|
-
"Used
|
|
143
|
+
"Used in sensors.<name>.* paths (the $source is the plugin id plus the sensor type). Defaults to the device's advertised name if it has one, otherwise its sensor type (e.g. 'SwitchBot Meter') — worth setting explicitly.",
|
|
144
144
|
},
|
|
145
145
|
zone: {
|
|
146
146
|
type: "string",
|
|
147
147
|
title: "Zone / location",
|
|
148
148
|
description:
|
|
149
|
-
"Used in environment.<zone>.* paths
|
|
150
|
-
examples: [
|
|
149
|
+
"Used in environment.<zone>.* paths. The SignalK spec puts indoor zones under 'inside', e.g. 'inside.mainCabin' becomes environment.inside.mainCabin.*; use 'outside' for the outside air.",
|
|
150
|
+
examples: [
|
|
151
|
+
"outside",
|
|
152
|
+
"inside",
|
|
153
|
+
"inside.mainCabin",
|
|
154
|
+
"inside.galley",
|
|
155
|
+
"inside.refrigerator",
|
|
156
|
+
"inside.freezer",
|
|
157
|
+
"inside.engineRoom",
|
|
158
|
+
],
|
|
151
159
|
},
|
|
152
160
|
enabled: { type: "boolean", title: "Enabled", default: true },
|
|
153
161
|
},
|
|
@@ -19,15 +19,14 @@ function identify(adv) {
|
|
|
19
19
|
function decode(adv) {
|
|
20
20
|
const sd = adv.serviceData.get(SERVICE_DATA_UUID);
|
|
21
21
|
|
|
22
|
-
// Byte 4 bit 7: sign (1 = positive). Byte 4 bits 0-6: integer part.
|
|
23
|
-
// Byte 3 bits 0-3: fractional decimal digit. Byte 5 bit 7
|
|
24
|
-
// (1 = the value
|
|
22
|
+
// Byte 4 bit 7: sign (1 = positive). Byte 4 bits 0-6: integer part (degC).
|
|
23
|
+
// Byte 3 bits 0-3: fractional decimal digit. Byte 5 bit 7 is the meter's
|
|
24
|
+
// display-unit setting (1 = shows Fahrenheit); the broadcast value is always
|
|
25
|
+
// Celsius whatever the display shows, as in pySwitchbot's decode_temp_humidity.
|
|
25
26
|
const sign = (sd[4] & 0x80) !== 0 ? 1 : -1;
|
|
26
27
|
const intPart = sd[4] & 0x7f;
|
|
27
28
|
const frac = (sd[3] & 0x0f) / 10;
|
|
28
|
-
const
|
|
29
|
-
const isFahrenheit = (sd[5] & 0x80) !== 0;
|
|
30
|
-
const tempC = isFahrenheit ? (rawTemp - 32) / 1.8 : rawTemp;
|
|
29
|
+
const tempC = sign * (intPart + frac);
|
|
31
30
|
|
|
32
31
|
const humidity = (sd[5] & 0x7f) / 100;
|
|
33
32
|
const battStrength = sd[2] / 100;
|
|
@@ -3,37 +3,49 @@
|
|
|
3
3
|
// Xiaomi LYWSD03MMC / similar running the pvvx or atc1441 custom firmware
|
|
4
4
|
// (https://github.com/pvvx/ATC_MiThermometer) advertises under its own name
|
|
5
5
|
// (ATC_XXXXXX, where XXXXXX is the last 3 MAC bytes) with a fixed-format
|
|
6
|
-
// service-data payload
|
|
7
|
-
// stock Xiaomi firmware.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// 0x181A service-data payload - no encryption key or GATT connection needed,
|
|
7
|
+
// unlike stock Xiaomi firmware. Two layouts share that UUID, told apart by length:
|
|
8
|
+
//
|
|
9
|
+
// - pvvx "custom" (15 bytes, little-endian): mac[6] temp[i16, x0.01 degC]
|
|
10
|
+
// humidity[u16, x0.01 %] voltage[u16, mV] battery[u8, %] counter[u8] flags[u8]
|
|
11
|
+
// - atc1441 (13 bytes, big-endian): mac[6] temp[i16, x0.1 degC] humidity[u8, %]
|
|
12
|
+
// battery[u8, %] voltage[u16, mV] counter[u8]
|
|
13
|
+
//
|
|
14
|
+
// The firmware's BTHome option (UUID 0xFCD2) is a different, TLV-encoded format
|
|
15
|
+
// and isn't decoded here.
|
|
10
16
|
|
|
11
17
|
const NAME_PATTERN = /^ATC_[A-Fa-f0-9]{6}$/;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
for (const uuid of SERVICE_DATA_UUIDS) {
|
|
16
|
-
const sd = adv.serviceData.get(uuid);
|
|
17
|
-
if (sd) return sd;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
18
|
+
const SERVICE_DATA_UUID = "0000181a-0000-1000-8000-00805f9b34fb";
|
|
19
|
+
const PVVX_LENGTH = 15;
|
|
20
|
+
const ATC1441_LENGTH = 13;
|
|
21
21
|
|
|
22
22
|
function identify(adv) {
|
|
23
23
|
if (!NAME_PATTERN.test(adv.name)) return false;
|
|
24
|
-
const sd =
|
|
25
|
-
return !!sd && sd.length >=
|
|
24
|
+
const sd = adv.serviceData.get(SERVICE_DATA_UUID);
|
|
25
|
+
return !!sd && (sd.length === ATC1441_LENGTH || sd.length >= PVVX_LENGTH);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function
|
|
29
|
-
|
|
28
|
+
function decodePvvx(sd) {
|
|
29
|
+
return {
|
|
30
|
+
temp: 273.15 + sd.readInt16LE(6) / 100,
|
|
31
|
+
humidity: sd.readUInt16LE(8) / 10000,
|
|
32
|
+
battVoltage: sd.readUInt16LE(10) / 1000,
|
|
33
|
+
battStrength: sd.readUInt8(12) / 100,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
function decodeAtc1441(sd) {
|
|
38
|
+
return {
|
|
39
|
+
temp: 273.15 + sd.readInt16BE(6) / 10,
|
|
40
|
+
humidity: sd.readUInt8(8) / 100,
|
|
41
|
+
battStrength: sd.readUInt8(9) / 100,
|
|
42
|
+
battVoltage: sd.readUInt16BE(10) / 1000,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
function decode(adv) {
|
|
47
|
+
const sd = adv.serviceData.get(SERVICE_DATA_UUID);
|
|
48
|
+
return sd.length >= PVVX_LENGTH ? decodePvvx(sd) : decodeAtc1441(sd);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
module.exports = {
|
package/lib/path-mapper.js
CHANGED
|
@@ -12,9 +12,9 @@ const FIELD_PATHS = {
|
|
|
12
12
|
description: "Zone's current temperature",
|
|
13
13
|
},
|
|
14
14
|
humidity: {
|
|
15
|
-
path: "environment.{zone}.
|
|
15
|
+
path: "environment.{zone}.relativeHumidity",
|
|
16
16
|
unit: "ratio",
|
|
17
|
-
description: "Zone's current humidity",
|
|
17
|
+
description: "Zone's current relative humidity",
|
|
18
18
|
},
|
|
19
19
|
pressure: {
|
|
20
20
|
path: "environment.{zone}.pressure",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizomatics/signalk-ble-sensors-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Passive BLE sensor listening for SignalK (SwitchBot, RuuviTag, and others) via the SignalK BLE Manager API — no GATT connection required.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ble",
|