bt-sensors-plugin-sk 1.3.7 → 1.3.8-beta10
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 +127 -714
- package/BTSensor.js +158 -37
- package/MissingAdapter.js +24 -0
- package/README.md +69 -0
- package/classLoader.js +1 -1
- package/index.js +113 -44
- package/package.json +4 -3
- package/plugin_defaults.json +5 -0
- package/public/159.js +1 -1
- package/public/218.js +1 -0
- package/public/540.js +1 -1
- package/public/598.js +3 -3
- package/public/images/ShellyFloodG4.webp +0 -0
- package/public/images/shelly-ws90.jpeg +0 -0
- package/public/main.js +1 -1
- package/public/remoteEntry.js +1 -1
- package/sensor_classes/BTHome/AbstractBTHomeSensor.js +42 -1
- package/sensor_classes/Beacon/AbstractBeaconMixin.js +6 -6
- package/sensor_classes/CyclopsSmartload.js +384 -0
- package/sensor_classes/GobiusCTankMeter.js +10 -7
- package/sensor_classes/Govee/GoveeSensor.js +1 -2
- package/sensor_classes/HumsienkBMS.js +102 -85
- package/sensor_classes/JBDBMS.js +96 -48
- package/sensor_classes/JikongBMS.js +144 -47
- package/sensor_classes/Junctek.js +1 -1
- package/sensor_classes/KilovaultHLXPlus.js +2 -2
- package/sensor_classes/MercurySmartcraft.js +1 -1
- package/sensor_classes/MopekaTankSensor.js +1 -1
- package/sensor_classes/RemoranWave3.js +4 -3
- package/sensor_classes/Renogy/RenogySensor.js +4 -0
- package/sensor_classes/RenogyBattery.js +4 -4
- package/sensor_classes/RenogyRoverClient.js +50 -50
- package/sensor_classes/RuuviTag.js +3 -3
- package/sensor_classes/SensorPush.js +55 -27
- package/sensor_classes/ShellyFloodG4.js +130 -0
- package/sensor_classes/ShellySBWS90CM.js +232 -0
- package/sensor_classes/ShenzhenLiOnBMS.js +12 -2
- package/sensor_classes/SwitchBotMeterPlus.js +5 -5
- package/sensor_classes/Victron/VictronConstants.js +19 -19
- package/sensor_classes/Victron/VictronImages.js +1 -1
- package/sensor_classes/Victron/VictronSensor.js +89 -6
- package/sensor_classes/VictronACCharger.js +6 -4
- package/sensor_classes/VictronBatteryMonitor.js +6 -3
- package/sensor_classes/VictronDCDCConverter.js +6 -6
- package/sensor_classes/VictronDCEnergyMeter.js +44 -30
- package/sensor_classes/VictronGXDevice.js +2 -2
- package/sensor_classes/VictronInverter.js +6 -8
- package/sensor_classes/VictronInverterRS.js +14 -12
- package/sensor_classes/VictronLynxSmartBMS.js +1 -1
- package/sensor_classes/VictronOrionXS.js +4 -4
- package/sensor_classes/VictronSmartBatteryProtect.js +6 -5
- package/sensor_classes/VictronSolarCharger.js +8 -2
- package/sensor_classes/VictronVEBus.js +6 -6
- package/sensor_classes/WT901BLE.js +1 -1
- package/sensor_classes/WattCycleBMS.js +370 -0
- package/sensor_classes/XiaomiMiBeacon.js +0 -1
- package/sensor_classes/wattcycle/NOTICE.md +32 -0
- package/sensor_classes/wattcycle/cli-dbus.js +238 -0
- package/sensor_classes/wattcycle/cli.js +292 -0
- package/sensor_classes/wattcycle/protocol.js +414 -0
- package/spec/contact_tracking.test.js +46 -0
- package/spec/cyclops_smartload.test.js +135 -0
- package/spec/wattcycle_protocol.test.js +239 -0
- package/src/components/BeaconRenderer.js +23 -18
- package/src/components/PluginConfigurationPanel.js +1 -2
- package/webpack.config.js +1 -9
- package/public/124.js +0 -1
package/AI-DEV.md
CHANGED
|
@@ -1,784 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Working on bt-sensors-plugin-sk with Claude Code
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This file describes how the maintainers and contributors use Claude Code on this codebase. It is opinionated about *this* codebase, not about Claude Code in general; the patterns here have been validated by real PRs (see #142, #143, #144, #145).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Two workflows are covered:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. **Adding a new sensor class.** Building support for a new Bluetooth device.
|
|
8
|
+
2. **Cross-cutting review and cleanup.** Finding latent bugs, leaks, and hygiene issues across many existing sensors at once.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Both assume Claude Code (or another agent-based AI coding tool with comparable capabilities). Some sections reference specific Claude Code features such as skills (`/simplify`), sub-agents, and agent teams.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
---
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
- Manufacturer's name and device model
|
|
15
|
-
- Bluetooth specifications (BLE services, characteristics, UUIDs)
|
|
16
|
-
- Data packet format and protocol documentation
|
|
17
|
-
- Available metrics (voltage, current, SOC, temperature, etc.)
|
|
14
|
+
## Workflow 1: adding a new sensor class
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
- Access to the physical device for testing
|
|
21
|
-
- **Android device with Bluetooth debugging enabled** (CRITICAL - see section below)
|
|
22
|
-
- Bluetooth scanning tools (`bluetoothctl`, nRF Connect app, etc.)
|
|
23
|
-
- **Vendor's mobile app** for the battery (if available)
|
|
24
|
-
- Sample data packets (advertising data, characteristic reads)
|
|
16
|
+
The plugin is a collection of sensor classes under `sensor_classes/`, each one extending `BTSensor` and handling one device family. New classes follow well-established patterns, so Claude's main job is recognizing the right pattern and copying it carefully, not designing something new.
|
|
25
17
|
|
|
26
|
-
###
|
|
27
|
-
- Service UUIDs the device advertises
|
|
28
|
-
- Characteristic UUIDs for reading data
|
|
29
|
-
- Data encoding format (byte order, scaling factors, units)
|
|
30
|
-
- Any CRC/checksum algorithms used
|
|
31
|
-
- Whether device uses advertising data or GATT connection
|
|
18
|
+
### Capture real device traffic first
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
This is the single most important step. Without real packets, you and Claude are both guessing.
|
|
34
21
|
|
|
35
|
-
|
|
22
|
+
- **Android HCI snoop log + vendor app** is the gold standard. Enable Developer Options on Android, turn on "Bluetooth HCI snoop log," use the vendor's app for 30 to 60 seconds pressing every button, then `adb pull /sdcard/Android/data/btsnoop_hci.log` and open in Wireshark. Filter on `bluetooth.addr == XX:XX:XX:XX:XX:XX`. Look for `ATT Write Request` (commands), `ATT Handle Value Notification` (push data), `ATT Read Response` (pull data), and `Advertising Data` (broadcast). Note what the vendor app displays at the moment each packet appears: that is the ground truth your decoder must match.
|
|
23
|
+
- **nRF Connect** is the fallback if there is no vendor app. Scan, connect, walk every service and characteristic, read each readable one, enable notifications on each notifiable one, screenshot hex with the matching device-display reading.
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
Keep the captures around. You will use them again to validate the decoder before touching real hardware.
|
|
38
26
|
|
|
39
|
-
|
|
40
|
-
- Reveals the exact protocol the device uses
|
|
41
|
-
- Shows actual commands and responses
|
|
42
|
-
- Eliminates guesswork about byte layouts
|
|
43
|
-
- Provides test data for validation
|
|
27
|
+
### Have Claude pick a pattern from existing classes
|
|
44
28
|
|
|
45
|
-
|
|
29
|
+
Three reference implementations cover most of the design space:
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- Go back to Settings → Developer Options
|
|
31
|
+
- `sensor_classes/VictronSmartLithium.js`: advertising data with AES-CTR encryption.
|
|
32
|
+
- `sensor_classes/JBDBMS.js`: GATT with command/response protocol.
|
|
33
|
+
- `sensor_classes/RenogyBattery.js`: GATT with Modbus-style register reads and polling.
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
- In Developer Options, enable "Bluetooth HCI snoop log"
|
|
54
|
-
- This captures ALL Bluetooth traffic to a file
|
|
35
|
+
A useful prompt: *"I'm adding a new sensor class for `<device>`. It uses `<advertising | GATT notifications | GATT polling>`. Read the three reference classes, summarize the pattern that fits best, and use it as the template."* Claude will read the actual files and report back, which is much better than improvising a structure from scratch.
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
- Install the battery manufacturer's official app
|
|
58
|
-
- Connect to your battery
|
|
59
|
-
- Navigate through all screens showing battery data
|
|
60
|
-
- Trigger all functions (refresh, settings, etc.)
|
|
61
|
-
- Let it run for 30-60 seconds
|
|
37
|
+
### Implementation order
|
|
62
38
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
adb bugreport
|
|
70
|
-
# Then extract: FS/data/misc/bluetooth/logs/btsnoop_hci.log
|
|
71
|
-
```
|
|
39
|
+
1. **Skeleton class.** Extends `BTSensor`, sets `static Domain`, `static Manufacturer`, `static Description`, `static ImageFile`. Empty `initSchema()`.
|
|
40
|
+
2. **`identify(device)`.** For advertising devices, match on manufacturer ID plus any disambiguating bytes. For GATT, match on advertised service UUID. Verify by running the plugin and confirming the device shows up under the right class.
|
|
41
|
+
3. **Schema.** In `initSchema()`, call `addDefaultPath()` for standard SignalK paths (voltage, current, SoC, temperature), `addMetadatum()` for device-specific paths, and `addDefaultParam()` for configurable parameters like `batteryID`. Each path's `.read` function takes a `Buffer` and returns the decoded value.
|
|
42
|
+
4. **Decoders.** Implement the `.read` functions using `buffer.readInt16LE`, `readUInt16BE`, etc. Match endianness to what your captured packets show.
|
|
43
|
+
5. **GATT lifecycle (if applicable).** `hasGATT()` returns true, `usingGATT()` returns the configured flag, `initGATTConnection()` discovers services and characteristics, `initGATTNotifications()` or `initGATTInterval()` sets up the data flow, `emitGATT()` parses incoming buffers and calls `emitValuesFrom(buffer)`. Implement `deactivateGATT()` for cleanup.
|
|
44
|
+
6. **Register the class.** Add to `classLoader.js` so it loads at startup.
|
|
72
45
|
|
|
73
|
-
|
|
74
|
-
- Open btsnoop_hci.log in Wireshark
|
|
75
|
-
- Filter for your device's MAC address: `bluetooth.addr == XX:XX:XX:XX:XX:XX`
|
|
76
|
-
- Look for:
|
|
77
|
-
- **ATT (Attribute Protocol) packets** - GATT read/write/notify operations
|
|
78
|
-
- **Advertising packets** - Manufacturer/Service data broadcasts
|
|
79
|
-
- **Command/response patterns** - Protocol structure
|
|
46
|
+
### Verify against captured packets before touching hardware
|
|
80
47
|
|
|
81
|
-
|
|
48
|
+
`BTSensor` provides `_test()` for feeding a captured hex packet into the decoder and printing the parsed values. Confirm the output matches what the vendor app showed at the moment the packet was captured. Mismatches almost always come down to one of:
|
|
82
49
|
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
- Note the characteristic handles (0x00XX) and UUIDs
|
|
50
|
+
- wrong endianness (off by 256x or byte-swapped),
|
|
51
|
+
- wrong sign (UInt vs Int),
|
|
52
|
+
- wrong scale factor,
|
|
53
|
+
- wrong byte offset.
|
|
88
54
|
|
|
89
|
-
|
|
90
|
-
- `Advertising Data` packets
|
|
91
|
-
- Manufacturer Data field with ID and hex bytes
|
|
92
|
-
- Service Data field with UUID and hex bytes
|
|
55
|
+
A passing `_test()` is necessary but not sufficient: it does not catch reconnect bugs, lifecycle leaks, or characteristic-discovery edge cases. Real-device verification is the only way to be sure.
|
|
93
56
|
|
|
94
|
-
|
|
57
|
+
### Run `/simplify` before opening the PR
|
|
95
58
|
|
|
96
|
-
|
|
97
|
-
Wireshark Filter: bluetooth.addr == AA:BB:CC:DD:EE:FF
|
|
98
|
-
|
|
99
|
-
Frame 142: ATT Write Request, Handle: 0x000e
|
|
100
|
-
Data: A5 5A 00 FF 01 03
|
|
101
|
-
(This is the "read battery info" command)
|
|
102
|
-
|
|
103
|
-
Frame 145: ATT Handle Value Notification, Handle: 0x000b
|
|
104
|
-
Data: A5 5A 00 01 01 0C E4 10 0E 55 19 [...]
|
|
105
|
-
(Device's response with battery data)
|
|
106
|
-
|
|
107
|
-
Vendor app displayed at this moment:
|
|
108
|
-
- Voltage: 13.2V (bytes 6-7: 0x0CE4 = 3300 * 0.01 / 2 cells)
|
|
109
|
-
- Current: 10.5A (bytes 8-9: 0x100E = 4110 * 0.01 / 4)
|
|
110
|
-
- SOC: 85% (byte 10: 0x55 = 85)
|
|
111
|
-
```
|
|
59
|
+
Before pushing, ask Claude Code to run `/simplify` on the diff. It looks for reuse opportunities (someone else already wrote a similar decoder), tightens overengineered code, and catches issues introduced during the change itself. Cheap and worth doing every time.
|
|
112
60
|
|
|
113
|
-
|
|
61
|
+
---
|
|
114
62
|
|
|
115
|
-
|
|
116
|
-
I've captured Bluetooth HCI snoop log from my [DEVICE_NAME] using the vendor's
|
|
117
|
-
Android app. Here's what Wireshark shows:
|
|
63
|
+
## Workflow 2: cross-cutting review and cleanup
|
|
118
64
|
|
|
119
|
-
|
|
120
|
-
Service UUID: 0000ffe0-0000-1000-8000-00805f9b34fb
|
|
121
|
-
Characteristic UUID: 0000ffe1-0000-1000-8000-00805f9b34fb
|
|
65
|
+
The codebase has 100+ sensor classes, mostly contributed device-by-device, so the same kinds of bugs tend to recur across many files: comma-expressions where function calls were meant, missing `await`s, `for` loops with the condition and update slots swapped, listeners that compound across reconnects. A targeted multi-agent pass catches a lot of these in one go.
|
|
122
66
|
|
|
123
|
-
|
|
124
|
-
A5 5A 00 FF 01 03
|
|
67
|
+
### The pattern (recently used for PRs #142, #143, #144, #145)
|
|
125
68
|
|
|
126
|
-
|
|
127
|
-
|
|
69
|
+
1. **Spawn an agent team with non-overlapping lenses.** Each teammate gets one bug class to look for. Useful lenses for this codebase:
|
|
70
|
+
- **Silent value-corruption.** Decoders that return wrong numbers without throwing: comma-expression bugs, missing `return` in arrow function bodies, wrong scale factors, bitwise-vs-arithmetic operator confusion (`^` instead of `**`).
|
|
71
|
+
- **Latent runtime crashes.** Bare `varName` instead of `this.varName`, missing `await`, `buffer.size` (does not exist on Node `Buffer`) instead of `buffer.length`, copy-paste typos that throw `ReferenceError` on first invocation.
|
|
72
|
+
- **Connection-lifecycle and resource leaks.** `setInterval` cleared with `clearTimeout`, listeners that compound across reconnects, command sequences that overlap on a single characteristic, retry loops with broken exit conditions.
|
|
73
|
+
- **Security and hygiene.** `eval`, `debugger`, dead code, unused imports, implicit globals, duplicate declarations.
|
|
128
74
|
|
|
129
|
-
|
|
130
|
-
- Voltage: 13.2V
|
|
131
|
-
- Current: 10.5A
|
|
132
|
-
- SOC: 85%
|
|
133
|
-
- Temperature: 25°C
|
|
75
|
+
Agent teams beat single sub-agents because teammates can challenge each other's findings via the team mailbox before reporting up to you. False positives drop dramatically. Set `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` in `~/.claude/settings.json` and spawn three teammates with distinct lenses in a single turn so they run concurrently.
|
|
134
76
|
|
|
135
|
-
|
|
136
|
-
1. Understand the protocol structure
|
|
137
|
-
2. Map hex bytes to displayed values
|
|
138
|
-
3. Determine byte positions, endianness, and scaling
|
|
139
|
-
4. Create the sensor class implementation
|
|
77
|
+
2. **Triage and group.** Have the agents pool every finding, dedupe overlap, and rank by blast radius and confidence. **Drop anything that cannot be verified from the code alone.** Byte-offset claims that need hardware to validate, claims about files on a live in-flight branch, decoder behavior that depends on what a real device actually emits: skip those rather than guess. Better to miss a real bug than to merge a wrong "fix."
|
|
140
78
|
|
|
141
|
-
|
|
142
|
-
Advertising Data shows:
|
|
143
|
-
Manufacturer Data (0x1234): AB CD 0C E4 10 0E 55 01 F4
|
|
79
|
+
3. **Group findings into themed PRs, not one big PR.** Different bug classes are different review burdens for the maintainer. PR #142 (silent value bugs) and PR #143 (runtime crashes) need different mental models from a reviewer; mixing them makes both harder to evaluate. Aim for roughly 5 to 10 fixes per PR with a coherent theme.
|
|
144
80
|
|
|
145
|
-
|
|
146
|
-
- Voltage: 13.2V
|
|
147
|
-
- Current: 10.5A
|
|
148
|
-
- SOC: 85%
|
|
81
|
+
4. **`/simplify` pass per PR.** Same as in workflow 1: run before opening each PR. It often catches an over-engineered patch and trims it down before it leaves your machine.
|
|
149
82
|
|
|
150
|
-
|
|
151
|
-
```
|
|
83
|
+
5. **Per-PR test plan tied to real hardware.** End every PR description with a checklist of things to verify on the actual devices. The maintainer is the one with the hardware; the test plan is the merge bar, not filler. PRs without a test plan put all the verification cost on the maintainer, and most maintainers will deprioritize them.
|
|
152
84
|
|
|
153
|
-
|
|
85
|
+
6. **Manual review before push.** Read every diff yourself. Hold back changes that look plausible to the agents but feel off on a second read. "Claude Code was involved" can mean very different things depending on whether a human looked at the diff before pushing.
|
|
154
86
|
|
|
155
|
-
|
|
87
|
+
### What this workflow does NOT do well
|
|
156
88
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
4. **Read values** from readable characteristics
|
|
161
|
-
5. **Enable notifications** on notifiable characteristics
|
|
162
|
-
6. **Screenshot hex values** and note what they represent
|
|
89
|
+
- **Architectural changes.** Agent teams find local bugs. Refactors and design shifts should be brainstormed with a human first, not handed to an agent team. Use the `brainstorming` skill for that.
|
|
90
|
+
- **Behavior changes that need hardware verification.** If a fix's correctness depends on what the device actually emits, the agents' confidence is meaningless. Either verify on hardware before opening the PR or skip the change.
|
|
91
|
+
- **Anything on an active feature branch.** Run this against `main` only. Running it across in-flight branches creates merge nightmares and steps on the contributor's work.
|
|
163
92
|
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
Using nRF Connect, I found on [DEVICE_NAME]:
|
|
93
|
+
---
|
|
167
94
|
|
|
168
|
-
|
|
169
|
-
Characteristics:
|
|
170
|
-
- 0000ffe1-... (Read, Notify)
|
|
171
|
-
Current value: A5 5A 00 01 01 0C E4 10 0E 55
|
|
95
|
+
## Appendix: tactical patterns
|
|
172
96
|
|
|
173
|
-
|
|
97
|
+
### Buffer reading
|
|
174
98
|
|
|
175
|
-
|
|
99
|
+
```js
|
|
100
|
+
buffer.readUInt16LE(offset) // unsigned 16-bit little-endian
|
|
101
|
+
buffer.readInt16LE(offset) // signed
|
|
102
|
+
buffer.readUInt16BE(offset) // big-endian
|
|
103
|
+
buffer.readUInt8(offset) // single byte
|
|
104
|
+
buffer.readInt8(offset)
|
|
105
|
+
buffer.readUInt32LE(offset)
|
|
176
106
|
```
|
|
177
107
|
|
|
178
|
-
|
|
108
|
+
Common decoder shapes:
|
|
179
109
|
|
|
180
|
-
|
|
110
|
+
```js
|
|
111
|
+
// Voltage: 0.01V units, unsigned LE
|
|
112
|
+
.read = (buffer) => buffer.readUInt16LE(2) / 100
|
|
181
113
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
I want to add support for a [DEVICE_NAME] Bluetooth battery to the bt-sensors-plugin-sk.
|
|
185
|
-
Please review the sensor_classes directory and identify existing battery sensor
|
|
186
|
-
implementations that are similar. Show me the key patterns used for:
|
|
187
|
-
- Device identification (matchManufacturerData or identify methods)
|
|
188
|
-
- Bluetooth service/characteristic UUIDs
|
|
189
|
-
- Data packet parsing
|
|
190
|
-
- SignalK path mapping
|
|
191
|
-
- GATT vs advertising data approaches
|
|
192
|
-
|
|
193
|
-
Focus on these example files:
|
|
194
|
-
- sensor_classes/VictronSmartLithium.js
|
|
195
|
-
- sensor_classes/JBDBMS.js
|
|
196
|
-
- sensor_classes/RenogyBattery.js
|
|
197
|
-
```
|
|
114
|
+
// Current: 0.01A units, signed LE (positive = charging)
|
|
115
|
+
.read = (buffer) => buffer.readInt16LE(4) / 100
|
|
198
116
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
- [`JBDBMS.js`](sensor_classes/JBDBMS.js:1) - GATT connection with command/response protocol
|
|
202
|
-
- [`RenogyBattery.js`](sensor_classes/RenogyBattery.js:1) - GATT with Modbus-style registers
|
|
203
|
-
- [`BTSensor.js`](BTSensor.js:1) - Base class showing required methods
|
|
117
|
+
// Temperature: 0.1°C, signed, returned as Kelvin
|
|
118
|
+
.read = (buffer) => buffer.readInt16LE(7) / 10 + 273.15
|
|
204
119
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
**Prompt to AI:**
|
|
208
|
-
```
|
|
209
|
-
My device [DOES/DOES NOT] require a GATT connection. It provides data via:
|
|
210
|
-
- [X] Advertising packets (manufacturer data or service data)
|
|
211
|
-
- [ ] GATT characteristic notifications
|
|
212
|
-
- [ ] GATT characteristic reads (polling)
|
|
213
|
-
|
|
214
|
-
Here's the Bluetooth scan data I captured:
|
|
215
|
-
[Paste output from bluetoothctl or nRF Connect]
|
|
216
|
-
|
|
217
|
-
Which approach should I use: advertising-based like VictronSmartLithium,
|
|
218
|
-
or GATT-based like JBDBMS or RenogyBattery?
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Step 3: Create Initial Sensor Class File
|
|
222
|
-
|
|
223
|
-
**Prompt to AI:**
|
|
224
|
-
```
|
|
225
|
-
Create a new sensor class file for [DEVICE_NAME] based on the following specifications:
|
|
226
|
-
|
|
227
|
-
Device: [DEVICE_NAME]
|
|
228
|
-
Manufacturer: [MANUFACTURER]
|
|
229
|
-
Communication method: [Advertising/GATT]
|
|
230
|
-
|
|
231
|
-
[IF ADVERTISING:]
|
|
232
|
-
Manufacturer ID: [0xXXXX]
|
|
233
|
-
The device advertises data in manufacturer data with this format:
|
|
234
|
-
[Describe byte layout]
|
|
235
|
-
|
|
236
|
-
[IF GATT:]
|
|
237
|
-
Service UUID: [UUID]
|
|
238
|
-
Characteristics:
|
|
239
|
-
- Read: [UUID] - [description]
|
|
240
|
-
- Notify: [UUID] - [description]
|
|
241
|
-
- Write: [UUID] - [description if applicable]
|
|
242
|
-
|
|
243
|
-
The device provides these metrics:
|
|
244
|
-
- Voltage (range: X-Y V)
|
|
245
|
-
- Current (range: -X to +Y A)
|
|
246
|
-
- State of Charge (0-100%)
|
|
247
|
-
- Temperature (range: X-Y °C)
|
|
248
|
-
- [Other metrics...]
|
|
249
|
-
|
|
250
|
-
Please create sensor_classes/[DeviceName].js following the pattern used in
|
|
251
|
-
[VictronSmartLithium.js/JBDBMS.js/RenogyBattery.js], including:
|
|
252
|
-
1. Class definition extending BTSensor
|
|
253
|
-
2. Static Domain property set to BTSensor.SensorDomains.electrical
|
|
254
|
-
3. Static identify() method for device identification
|
|
255
|
-
4. Static ImageFile property
|
|
256
|
-
5. initSchema() method with metadata
|
|
257
|
-
6. Data parsing methods
|
|
258
|
-
7. SignalK path defaults
|
|
259
|
-
```
|
|
120
|
+
// SoC: percent as ratio
|
|
121
|
+
.read = (buffer) => buffer.readUInt8(6) / 100
|
|
260
122
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
- Defines device identification logic
|
|
266
|
-
- Creates default SignalK path mappings
|
|
267
|
-
|
|
268
|
-
### Step 4: Implement Device Identification
|
|
269
|
-
|
|
270
|
-
**For Advertising-based Devices:**
|
|
271
|
-
|
|
272
|
-
**Prompt to AI:**
|
|
273
|
-
```
|
|
274
|
-
I have Bluetooth advertising data from [DEVICE_NAME]:
|
|
275
|
-
Manufacturer Data: [paste hex dump, e.g., "AB CD 01 02 03 04..."]
|
|
276
|
-
or
|
|
277
|
-
Service Data (UUID [UUID]): [paste hex dump]
|
|
278
|
-
|
|
279
|
-
The manufacturer ID should be: [0xXXXX]
|
|
280
|
-
|
|
281
|
-
Please implement the static identify(device) method to correctly identify this device.
|
|
282
|
-
Use the pattern from VictronSensor.js which checks manufacturer ID and additional
|
|
283
|
-
identifying bytes if needed.
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**For GATT Devices:**
|
|
287
|
-
|
|
288
|
-
**Prompt to AI:**
|
|
289
|
-
```
|
|
290
|
-
My GATT device advertises the service UUID: [UUID]
|
|
291
|
-
|
|
292
|
-
Please implement the static identify(device) method that:
|
|
293
|
-
1. Checks if the device advertises this service UUID
|
|
294
|
-
2. Returns the class if matched, null otherwise
|
|
295
|
-
|
|
296
|
-
Use this pattern:
|
|
297
|
-
static async identify(device){
|
|
298
|
-
const serviceUUIDs = await BTSensor.getDeviceProp(device, 'UUIDs')
|
|
299
|
-
if (serviceUUIDs && serviceUUIDs.includes('[UUID]'))
|
|
300
|
-
return this
|
|
301
|
-
return null
|
|
123
|
+
// "Not available" sentinel handling
|
|
124
|
+
.read = (buffer) => {
|
|
125
|
+
const v = buffer.readInt16LE(8)
|
|
126
|
+
return v === 0x7FFF ? NaN : v / 10
|
|
302
127
|
}
|
|
303
128
|
```
|
|
304
129
|
|
|
305
|
-
###
|
|
306
|
-
|
|
307
|
-
**Prompt to AI:**
|
|
308
|
-
```
|
|
309
|
-
The [DEVICE_NAME] sends data in manufacturer data packets with this format:
|
|
310
|
-
|
|
311
|
-
Byte 0-1: Manufacturer ID (0xXXXX, little-endian)
|
|
312
|
-
Byte 2-3: Voltage (0.01V units, unsigned 16-bit LE)
|
|
313
|
-
Byte 4-5: Current (0.01A units, signed 16-bit LE, positive=charging)
|
|
314
|
-
Byte 6: State of Charge (%, unsigned 8-bit, 0-100)
|
|
315
|
-
Byte 7-8: Temperature (0.1°C units, signed 16-bit LE)
|
|
316
|
-
[Continue for all fields...]
|
|
317
|
-
|
|
318
|
-
Sample packet: [provide actual hex dump, e.g., "CD AB 10 0D E8 03 64 5A 01"]
|
|
319
|
-
|
|
320
|
-
Please implement the propertiesChanged() method that:
|
|
321
|
-
1. Calls super.propertiesChanged(props) first
|
|
322
|
-
2. Extracts manufacturer data
|
|
323
|
-
3. Parses the buffer using the read functions defined in initSchema()
|
|
324
|
-
4. Calls emitValuesFrom(buffer) to emit all values
|
|
325
|
-
|
|
326
|
-
Also update initSchema() with the correct read functions for each path using:
|
|
327
|
-
.read = (buffer) => { return buffer.readUInt16LE(2) / 100 } // for voltage at byte 2
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**Common Buffer Reading Methods:**
|
|
331
|
-
- `buffer.readUInt16LE(offset)` - Unsigned 16-bit little-endian
|
|
332
|
-
- `buffer.readInt16LE(offset)` - Signed 16-bit little-endian
|
|
333
|
-
- `buffer.readUInt8(offset)` - Unsigned 8-bit
|
|
334
|
-
- `buffer.readInt8(offset)` - Signed 8-bit
|
|
335
|
-
- `buffer.readUInt32LE(offset)` - Unsigned 32-bit little-endian
|
|
336
|
-
- Use `BE` suffix for big-endian variants
|
|
337
|
-
|
|
338
|
-
### Step 6: Implement GATT Communication (GATT Method)
|
|
339
|
-
|
|
340
|
-
**Prompt to AI:**
|
|
341
|
-
```
|
|
342
|
-
My GATT device uses this protocol:
|
|
343
|
-
Service UUID: [UUID]
|
|
344
|
-
Notify Characteristic: [UUID] - Sends battery data
|
|
345
|
-
Write Characteristic: [UUID] - Accepts commands
|
|
346
|
-
Read Characteristic: [UUID] - Returns data on request
|
|
347
|
-
|
|
348
|
-
Command format: [describe, e.g., "0xDD 0xA5 CMD 0x00 0xFF CHECKSUM 0x77"]
|
|
349
|
-
Response format: [describe structure]
|
|
350
|
-
|
|
351
|
-
Please implement:
|
|
352
|
-
1. initSchema() that connects to the device and sets up characteristics
|
|
353
|
-
2. initGATTConnection() override if needed
|
|
354
|
-
3. initGATTNotifications() or initGATTInterval() depending on polling vs notifications
|
|
355
|
-
4. emitGATT() method that requests data and parses responses
|
|
356
|
-
5. Helper methods like sendReadFunctionRequest() if needed
|
|
357
|
-
|
|
358
|
-
Follow the pattern from JBDBMS.js which uses command/response protocol.
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**For devices that need polling:**
|
|
362
|
-
|
|
363
|
-
**Prompt to AI:**
|
|
364
|
-
```
|
|
365
|
-
Add GATT parameters to support polling:
|
|
366
|
-
- In initSchema(), call this.addGATTParameter() for pollFreq
|
|
367
|
-
- Set hasGATT() to return true
|
|
368
|
-
- Set usingGATT() to return this.useGATT
|
|
369
|
-
- Implement initGATTInterval() to poll at specified frequency
|
|
370
|
-
- Implement emitGATT() to read and emit all values
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### Step 7: Define SignalK Path Mappings
|
|
374
|
-
|
|
375
|
-
**Prompt to AI:**
|
|
376
|
-
```
|
|
377
|
-
Please implement proper SignalK path defaults in initSchema() for this battery.
|
|
378
|
-
Map the parsed values to these SignalK paths:
|
|
379
|
-
|
|
380
|
-
Use addDefaultPath() for standard paths:
|
|
381
|
-
- Voltage → electrical.batteries.voltage
|
|
382
|
-
- Current → electrical.batteries.current
|
|
383
|
-
- SOC → electrical.batteries.capacity.stateOfCharge
|
|
384
|
-
- Temperature → electrical.batteries.temperature
|
|
385
|
-
- Remaining capacity → electrical.batteries.capacity.remaining
|
|
386
|
-
- Cycles → electrical.batteries.cycles
|
|
387
|
-
|
|
388
|
-
Use addMetadatum() for device-specific paths:
|
|
389
|
-
- Cell voltages → electrical.batteries.{batteryID}.cell[N].voltage
|
|
390
|
-
- Protection status → electrical.batteries.{batteryID}.protectionStatus
|
|
391
|
-
- [Other custom metrics]
|
|
392
|
-
|
|
393
|
-
Add parameter for battery ID:
|
|
394
|
-
this.addDefaultParam("batteryID").default="house"
|
|
395
|
-
|
|
396
|
-
Ensure all paths use proper template variables like {batteryID} where appropriate.
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
**SignalK Path Reference:**
|
|
400
|
-
```
|
|
401
|
-
electrical.batteries.{id}.voltage // V
|
|
402
|
-
electrical.batteries.{id}.current // A (positive = charging)
|
|
403
|
-
electrical.batteries.{id}.temperature // K (Kelvin)
|
|
404
|
-
electrical.batteries.{id}.capacity.stateOfCharge // ratio (0-1)
|
|
405
|
-
electrical.batteries.{id}.capacity.remaining // C (coulombs/amp-hours)
|
|
406
|
-
electrical.batteries.{id}.capacity.actual // C (total capacity)
|
|
407
|
-
electrical.batteries.{id}.cycles // count
|
|
408
|
-
electrical.batteries.{id}.lifetimeDischarge // C
|
|
409
|
-
electrical.batteries.{id}.lifetimeRecharge // C
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Step 8: Add Device Image and Description
|
|
413
|
-
|
|
414
|
-
**Prompt to AI:**
|
|
415
|
-
```
|
|
416
|
-
I have an image for [DEVICE_NAME] saved as: public/images/[DeviceName].webp
|
|
417
|
-
|
|
418
|
-
Please update the sensor class to include:
|
|
419
|
-
1. static ImageFile = "[DeviceName].webp"
|
|
420
|
-
2. static Description = "[Brief description of device]"
|
|
421
|
-
3. static Manufacturer = "[Manufacturer Name]"
|
|
422
|
-
|
|
423
|
-
Follow the pattern from other sensor classes.
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### Step 9: Register the Sensor Class
|
|
427
|
-
|
|
428
|
-
**Prompt to AI:**
|
|
429
|
-
```
|
|
430
|
-
Please help me register the new [DeviceName] sensor class in the plugin:
|
|
431
|
-
1. Check classLoader.js to see the pattern for importing and registering classes
|
|
432
|
-
2. Add the appropriate require() statement
|
|
433
|
-
3. Add it to the class map
|
|
434
|
-
4. Ensure it's in the correct order for identification
|
|
435
|
-
|
|
436
|
-
Show me the exact changes needed to classLoader.js.
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### Step 10: Test with Real Device
|
|
440
|
-
|
|
441
|
-
**Prompt to AI:**
|
|
442
|
-
```
|
|
443
|
-
I'm testing with the actual [DEVICE_NAME] device. Here's what I'm seeing:
|
|
444
|
-
|
|
445
|
-
Debug output:
|
|
446
|
-
[Paste logs, errors, or unexpected behavior]
|
|
447
|
-
|
|
448
|
-
Expected values from device display:
|
|
449
|
-
- Voltage: [X]V
|
|
450
|
-
- Current: [X]A
|
|
451
|
-
- SOC: [X]%
|
|
452
|
-
- Temperature: [X]°C
|
|
453
|
-
|
|
454
|
-
Actual parsed values:
|
|
455
|
-
- Voltage: [Y]V
|
|
456
|
-
- Current: [Y]A
|
|
457
|
-
- SOC: [Y]%
|
|
458
|
-
- Temperature: [Y]°C
|
|
459
|
-
|
|
460
|
-
Please help debug:
|
|
461
|
-
1. Are the byte offsets correct?
|
|
462
|
-
2. Is the byte order (endianness) correct?
|
|
463
|
-
3. Are scaling factors correct?
|
|
464
|
-
4. Are signed vs unsigned interpretations correct?
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
**Common Issues & Fixes:**
|
|
468
|
-
- **Values 256x too large/small**: Wrong endianness (LE vs BE)
|
|
469
|
-
- **Negative values where shouldn't be**: Using UInt instead of Int
|
|
470
|
-
- **Values in wrong range**: Incorrect scaling factor
|
|
471
|
-
- **Values always same**: Wrong byte offset or not updating
|
|
472
|
-
|
|
473
|
-
### Step 11: Add Error Handling
|
|
474
|
-
|
|
475
|
-
**Prompt to AI:**
|
|
476
|
-
```
|
|
477
|
-
Please add robust error handling to the [DeviceName] class for:
|
|
478
|
-
|
|
479
|
-
For advertising-based devices:
|
|
480
|
-
1. Check manufacturer data exists before parsing
|
|
481
|
-
2. Validate buffer length before reading
|
|
482
|
-
3. Handle out-of-range values gracefully
|
|
483
|
-
4. Add try-catch in propertiesChanged()
|
|
484
|
-
|
|
485
|
-
For GATT devices:
|
|
486
|
-
1. Add timeouts for response waits
|
|
487
|
-
2. Validate checksums if protocol uses them
|
|
488
|
-
3. Handle disconnections gracefully
|
|
489
|
-
4. Add retry logic for failed reads
|
|
490
|
-
5. Implement deactivateGATT() cleanup
|
|
491
|
-
|
|
492
|
-
Follow the error handling patterns in JBDBMS.js and BTSensor.js.
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
### Step 12: Documentation and Testing
|
|
130
|
+
### SignalK paths
|
|
496
131
|
|
|
497
|
-
|
|
498
|
-
```
|
|
499
|
-
Please add comprehensive documentation to the [DeviceName] class:
|
|
500
|
-
|
|
501
|
-
1. Add JSDoc comments for all methods explaining:
|
|
502
|
-
- Parameters and return values
|
|
503
|
-
- Protocol details
|
|
504
|
-
- Data format specifics
|
|
505
|
-
|
|
506
|
-
2. Add a comment block at the top documenting the data format:
|
|
507
|
-
/*
|
|
508
|
-
[DeviceName] Data Format
|
|
509
|
-
Byte X-Y: [Field] (units, format, range)
|
|
510
|
-
...
|
|
511
|
-
*/
|
|
132
|
+
Standard battery paths (units in parentheses):
|
|
512
133
|
|
|
513
|
-
3. Create a _test() usage example showing how to test parsing with sample data
|
|
514
|
-
|
|
515
|
-
4. Document any device-specific quirks or limitations
|
|
516
134
|
```
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
135
|
+
electrical.batteries.{id}.voltage (V)
|
|
136
|
+
electrical.batteries.{id}.current (A, +ve = charging)
|
|
137
|
+
electrical.batteries.{id}.temperature (K)
|
|
138
|
+
electrical.batteries.{id}.capacity.stateOfCharge (ratio 0-1)
|
|
139
|
+
electrical.batteries.{id}.capacity.remaining (C, coulombs)
|
|
140
|
+
electrical.batteries.{id}.capacity.actual (C, total)
|
|
141
|
+
electrical.batteries.{id}.cycles (count)
|
|
142
|
+
electrical.batteries.{id}.lifetimeDischarge (C)
|
|
143
|
+
electrical.batteries.{id}.lifetimeRecharge (C)
|
|
522
144
|
```
|
|
523
|
-
User: I want to add support for the Acme BT-100 Bluetooth battery. It uses
|
|
524
|
-
advertising data with manufacturer ID 0x1234. Here's a sample packet:
|
|
525
|
-
34 12 10 0D E8 03 64 5A 01
|
|
526
|
-
|
|
527
|
-
AI: [Analyzes structure, creates initial class file]
|
|
528
|
-
|
|
529
|
-
User: The packet format is:
|
|
530
|
-
Bytes 0-1: Manufacturer ID (0x1234)
|
|
531
|
-
Bytes 2-3: Voltage in 0.01V (little-endian, so 0x0D10 = 3344 = 33.44V)
|
|
532
|
-
Bytes 4-5: Current in 0.01A (signed LE, 0x03E8 = 1000 = 10.00A)
|
|
533
|
-
Byte 6: SOC (0x64 = 100 = 100%)
|
|
534
|
-
Bytes 7-8: Temp in 0.1°C (signed LE, 0x015A = 346 = 34.6°C)
|
|
535
|
-
|
|
536
|
-
AI: [Implements propertiesChanged() and read functions with correct parsing]
|
|
537
145
|
|
|
538
|
-
|
|
146
|
+
Per-cell paths use `electrical.batteries.{id}.cell[N].voltage`.
|
|
539
147
|
|
|
540
|
-
|
|
148
|
+
### Common gotchas
|
|
541
149
|
|
|
542
|
-
|
|
150
|
+
| Symptom | Likely cause |
|
|
151
|
+
|---------|--------------|
|
|
152
|
+
| Values 256x off | Wrong endianness |
|
|
153
|
+
| Negative when charging | UInt vs Int, or signed value being read unsigned |
|
|
154
|
+
| Always the same constant | Wrong byte offset, OR a `(x, y)` comma-expression where a function call was intended |
|
|
155
|
+
| `undefined` from a decoder | Arrow function body uses `{ ... }` but is missing `return` |
|
|
156
|
+
| `Cannot read properties of null` after disconnect | GATT handle accessed without checking that `_gattConnection` is still non-null |
|
|
157
|
+
| Listener count grows over time | Override missing `super.propertiesChanged()` call, or a reconnect path that re-adds listeners without removing the old ones |
|
|
543
158
|
|
|
544
|
-
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
## Bluetooth Scanning Commands
|
|
548
|
-
|
|
549
|
-
### Using bluetoothctl
|
|
550
|
-
```bash
|
|
551
|
-
# Start scanning
|
|
552
|
-
bluetoothctl
|
|
553
|
-
scan on
|
|
554
|
-
|
|
555
|
-
# Wait for device to appear, then:
|
|
556
|
-
info [MAC_ADDRESS]
|
|
557
|
-
|
|
558
|
-
# Look for:
|
|
559
|
-
# - ManufacturerData: key [ID] value [hex bytes]
|
|
560
|
-
# - ServiceData: key [UUID] value [hex bytes]
|
|
561
|
-
# - UUIDs: [list of service UUIDs]
|
|
562
|
-
```
|
|
159
|
+
### Capturing HCI logs (quick reference)
|
|
563
160
|
|
|
564
|
-
### Using hcitool/hcidump
|
|
565
161
|
```bash
|
|
566
|
-
#
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
// Test with sample packet
|
|
580
|
-
const sampleData = "34 12 10 0D E8 03 64 5A 01"
|
|
581
|
-
MyBattery._test(sampleData)
|
|
582
|
-
|
|
583
|
-
// Should output:
|
|
584
|
-
// voltage=33.44
|
|
585
|
-
// current=10.00
|
|
586
|
-
// SOC=1.00
|
|
587
|
-
// temperature=307.75
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### Live Testing Checklist
|
|
591
|
-
- [ ] Device correctly identified during scan
|
|
592
|
-
- [ ] Connection establishes (if GATT)
|
|
593
|
-
- [ ] All metrics parse with correct values
|
|
594
|
-
- [ ] **Values match vendor app exactly** (critical!)
|
|
595
|
-
- [ ] **Parsed values match HCI snoop captured data**
|
|
596
|
-
- [ ] Values also match device's physical display (if available)
|
|
597
|
-
- [ ] Reconnection works after going out of range
|
|
598
|
-
- [ ] SignalK paths appear in data browser
|
|
599
|
-
- [ ] Units are correct (Kelvin for temp, etc.)
|
|
600
|
-
- [ ] No memory leaks over extended operation
|
|
601
|
-
|
|
602
|
-
### Validating Against Captured HCI Data
|
|
603
|
-
|
|
604
|
-
After implementing your sensor class, verify it parses the same packets you captured:
|
|
605
|
-
|
|
606
|
-
**Extract specific packets from your capture:**
|
|
607
|
-
```bash
|
|
608
|
-
# Use tshark to extract hex data from your captured log
|
|
162
|
+
# Android: enable in Developer Options, then
|
|
163
|
+
adb pull /sdcard/Android/data/btsnoop_hci.log
|
|
164
|
+
# Or, on newer Android:
|
|
165
|
+
adb bugreport
|
|
166
|
+
# then extract: FS/data/misc/bluetooth/logs/btsnoop_hci.log
|
|
167
|
+
|
|
168
|
+
# Wireshark filter:
|
|
169
|
+
bluetooth.addr == XX:XX:XX:XX:XX:XX
|
|
170
|
+
# look at: ATT Write Request, ATT Handle Value Notification,
|
|
171
|
+
# ATT Read Response, Advertising Data
|
|
172
|
+
|
|
173
|
+
# tshark for batch extraction:
|
|
609
174
|
tshark -r btsnoop_hci.log -Y "btatt && bluetooth.addr == XX:XX:XX:XX:XX:XX" \
|
|
610
175
|
-T fields -e btatt.value
|
|
611
176
|
```
|
|
612
177
|
|
|
613
|
-
|
|
614
|
-
```javascript
|
|
615
|
-
const MyBattery = require('./sensor_classes/MyBattery.js')
|
|
616
|
-
|
|
617
|
-
// Use actual packet from your HCI capture
|
|
618
|
-
const capturedPacket = "A5 5A 00 01 01 0C E4 10 0E 55 19 00 01 02"
|
|
619
|
-
MyBattery._test(capturedPacket)
|
|
620
|
-
|
|
621
|
-
// Output should match what vendor app showed:
|
|
622
|
-
// voltage=13.2
|
|
623
|
-
// current=10.5
|
|
624
|
-
// SOC=0.85
|
|
625
|
-
// temperature=298.15
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
**Prompt to AI if values don't match:**
|
|
629
|
-
```
|
|
630
|
-
My implementation parses this HCI-captured packet:
|
|
631
|
-
A5 5A 00 01 01 0C E4 10 0E 55
|
|
632
|
-
|
|
633
|
-
And produces:
|
|
634
|
-
- voltage=33.0V (WRONG)
|
|
635
|
-
- current=10.5A (CORRECT)
|
|
178
|
+
### bluetoothctl quick scan
|
|
636
179
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
**AI will likely identify:** You need to divide by number of cells, or the scaling factor is different, or byte order is reversed.
|
|
646
|
-
|
|
647
|
-
## Common Data Parsing Patterns
|
|
648
|
-
|
|
649
|
-
### Temperature Conversions
|
|
650
|
-
```javascript
|
|
651
|
-
// Celsius to Kelvin
|
|
652
|
-
.read = (buffer) => { return buffer.readInt16LE(offset) / 10 + 273.15 }
|
|
653
|
-
|
|
654
|
-
// Handle "not available" values
|
|
655
|
-
.read = (buffer) => {
|
|
656
|
-
const val = buffer.readInt8(offset)
|
|
657
|
-
return val === 0x7F ? NaN : val + 273.15
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### Current Direction
|
|
662
|
-
```javascript
|
|
663
|
-
// Positive = charging, negative = discharging
|
|
664
|
-
.read = (buffer) => { return buffer.readInt16LE(offset) / 100 }
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
### State of Charge
|
|
668
|
-
```javascript
|
|
669
|
-
// Percent to ratio (0-1)
|
|
670
|
-
.read = (buffer) => { return buffer.readUInt8(offset) / 100 }
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### Bit Flags
|
|
674
|
-
```javascript
|
|
675
|
-
.read = (buffer) => {
|
|
676
|
-
const byte = buffer.readUInt8(offset)
|
|
677
|
-
return {
|
|
678
|
-
charging: (byte & 0x01) !== 0,
|
|
679
|
-
discharging: (byte & 0x02) !== 0,
|
|
680
|
-
balancing: (byte & 0x04) !== 0,
|
|
681
|
-
fault: (byte & 0x80) !== 0
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
## Advanced Topics
|
|
687
|
-
|
|
688
|
-
### Encryption
|
|
689
|
-
Some devices (like Victron) encrypt advertising data. If your device uses encryption:
|
|
690
|
-
|
|
691
|
-
**Prompt to AI:**
|
|
692
|
-
```
|
|
693
|
-
My device uses AES-CTR encryption with a 128-bit key. The encrypted data starts
|
|
694
|
-
at byte 8 of the manufacturer data. The nonce/counter is in bytes 0-7.
|
|
695
|
-
|
|
696
|
-
Please implement:
|
|
697
|
-
1. A decrypt() method using Node.js crypto module
|
|
698
|
-
2. Update propertiesChanged() to decrypt before parsing
|
|
699
|
-
3. Add encryptionKey parameter to configuration
|
|
700
|
-
|
|
701
|
-
Follow the pattern from VictronSensor.js.
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
### Multiple Cell Voltages
|
|
705
|
-
For batteries with multiple cells:
|
|
706
|
-
|
|
707
|
-
**Prompt to AI:**
|
|
708
|
-
```
|
|
709
|
-
The battery has [N] cells. Cell voltages are packed into the data:
|
|
710
|
-
- Starting at byte X
|
|
711
|
-
- Each cell is 2 bytes, little-endian
|
|
712
|
-
- Values are in millivolts (1mV = 0.001V)
|
|
713
|
-
|
|
714
|
-
Please:
|
|
715
|
-
1. Add a loop in initSchema() to create cell voltage paths dynamically
|
|
716
|
-
2. Create read functions for each cell
|
|
717
|
-
3. Use template {batteryID} in paths like:
|
|
718
|
-
electrical.batteries.{batteryID}.cell[N].voltage
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
### Checksum Validation
|
|
722
|
-
For protocols with checksums:
|
|
723
|
-
|
|
724
|
-
**Prompt to AI:**
|
|
725
|
-
```
|
|
726
|
-
The protocol includes a checksum at bytes [X-Y]:
|
|
727
|
-
- Type: [CRC-16/sum/XOR]
|
|
728
|
-
- Calculated over bytes [A-B]
|
|
729
|
-
- [Describe algorithm]
|
|
730
|
-
|
|
731
|
-
Please implement a validateChecksum() function and call it before parsing data.
|
|
732
|
-
Reject invalid packets with this.debug() message.
|
|
733
|
-
|
|
734
|
-
See JBDBMS.js checkSum() function as reference.
|
|
180
|
+
```bash
|
|
181
|
+
bluetoothctl
|
|
182
|
+
scan on
|
|
183
|
+
# wait for the device to appear
|
|
184
|
+
info XX:XX:XX:XX:XX:XX
|
|
185
|
+
# look for: ManufacturerData, ServiceData, UUIDs
|
|
735
186
|
```
|
|
736
187
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
| Problem | Likely Cause | Solution Prompt |
|
|
740
|
-
|---------|-------------|-----------------|
|
|
741
|
-
| Device not appearing in scan | UUID filter, not advertising | "Check if identify() method is correct. Show me how to debug device detection." |
|
|
742
|
-
| Values are NaN | Wrong byte offsets, invalid data | "Values showing NaN. Help me verify byte offsets and add validation." |
|
|
743
|
-
| Connection fails | Wrong service UUID, device busy | "GATT connection failing with error: [error]. What should I check?" |
|
|
744
|
-
| Values incorrect magnitude | Wrong scaling factor | "Values are 100x too large. Check my scaling factors." |
|
|
745
|
-
| Negative when should be positive | Unsigned vs signed | "Current showing negative when charging. Fix my Int/UInt usage." |
|
|
746
|
-
| Updates stop after a while | Memory leak, no cleanup | "Device stops updating after 5 minutes. Check my cleanup in stopListening()." |
|
|
188
|
+
---
|
|
747
189
|
|
|
748
190
|
## Resources
|
|
749
191
|
|
|
750
|
-
- [SignalK
|
|
751
|
-
- [Bluetooth GATT
|
|
752
|
-
- [Node.js Buffer
|
|
753
|
-
-
|
|
754
|
-
-
|
|
755
|
-
-
|
|
756
|
-
|
|
757
|
-
## Summary
|
|
758
|
-
|
|
759
|
-
The AI-assisted development process follows this pattern:
|
|
760
|
-
|
|
761
|
-
1. **Research** - Analyze similar implementations
|
|
762
|
-
2. **Identify** - Determine communication method (advertising vs GATT)
|
|
763
|
-
3. **Create** - Generate skeleton sensor class
|
|
764
|
-
4. **Implement** - Add device identification logic
|
|
765
|
-
5. **Parse** - Implement data parsing with correct byte operations
|
|
766
|
-
6. **Map** - Create SignalK path mappings with defaults
|
|
767
|
-
7. **Register** - Add to classLoader.js
|
|
768
|
-
8. **Test** - Verify with real hardware
|
|
769
|
-
9. **Debug** - Fix scaling, offsets, byte order issues
|
|
770
|
-
10. **Refine** - Add error handling and documentation
|
|
771
|
-
11. **Complete** - Test thoroughly and contribute back
|
|
772
|
-
|
|
773
|
-
By providing clear, specific prompts with actual device data (hex dumps, protocol specs, test results), AI can effectively assist in creating robust, maintainable sensor implementations that follow the established patterns in this codebase.
|
|
774
|
-
|
|
775
|
-
## Key Success Factors
|
|
776
|
-
|
|
777
|
-
1. **Have real device data** - Actual advertising packets or GATT responses
|
|
778
|
-
2. **Know the protocol** - Byte layout, scaling factors, data types
|
|
779
|
-
3. **Test iteratively** - Make small changes, test each one
|
|
780
|
-
4. **Compare values** - Match against device's own display
|
|
781
|
-
5. **Use existing patterns** - Don't reinvent, follow proven implementations
|
|
782
|
-
6. **Document thoroughly** - Help the next developer
|
|
783
|
-
|
|
784
|
-
Good luck adding your Bluetooth battery! 🔋⚡
|
|
192
|
+
- [SignalK specification](http://signalk.org/specification/latest/doc/vesselsBranch.html)
|
|
193
|
+
- [Bluetooth GATT specifications](https://www.bluetooth.com/specifications/specs/)
|
|
194
|
+
- [Node.js Buffer API](https://nodejs.org/api/buffer.html)
|
|
195
|
+
- `README.md`: plugin overview
|
|
196
|
+
- `BTSensor.js`: base class for all sensor implementations
|
|
197
|
+
- `sensor_classes/DEVELOPMENT.md`: older device-style guide; some overlap with this file
|