@hangtime/grip-connect 0.10.9 → 0.13.0
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/README.md +14 -3
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interfaces/command.interface.d.ts +120 -20
- package/dist/cjs/interfaces/command.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/climbro.interface.d.ts +25 -0
- package/dist/cjs/interfaces/device/climbro.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/cts500.interface.d.ts +96 -0
- package/dist/cjs/interfaces/device/cts500.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/device/cts500.interface.js +3 -0
- package/dist/cjs/interfaces/device/cts500.interface.js.map +1 -0
- package/dist/cjs/interfaces/device/forceboard.interface.d.ts +2 -2
- package/dist/cjs/interfaces/device/forceboard.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/device/progressor.interface.d.ts +17 -7
- package/dist/cjs/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/cjs/interfaces/index.d.ts +2 -0
- package/dist/cjs/interfaces/index.d.ts.map +1 -1
- package/dist/cjs/interfaces/nordic.interface.d.ts +47 -0
- package/dist/cjs/interfaces/nordic.interface.d.ts.map +1 -0
- package/dist/cjs/interfaces/nordic.interface.js +3 -0
- package/dist/cjs/interfaces/nordic.interface.js.map +1 -0
- package/dist/cjs/models/device/climbro.model.d.ts +25 -0
- package/dist/cjs/models/device/climbro.model.d.ts.map +1 -1
- package/dist/cjs/models/device/climbro.model.js +93 -1
- package/dist/cjs/models/device/climbro.model.js.map +1 -1
- package/dist/cjs/models/device/cts500.model.d.ts +173 -0
- package/dist/cjs/models/device/cts500.model.d.ts.map +1 -0
- package/dist/cjs/models/device/cts500.model.js +588 -0
- package/dist/cjs/models/device/cts500.model.js.map +1 -0
- package/dist/cjs/models/device/entralpi.model.d.ts.map +1 -1
- package/dist/cjs/models/device/entralpi.model.js +3 -5
- package/dist/cjs/models/device/entralpi.model.js.map +1 -1
- package/dist/cjs/models/device/forceboard.model.d.ts +2 -2
- package/dist/cjs/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/cjs/models/device/forceboard.model.js +3 -14
- package/dist/cjs/models/device/forceboard.model.js.map +1 -1
- package/dist/cjs/models/device/progressor.model.d.ts +25 -12
- package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
- package/dist/cjs/models/device/progressor.model.js +82 -31
- package/dist/cjs/models/device/progressor.model.js.map +1 -1
- package/dist/cjs/models/device.model.d.ts +7 -0
- package/dist/cjs/models/device.model.d.ts.map +1 -1
- package/dist/cjs/models/device.model.js +52 -32
- package/dist/cjs/models/device.model.js.map +1 -1
- package/dist/cjs/models/index.d.ts +2 -0
- package/dist/cjs/models/index.d.ts.map +1 -1
- package/dist/cjs/models/index.js +6 -1
- package/dist/cjs/models/index.js.map +1 -1
- package/dist/cjs/models/nordic.model.d.ts +128 -0
- package/dist/cjs/models/nordic.model.d.ts.map +1 -0
- package/dist/cjs/models/nordic.model.js +405 -0
- package/dist/cjs/models/nordic.model.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/command.interface.d.ts +120 -20
- package/dist/interfaces/command.interface.d.ts.map +1 -1
- package/dist/interfaces/device/climbro.interface.d.ts +25 -0
- package/dist/interfaces/device/climbro.interface.d.ts.map +1 -1
- package/dist/interfaces/device/cts500.interface.d.ts +96 -0
- package/dist/interfaces/device/cts500.interface.d.ts.map +1 -0
- package/dist/interfaces/device/cts500.interface.js +2 -0
- package/dist/interfaces/device/cts500.interface.js.map +1 -0
- package/dist/interfaces/device/forceboard.interface.d.ts +2 -2
- package/dist/interfaces/device/forceboard.interface.d.ts.map +1 -1
- package/dist/interfaces/device/progressor.interface.d.ts +17 -7
- package/dist/interfaces/device/progressor.interface.d.ts.map +1 -1
- package/dist/interfaces/index.d.ts +2 -0
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/nordic.interface.d.ts +47 -0
- package/dist/interfaces/nordic.interface.d.ts.map +1 -0
- package/dist/interfaces/nordic.interface.js +2 -0
- package/dist/interfaces/nordic.interface.js.map +1 -0
- package/dist/models/device/climbro.model.d.ts +25 -0
- package/dist/models/device/climbro.model.d.ts.map +1 -1
- package/dist/models/device/climbro.model.js +93 -1
- package/dist/models/device/climbro.model.js.map +1 -1
- package/dist/models/device/cts500.model.d.ts +173 -0
- package/dist/models/device/cts500.model.d.ts.map +1 -0
- package/dist/models/device/cts500.model.js +584 -0
- package/dist/models/device/cts500.model.js.map +1 -0
- package/dist/models/device/entralpi.model.d.ts.map +1 -1
- package/dist/models/device/entralpi.model.js +3 -5
- package/dist/models/device/entralpi.model.js.map +1 -1
- package/dist/models/device/forceboard.model.d.ts +2 -2
- package/dist/models/device/forceboard.model.d.ts.map +1 -1
- package/dist/models/device/forceboard.model.js +3 -14
- package/dist/models/device/forceboard.model.js.map +1 -1
- package/dist/models/device/progressor.model.d.ts +25 -12
- package/dist/models/device/progressor.model.d.ts.map +1 -1
- package/dist/models/device/progressor.model.js +82 -31
- package/dist/models/device/progressor.model.js.map +1 -1
- package/dist/models/device.model.d.ts +7 -0
- package/dist/models/device.model.d.ts.map +1 -1
- package/dist/models/device.model.js +51 -32
- package/dist/models/device.model.js.map +1 -1
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -0
- package/dist/models/index.js.map +1 -1
- package/dist/models/nordic.model.d.ts +128 -0
- package/dist/models/nordic.model.d.ts.map +1 -0
- package/dist/models/nordic.model.js +393 -0
- package/dist/models/nordic.model.js.map +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/index.ts +2 -0
- package/src/interfaces/command.interface.ts +143 -20
- package/src/interfaces/device/climbro.interface.ts +30 -0
- package/src/interfaces/device/cts500.interface.ts +113 -0
- package/src/interfaces/device/forceboard.interface.ts +2 -2
- package/src/interfaces/device/progressor.interface.ts +19 -7
- package/src/interfaces/index.ts +4 -0
- package/src/interfaces/nordic.interface.ts +47 -0
- package/src/models/device/climbro.model.ts +98 -1
- package/src/models/device/cts500.model.ts +702 -0
- package/src/models/device/entralpi.model.ts +3 -5
- package/src/models/device/forceboard.model.ts +3 -14
- package/src/models/device/progressor.model.ts +86 -31
- package/src/models/device.model.ts +60 -32
- package/src/models/index.ts +4 -0
- package/src/models/nordic.model.ts +468 -0
|
@@ -169,11 +169,9 @@ export class Entralpi extends Device implements IEntralpi {
|
|
|
169
169
|
const receivedData: string = (value.getUint16(0) / 100).toFixed(1)
|
|
170
170
|
|
|
171
171
|
const convertedData = Number(receivedData)
|
|
172
|
-
// Adjust weight by using the tare value
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
const tare = this.applyTare(convertedData)
|
|
176
|
-
const numericData = tare === 0 ? convertedData : (convertedData - tare) * -1
|
|
172
|
+
// Adjust weight by using the tare value.
|
|
173
|
+
// Keep stream output consistent with other devices: positive load after tare.
|
|
174
|
+
const numericData = convertedData - this.applyTare(convertedData)
|
|
177
175
|
const currentMassTotal = Math.max(-1000, numericData)
|
|
178
176
|
|
|
179
177
|
// Update session stats before building packet
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NordicDfuDevice, createNordicDfuService } from "../nordic.model.js"
|
|
2
2
|
import type { IForceBoard } from "../../interfaces/device/forceboard.interface.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Represents a PitchSix Force Board device.
|
|
6
6
|
* {@link https://pitchsix.com}
|
|
7
7
|
*/
|
|
8
|
-
export class ForceBoard extends
|
|
8
|
+
export class ForceBoard extends NordicDfuDevice implements IForceBoard {
|
|
9
9
|
protected override streamUnit = "lbs" as const
|
|
10
10
|
|
|
11
11
|
constructor() {
|
|
@@ -46,18 +46,7 @@ export class ForceBoard extends Device implements IForceBoard {
|
|
|
46
46
|
},
|
|
47
47
|
],
|
|
48
48
|
},
|
|
49
|
-
|
|
50
|
-
name: "Nordic Device Firmware Update (DFU) Service",
|
|
51
|
-
id: "dfu",
|
|
52
|
-
uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
|
|
53
|
-
characteristics: [
|
|
54
|
-
{
|
|
55
|
-
name: "Buttonless DFU",
|
|
56
|
-
id: "dfu",
|
|
57
|
-
uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
49
|
+
createNordicDfuService(),
|
|
61
50
|
{
|
|
62
51
|
name: "",
|
|
63
52
|
id: "",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NordicDfuDevice, createNordicDfuService } from "../nordic.model.js"
|
|
2
2
|
import type { IProgressor } from "../../interfaces/device/progressor.interface.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -66,9 +66,8 @@ function parseProgressorIdPayload(payload: Uint8Array): string {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Parse calibration
|
|
70
|
-
*
|
|
71
|
-
* and a third value (version/reserved). Exact layout is device-specific.
|
|
69
|
+
* Parse calibration block: 3× float32 LE.
|
|
70
|
+
* value = raw * slope + intercept + trim.
|
|
72
71
|
*/
|
|
73
72
|
function parseCalibrationCurvePayload(payload: Uint8Array): string {
|
|
74
73
|
const hex = toHex(payload)
|
|
@@ -76,14 +75,49 @@ function parseCalibrationCurvePayload(payload: Uint8Array): string {
|
|
|
76
75
|
if (payload.length !== 12) return hex
|
|
77
76
|
|
|
78
77
|
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)
|
|
79
|
-
const
|
|
78
|
+
const slope = view.getFloat32(0, true)
|
|
79
|
+
const intercept = view.getFloat32(4, true)
|
|
80
|
+
const trim = view.getFloat32(8, true)
|
|
81
|
+
const effectiveOffset = intercept + trim
|
|
82
|
+
|
|
83
|
+
const formatSignedFloat = (value: number): string => {
|
|
84
|
+
const formatted = formatCalibrationFloat(Math.abs(value))
|
|
85
|
+
return value < 0 ? ` - ${formatted}` : ` + ${formatted}`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return `${hex} — slope: ${formatCalibrationFloat(slope)} | intercept: ${formatCalibrationFloat(intercept)} | trim: ${formatCalibrationFloat(trim)} | effective offset: ${formatCalibrationFloat(effectiveOffset)} | formula: raw * ${formatCalibrationFloat(slope)}${formatSignedFloat(intercept)}${formatSignedFloat(trim)}`
|
|
89
|
+
}
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Format floating-point values for calibration-table display.
|
|
93
|
+
*/
|
|
94
|
+
function formatCalibrationFloat(value: number): string {
|
|
95
|
+
if (!Number.isFinite(value)) return String(value)
|
|
96
|
+
const abs = Math.abs(value)
|
|
97
|
+
return abs !== 0 && (abs >= 1_000_000 || abs < 0.0001) ? value.toExponential(6) : value.toFixed(6)
|
|
82
98
|
}
|
|
83
99
|
|
|
84
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Parse one calibration table record: [u32 lower, u32 upper, f32 slope, f32 intercept].
|
|
102
|
+
*/
|
|
103
|
+
function parseCalibrationTableRecordPayload(payload: Uint8Array, index: number): string {
|
|
104
|
+
if (payload.length !== 16) return `${String(index).padStart(2, "0")}: ${toHex(payload)}`
|
|
105
|
+
|
|
106
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)
|
|
107
|
+
const lowerRaw = view.getUint32(0, true)
|
|
108
|
+
const upperRaw = view.getUint32(4, true)
|
|
109
|
+
const slope = view.getFloat32(8, true)
|
|
110
|
+
const intercept = view.getFloat32(12, true)
|
|
111
|
+
const hex = toHex(payload)
|
|
112
|
+
|
|
113
|
+
return `${String(index).padStart(2, "0")}: ${hex} | raw ${lowerRaw.toLocaleString()}..${upperRaw.toLocaleString()} | slope ${formatCalibrationFloat(slope)} | intercept ${formatCalibrationFloat(intercept)}`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class Progressor extends NordicDfuDevice implements IProgressor {
|
|
85
117
|
/** Device timestamps (µs) of recent samples (samples in last 1s device time). */
|
|
86
118
|
private recentSampleTimestamps: number[] = []
|
|
119
|
+
/** 1-based index for multi-packet calibration-table export responses. */
|
|
120
|
+
private calibrationTableRecordIndex = 0
|
|
87
121
|
|
|
88
122
|
constructor() {
|
|
89
123
|
super({
|
|
@@ -106,20 +140,9 @@ export class Progressor extends Device implements IProgressor {
|
|
|
106
140
|
},
|
|
107
141
|
],
|
|
108
142
|
},
|
|
109
|
-
|
|
110
|
-
name: "Nordic Device Firmware Update (DFU) Service",
|
|
111
|
-
id: "dfu",
|
|
112
|
-
uuid: "0000fe59-0000-1000-8000-00805f9b34fb",
|
|
113
|
-
characteristics: [
|
|
114
|
-
{
|
|
115
|
-
name: "Buttonless DFU",
|
|
116
|
-
id: "dfu",
|
|
117
|
-
uuid: "8ec90003-f315-4f60-9fb8-838830daea50",
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
},
|
|
143
|
+
createNordicDfuService(),
|
|
121
144
|
],
|
|
122
|
-
// Tindeq API: opcode = single byte (ASCII char code = decimal 100–114
|
|
145
|
+
// Tindeq API: opcode = single byte (ASCII char code = decimal 100–114 v2 firmware: 115-118)
|
|
123
146
|
commands: {
|
|
124
147
|
TARE_SCALE: "d", // 100 (0x64)
|
|
125
148
|
START_WEIGHT_MEAS: "e", // 101 (0x65)
|
|
@@ -136,6 +159,11 @@ export class Progressor extends Device implements IProgressor {
|
|
|
136
159
|
GET_PROGRESSOR_ID: "p", // 112 (0x70)
|
|
137
160
|
SET_CALIBRATION: "q", // 113 (0x71)
|
|
138
161
|
GET_CALIBRATION: "r", // 114 (0x72)
|
|
162
|
+
// V2 FIRMWARE ONLY COMMANDS
|
|
163
|
+
// ADD_CALIBRATION_TABLE_POINT: "s", // 115 (0x73)
|
|
164
|
+
GET_CALIBRATION_TABLE: "t", // 116 (0x74)
|
|
165
|
+
REBOOT: "u", // 117 (0x75)
|
|
166
|
+
// CLR_CALIBRATION_TABLE: "v", // 118 (0x76)
|
|
139
167
|
},
|
|
140
168
|
})
|
|
141
169
|
}
|
|
@@ -153,7 +181,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
153
181
|
}
|
|
154
182
|
|
|
155
183
|
/**
|
|
156
|
-
* Retrieves firmware version from the device
|
|
184
|
+
* Retrieves firmware version from the device.
|
|
157
185
|
* @returns {Promise<string>} A Promise that resolves with the firmware version,
|
|
158
186
|
*/
|
|
159
187
|
firmware = async (): Promise<string | undefined> => {
|
|
@@ -165,7 +193,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
/**
|
|
168
|
-
* Retrieves the Progressor ID from the device
|
|
196
|
+
* Retrieves the Progressor ID from the device.
|
|
169
197
|
* @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
|
|
170
198
|
*/
|
|
171
199
|
progressorId = async (): Promise<string | undefined> => {
|
|
@@ -177,8 +205,8 @@ export class Progressor extends Device implements IProgressor {
|
|
|
177
205
|
}
|
|
178
206
|
|
|
179
207
|
/**
|
|
180
|
-
* Retrieves calibration
|
|
181
|
-
*
|
|
208
|
+
* Retrieves the linear calibration block from the device.
|
|
209
|
+
* Returns raw hex plus decoded slope/intercept/trim coefficients.
|
|
182
210
|
*/
|
|
183
211
|
calibration = async (): Promise<string | undefined> => {
|
|
184
212
|
let response: string | undefined = undefined
|
|
@@ -189,7 +217,21 @@ export class Progressor extends Device implements IProgressor {
|
|
|
189
217
|
}
|
|
190
218
|
|
|
191
219
|
/**
|
|
192
|
-
*
|
|
220
|
+
* Retrieves the hidden 15-entry piecewise calibration table.
|
|
221
|
+
* Each response packet contains one 16-byte record.
|
|
222
|
+
* @returns {Promise<string | undefined>} Newline-separated decoded records.
|
|
223
|
+
*/
|
|
224
|
+
calibrationTable = async (): Promise<string | undefined> => {
|
|
225
|
+
const responses: string[] = []
|
|
226
|
+
this.calibrationTableRecordIndex = 0
|
|
227
|
+
await this.write("progressor", "tx", this.commands.GET_CALIBRATION_TABLE, 1000, (data) => {
|
|
228
|
+
responses.push(data)
|
|
229
|
+
})
|
|
230
|
+
return responses.length > 0 ? responses.join("\n") : undefined
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Computes calibration curve from stored points and saves to flash.
|
|
193
235
|
* Requires addCalibrationPoint() for zero and reference. Normal flow: i → i → j.
|
|
194
236
|
* @returns {Promise<void>} A Promise that resolves when the command is sent.
|
|
195
237
|
*/
|
|
@@ -198,24 +240,24 @@ export class Progressor extends Device implements IProgressor {
|
|
|
198
240
|
}
|
|
199
241
|
|
|
200
242
|
/**
|
|
201
|
-
*
|
|
243
|
+
* Write calibration block directly (raw overwrite).
|
|
202
244
|
*
|
|
203
245
|
* Payload layout (14 bytes):
|
|
204
|
-
* - [0] opcode ('q'
|
|
246
|
+
* - [0] opcode ('q')
|
|
205
247
|
* - [1] reserved (ignored by firmware)
|
|
206
|
-
* - [2..13] 12-byte calibration
|
|
248
|
+
* - [2..13] 12-byte calibration block (3× float32 LE: slope, intercept, trim)
|
|
207
249
|
*
|
|
208
250
|
* Notes:
|
|
209
251
|
* - This command does not compute anything; it overwrites stored calibration data.
|
|
210
|
-
* - Sending only the opcode (no 12-byte
|
|
252
|
+
* - Sending only the opcode (no 12-byte calibration block) is not a supported "reset" mode.
|
|
211
253
|
*
|
|
212
|
-
* @param curve - Raw 12-byte calibration
|
|
254
|
+
* @param curve - Raw 12-byte calibration block (3× float32 LE: slope, intercept, trim) (required).
|
|
213
255
|
* @returns Promise that resolves when the command is sent.
|
|
214
256
|
*/
|
|
215
257
|
setCalibration = async (curve: Uint8Array): Promise<void> => {
|
|
216
258
|
if (curve.length !== 12) throw new Error("Curve must be 12 bytes")
|
|
217
259
|
|
|
218
|
-
const opcode = (this.commands.SET_CALIBRATION as string).charCodeAt(0)
|
|
260
|
+
const opcode = (this.commands.SET_CALIBRATION as string).charCodeAt(0)
|
|
219
261
|
const payload = new Uint8Array(14)
|
|
220
262
|
|
|
221
263
|
payload[0] = opcode
|
|
@@ -269,6 +311,16 @@ export class Progressor extends Device implements IProgressor {
|
|
|
269
311
|
await this.write("progressor", "tx", typeof cmd === "string" ? cmd : String(cmd), 0)
|
|
270
312
|
}
|
|
271
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Reboots the device immediately.
|
|
316
|
+
* @returns {Promise<void>} A Promise that resolves when the command is sent.
|
|
317
|
+
*/
|
|
318
|
+
reboot = async (): Promise<void> => {
|
|
319
|
+
const opcode = (this.commands.REBOOT as string).charCodeAt(0)
|
|
320
|
+
// Send byte 1 to trigger the reboot.
|
|
321
|
+
await this.write("progressor", "tx", new Uint8Array([opcode, 0, 1]), 0)
|
|
322
|
+
}
|
|
323
|
+
|
|
272
324
|
/**
|
|
273
325
|
* Retrieves error information from the device.
|
|
274
326
|
* @returns {Promise<string | undefined>} A Promise that resolves with the error info text.
|
|
@@ -364,6 +416,9 @@ export class Progressor extends Device implements IProgressor {
|
|
|
364
416
|
output = parseProgressorIdPayload(payload)
|
|
365
417
|
} else if (this.writeLast === this.commands.GET_CALIBRATION) {
|
|
366
418
|
output = parseCalibrationCurvePayload(payload)
|
|
419
|
+
} else if (this.writeLast === this.commands.GET_CALIBRATION_TABLE) {
|
|
420
|
+
this.calibrationTableRecordIndex += 1
|
|
421
|
+
output = parseCalibrationTableRecordPayload(payload, this.calibrationTableRecordIndex)
|
|
367
422
|
} else {
|
|
368
423
|
// Unknown command response: return raw hex
|
|
369
424
|
output = toHex(payload)
|
|
@@ -213,6 +213,14 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
213
213
|
*/
|
|
214
214
|
private tareActive = false
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The characteristic ID that accepts notifications.
|
|
218
|
+
* Switching to "buttonless" allows to enter DFU mode and update the firmware.
|
|
219
|
+
*
|
|
220
|
+
* @type {"rx" | "buttonless" | "control"}
|
|
221
|
+
*/
|
|
222
|
+
protected notifyCharacteristicId: "rx" | "buttonless" = "rx"
|
|
223
|
+
|
|
216
224
|
/**
|
|
217
225
|
* Timestamp when the tare calibration process started.
|
|
218
226
|
* @type {number | null}
|
|
@@ -562,10 +570,12 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
562
570
|
// }
|
|
563
571
|
// }
|
|
564
572
|
|
|
565
|
-
this.bluetooth
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
573
|
+
if (!this.bluetooth?.gatt) {
|
|
574
|
+
this.bluetooth = await bluetooth.requestDevice({
|
|
575
|
+
filters: this.filters,
|
|
576
|
+
optionalServices: deviceServices,
|
|
577
|
+
})
|
|
578
|
+
}
|
|
569
579
|
|
|
570
580
|
if (!this.bluetooth.gatt) {
|
|
571
581
|
throw new Error("GATT is not available on this device")
|
|
@@ -573,7 +583,7 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
573
583
|
|
|
574
584
|
this.bluetooth.addEventListener("gattserverdisconnected", this.onDisconnectedListener)
|
|
575
585
|
|
|
576
|
-
this.server = await this.bluetooth.gatt.connect()
|
|
586
|
+
this.server = this.bluetooth.gatt.connected ? this.bluetooth.gatt : await this.bluetooth.gatt.connect()
|
|
577
587
|
|
|
578
588
|
if (this.server.connected) {
|
|
579
589
|
await this.onConnected(onSuccess)
|
|
@@ -596,31 +606,39 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
596
606
|
* device.disconnect();
|
|
597
607
|
*/
|
|
598
608
|
disconnect = (): void => {
|
|
599
|
-
|
|
609
|
+
const isConnected = this.isConnected()
|
|
610
|
+
if (isConnected) {
|
|
600
611
|
this.updateTimestamp()
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Remove all notification listeners and stop notifications if possible.
|
|
615
|
+
this.services.forEach((service) => {
|
|
616
|
+
service.characteristics.forEach((char) => {
|
|
617
|
+
if (!char.characteristic || char.id !== this.notifyCharacteristicId) return
|
|
618
|
+
|
|
619
|
+
if (isConnected) {
|
|
620
|
+
// Best effort only: avoid unhandled rejections when the device already disconnected.
|
|
621
|
+
void char.characteristic.stopNotifications().catch(() => undefined)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const listener = this.notificationListeners.get(char.uuid)
|
|
625
|
+
if (listener) {
|
|
626
|
+
char.characteristic.removeEventListener("characteristicvaluechanged", listener)
|
|
627
|
+
this.notificationListeners.delete(char.uuid)
|
|
628
|
+
}
|
|
614
629
|
})
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.
|
|
622
|
-
this.isActive = false
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
// Remove disconnect listener
|
|
633
|
+
this.bluetooth?.removeEventListener("gattserverdisconnected", this.onDisconnectedListener)
|
|
634
|
+
// Safely attempt to disconnect the device's GATT server, if available
|
|
635
|
+
if (this.bluetooth?.gatt?.connected) {
|
|
636
|
+
this.bluetooth.gatt.disconnect()
|
|
623
637
|
}
|
|
638
|
+
// Reset properties
|
|
639
|
+
this.server = undefined
|
|
640
|
+
this.writeLast = null
|
|
641
|
+
this.isActive = false
|
|
624
642
|
}
|
|
625
643
|
|
|
626
644
|
/**
|
|
@@ -890,7 +908,9 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
890
908
|
}
|
|
891
909
|
|
|
892
910
|
for (const service of services) {
|
|
893
|
-
const matchingService = this.services.find(
|
|
911
|
+
const matchingService = this.services.find(
|
|
912
|
+
(boardService) => boardService.uuid.toLowerCase() === service.uuid.toLowerCase(),
|
|
913
|
+
)
|
|
894
914
|
|
|
895
915
|
if (matchingService) {
|
|
896
916
|
// Android bug: Add a small delay before getting characteristics
|
|
@@ -899,16 +919,20 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
899
919
|
const characteristics = await service.getCharacteristics()
|
|
900
920
|
|
|
901
921
|
for (const characteristic of matchingService.characteristics) {
|
|
902
|
-
const matchingCharacteristic = characteristics.find(
|
|
922
|
+
const matchingCharacteristic = characteristics.find(
|
|
923
|
+
(char) => char.uuid.toLowerCase() === characteristic.uuid.toLowerCase(),
|
|
924
|
+
)
|
|
903
925
|
|
|
904
926
|
if (matchingCharacteristic) {
|
|
905
927
|
// Find the corresponding characteristic descriptor in the service's characteristics array
|
|
906
|
-
const descriptor = matchingService.characteristics.find(
|
|
928
|
+
const descriptor = matchingService.characteristics.find(
|
|
929
|
+
(char) => char.uuid.toLowerCase() === matchingCharacteristic.uuid.toLowerCase(),
|
|
930
|
+
)
|
|
907
931
|
if (descriptor) {
|
|
908
932
|
// Assign the actual Bluetooth characteristic object to the descriptor so it can be used later
|
|
909
933
|
descriptor.characteristic = matchingCharacteristic
|
|
910
|
-
// Look for
|
|
911
|
-
if (descriptor.id ===
|
|
934
|
+
// Look for our default notify characteristic id that accepts notifications
|
|
935
|
+
if (descriptor.id === this.notifyCharacteristicId) {
|
|
912
936
|
// Start receiving notifications for changes on this characteristic
|
|
913
937
|
matchingCharacteristic.startNotifications()
|
|
914
938
|
// Triggered when the characteristic's value changes
|
|
@@ -926,6 +950,10 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
926
950
|
this.notificationListeners.set(descriptor.uuid, listener)
|
|
927
951
|
}
|
|
928
952
|
}
|
|
953
|
+
} else if (matchingService.id === "dfu") {
|
|
954
|
+
// App mode exposes buttonless only, bootloader exposes control+packet only.
|
|
955
|
+
delete characteristic.characteristic
|
|
956
|
+
continue
|
|
929
957
|
} else {
|
|
930
958
|
throw new Error(`Characteristic ${characteristic.uuid} not found in service ${service.uuid}`)
|
|
931
959
|
}
|
package/src/models/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { Climbro } from "./device/climbro.model.js"
|
|
2
2
|
|
|
3
|
+
export { CTS500 } from "./device/cts500.model.js"
|
|
4
|
+
|
|
3
5
|
export { Entralpi } from "./device/entralpi.model.js"
|
|
4
6
|
|
|
5
7
|
export { ForceBoard } from "./device/forceboard.model.js"
|
|
6
8
|
|
|
9
|
+
export { NordicDfuDevice, createNordicDfuService } from "./nordic.model.js"
|
|
10
|
+
|
|
7
11
|
export { KilterBoard, KilterBoardPlacementRoles } from "./device/kilterboard.model.js"
|
|
8
12
|
|
|
9
13
|
export { Motherboard } from "./device/motherboard.model.js"
|