@hangtime/grip-connect 0.10.6 → 0.12.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 +3 -3
- package/dist/cjs/interfaces/command.interface.d.ts +12 -1
- 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/progressor.interface.d.ts +15 -5
- package/dist/cjs/interfaces/device/progressor.interface.d.ts.map +1 -1
- 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 +95 -3
- package/dist/cjs/models/device/climbro.model.js.map +1 -1
- 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/progressor.model.d.ts +23 -10
- package/dist/cjs/models/device/progressor.model.d.ts.map +1 -1
- package/dist/cjs/models/device/progressor.model.js +80 -18
- package/dist/cjs/models/device/progressor.model.js.map +1 -1
- package/dist/cjs/models/device.model.d.ts.map +1 -1
- package/dist/cjs/models/device.model.js +30 -25
- package/dist/cjs/models/device.model.js.map +1 -1
- package/dist/interfaces/command.interface.d.ts +12 -1
- 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/progressor.interface.d.ts +15 -5
- package/dist/interfaces/device/progressor.interface.d.ts.map +1 -1
- 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 +95 -3
- package/dist/models/device/climbro.model.js.map +1 -1
- 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/progressor.model.d.ts +23 -10
- package/dist/models/device/progressor.model.d.ts.map +1 -1
- package/dist/models/device/progressor.model.js +80 -18
- package/dist/models/device/progressor.model.js.map +1 -1
- package/dist/models/device.model.d.ts.map +1 -1
- package/dist/models/device.model.js +30 -25
- package/dist/models/device.model.js.map +1 -1
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/package.json +4 -1
- package/src/interfaces/command.interface.ts +14 -1
- package/src/interfaces/device/climbro.interface.ts +30 -0
- package/src/interfaces/device/progressor.interface.ts +17 -5
- package/src/models/device/climbro.model.ts +100 -3
- package/src/models/device/entralpi.model.ts +3 -5
- package/src/models/device/progressor.model.ts +84 -18
- package/src/models/device.model.ts +39 -25
|
@@ -57,10 +57,67 @@ export class Climbro extends Device implements IClimbro {
|
|
|
57
57
|
uuid: "49535343-fe7d-4ae5-8fa9-9fafd205e455",
|
|
58
58
|
characteristics: [
|
|
59
59
|
{
|
|
60
|
-
name: "
|
|
60
|
+
name: "UART Tramsmit (we set as rx)",
|
|
61
61
|
id: "rx",
|
|
62
62
|
uuid: "49535343-1e4d-4bd9-ba61-23c647249616",
|
|
63
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: "UART Receive (we set as tx)",
|
|
66
|
+
id: "tx",
|
|
67
|
+
uuid: "49535343-8841-43f4-a8d4-ecbe34729bb3",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "Transparent Control Point",
|
|
71
|
+
id: "tcp",
|
|
72
|
+
uuid: "49535343-4c8a-39b3-2f49-511cff073b7e",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Device Information",
|
|
78
|
+
id: "device",
|
|
79
|
+
uuid: "0000180a-0000-1000-8000-00805f9b34fb",
|
|
80
|
+
characteristics: [
|
|
81
|
+
{
|
|
82
|
+
name: "System ID",
|
|
83
|
+
id: "system",
|
|
84
|
+
uuid: "00002a23-0000-1000-8000-00805f9b34fb",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "Model Number String",
|
|
88
|
+
id: "model", // RN487x
|
|
89
|
+
uuid: "00002a24-0000-1000-8000-00805f9b34fb",
|
|
90
|
+
},
|
|
91
|
+
// {
|
|
92
|
+
// name: "Serial Number String (Blocked)",
|
|
93
|
+
// id: "serial",
|
|
94
|
+
// uuid: "00002a25-0000-1000-8000-00805f9b34fb",
|
|
95
|
+
// },
|
|
96
|
+
{
|
|
97
|
+
name: "Firmware Revision String",
|
|
98
|
+
id: "firmware",
|
|
99
|
+
uuid: "00002a26-0000-1000-8000-00805f9b34fb",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "Hardware Revision String",
|
|
103
|
+
id: "hardware", // 5505 102_BLDK3
|
|
104
|
+
uuid: "00002a27-0000-1000-8000-00805f9b34fb",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "Software Revision String",
|
|
108
|
+
id: "software", // 1.30
|
|
109
|
+
uuid: "00002a28-0000-1000-8000-00805f9b34fb",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "Manufacturer Name String",
|
|
113
|
+
id: "manufacturer", // Microchip
|
|
114
|
+
uuid: "00002a29-0000-1000-8000-00805f9b34fb",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "IEEE 11073-20601 Regulatory Certification Data List",
|
|
118
|
+
id: "certification",
|
|
119
|
+
uuid: "00002a2a-0000-1000-8000-00805f9b34fb",
|
|
120
|
+
},
|
|
64
121
|
],
|
|
65
122
|
},
|
|
66
123
|
],
|
|
@@ -76,6 +133,46 @@ export class Climbro extends Device implements IClimbro {
|
|
|
76
133
|
return this.batteryLevel.toString()
|
|
77
134
|
}
|
|
78
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Retrieves hardware version from the device.
|
|
138
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the hardware version.
|
|
139
|
+
*/
|
|
140
|
+
hardware = async (): Promise<string | undefined> => {
|
|
141
|
+
return await this.read("device", "hardware", 250)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retrieves manufacturer information from the device.
|
|
146
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the manufacturer information.
|
|
147
|
+
*/
|
|
148
|
+
manufacturer = async (): Promise<string | undefined> => {
|
|
149
|
+
return await this.read("device", "manufacturer", 250)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Retrieves model number from the device.
|
|
154
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the model number.
|
|
155
|
+
*/
|
|
156
|
+
model = async (): Promise<string | undefined> => {
|
|
157
|
+
return await this.read("device", "model", 250)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Retrieves software version from the device.
|
|
162
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the software version.
|
|
163
|
+
*/
|
|
164
|
+
software = async (): Promise<string | undefined> => {
|
|
165
|
+
return await this.read("device", "software", 250)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Retrieves system id from the device.
|
|
170
|
+
* @returns {Promise<string | undefined>} A Promise that resolves with the system id.
|
|
171
|
+
*/
|
|
172
|
+
system = async (): Promise<string | undefined> => {
|
|
173
|
+
return await this.read("device", "system", 250)
|
|
174
|
+
}
|
|
175
|
+
|
|
79
176
|
/**
|
|
80
177
|
* Handles data received from the device, processes force measurements and battery data
|
|
81
178
|
* according to the Climbro protocol.
|
|
@@ -92,7 +189,7 @@ export class Climbro extends Device implements IClimbro {
|
|
|
92
189
|
let flagSynchro = this.flagSynchro
|
|
93
190
|
let forceCount = 0
|
|
94
191
|
for (let i = 0; i < byteCount; i++) {
|
|
95
|
-
|
|
192
|
+
const b = buffer[i]
|
|
96
193
|
if (b === ClimbroResponses.BAT_DAT) {
|
|
97
194
|
flagSynchro = ClimbroResponses.BAT_DAT
|
|
98
195
|
continue
|
|
@@ -102,7 +199,7 @@ export class Climbro extends Device implements IClimbro {
|
|
|
102
199
|
continue
|
|
103
200
|
}
|
|
104
201
|
if (b === ClimbroResponses.DAT_36KG) {
|
|
105
|
-
|
|
202
|
+
// 36kg sentinel: fall through to sync/force handling
|
|
106
203
|
}
|
|
107
204
|
if (flagSynchro === ClimbroResponses.BAT_DAT) continue
|
|
108
205
|
if (flagSynchro === ClimbroResponses.SENS_DAT) {
|
|
@@ -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
|
|
@@ -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
|
+
}
|
|
90
|
+
|
|
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)
|
|
98
|
+
}
|
|
99
|
+
|
|
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)
|
|
80
112
|
|
|
81
|
-
return `${hex}
|
|
113
|
+
return `${String(index).padStart(2, "0")}: ${hex} | raw ${lowerRaw.toLocaleString()}..${upperRaw.toLocaleString()} | slope ${formatCalibrationFloat(slope)} | intercept ${formatCalibrationFloat(intercept)}`
|
|
82
114
|
}
|
|
83
115
|
|
|
84
116
|
export class Progressor extends Device 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({
|
|
@@ -119,7 +153,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
119
153
|
],
|
|
120
154
|
},
|
|
121
155
|
],
|
|
122
|
-
// Tindeq API: opcode = single byte (ASCII char code = decimal 100–114
|
|
156
|
+
// Tindeq API: opcode = single byte (ASCII char code = decimal 100–114 v2 firmware: 115-118)
|
|
123
157
|
commands: {
|
|
124
158
|
TARE_SCALE: "d", // 100 (0x64)
|
|
125
159
|
START_WEIGHT_MEAS: "e", // 101 (0x65)
|
|
@@ -136,6 +170,11 @@ export class Progressor extends Device implements IProgressor {
|
|
|
136
170
|
GET_PROGRESSOR_ID: "p", // 112 (0x70)
|
|
137
171
|
SET_CALIBRATION: "q", // 113 (0x71)
|
|
138
172
|
GET_CALIBRATION: "r", // 114 (0x72)
|
|
173
|
+
// V2 FIRMWARE ONLY COMMANDS
|
|
174
|
+
// ADD_CALIBRATION_TABLE_POINT: "s", // 115 (0x73)
|
|
175
|
+
GET_CALIBRATION_TABLE: "t", // 116 (0x74)
|
|
176
|
+
REBOOT: "u", // 117 (0x75)
|
|
177
|
+
// CLR_CALIBRATION_TABLE: "v", // 118 (0x76)
|
|
139
178
|
},
|
|
140
179
|
})
|
|
141
180
|
}
|
|
@@ -153,7 +192,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
153
192
|
}
|
|
154
193
|
|
|
155
194
|
/**
|
|
156
|
-
* Retrieves firmware version from the device
|
|
195
|
+
* Retrieves firmware version from the device.
|
|
157
196
|
* @returns {Promise<string>} A Promise that resolves with the firmware version,
|
|
158
197
|
*/
|
|
159
198
|
firmware = async (): Promise<string | undefined> => {
|
|
@@ -165,7 +204,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
165
204
|
}
|
|
166
205
|
|
|
167
206
|
/**
|
|
168
|
-
* Retrieves the Progressor ID from the device
|
|
207
|
+
* Retrieves the Progressor ID from the device.
|
|
169
208
|
* @returns {Promise<string>} A Promise that resolves with the raw response (hex of payload).
|
|
170
209
|
*/
|
|
171
210
|
progressorId = async (): Promise<string | undefined> => {
|
|
@@ -177,8 +216,8 @@ export class Progressor extends Device implements IProgressor {
|
|
|
177
216
|
}
|
|
178
217
|
|
|
179
218
|
/**
|
|
180
|
-
* Retrieves calibration
|
|
181
|
-
*
|
|
219
|
+
* Retrieves the linear calibration block from the device.
|
|
220
|
+
* Returns raw hex plus decoded slope/intercept/trim coefficients.
|
|
182
221
|
*/
|
|
183
222
|
calibration = async (): Promise<string | undefined> => {
|
|
184
223
|
let response: string | undefined = undefined
|
|
@@ -189,7 +228,21 @@ export class Progressor extends Device implements IProgressor {
|
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
/**
|
|
192
|
-
*
|
|
231
|
+
* Retrieves the hidden 15-entry piecewise calibration table.
|
|
232
|
+
* Each response packet contains one 16-byte record.
|
|
233
|
+
* @returns {Promise<string | undefined>} Newline-separated decoded records.
|
|
234
|
+
*/
|
|
235
|
+
calibrationTable = async (): Promise<string | undefined> => {
|
|
236
|
+
const responses: string[] = []
|
|
237
|
+
this.calibrationTableRecordIndex = 0
|
|
238
|
+
await this.write("progressor", "tx", this.commands.GET_CALIBRATION_TABLE, 1000, (data) => {
|
|
239
|
+
responses.push(data)
|
|
240
|
+
})
|
|
241
|
+
return responses.length > 0 ? responses.join("\n") : undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Computes calibration curve from stored points and saves to flash.
|
|
193
246
|
* Requires addCalibrationPoint() for zero and reference. Normal flow: i → i → j.
|
|
194
247
|
* @returns {Promise<void>} A Promise that resolves when the command is sent.
|
|
195
248
|
*/
|
|
@@ -198,24 +251,24 @@ export class Progressor extends Device implements IProgressor {
|
|
|
198
251
|
}
|
|
199
252
|
|
|
200
253
|
/**
|
|
201
|
-
*
|
|
254
|
+
* Write calibration block directly (raw overwrite).
|
|
202
255
|
*
|
|
203
256
|
* Payload layout (14 bytes):
|
|
204
|
-
* - [0] opcode ('q'
|
|
257
|
+
* - [0] opcode ('q')
|
|
205
258
|
* - [1] reserved (ignored by firmware)
|
|
206
|
-
* - [2..13] 12-byte calibration
|
|
259
|
+
* - [2..13] 12-byte calibration block (3× float32 LE: slope, intercept, trim)
|
|
207
260
|
*
|
|
208
261
|
* Notes:
|
|
209
262
|
* - This command does not compute anything; it overwrites stored calibration data.
|
|
210
|
-
* - Sending only the opcode (no 12-byte
|
|
263
|
+
* - Sending only the opcode (no 12-byte calibration block) is not a supported "reset" mode.
|
|
211
264
|
*
|
|
212
|
-
* @param curve - Raw 12-byte calibration
|
|
265
|
+
* @param curve - Raw 12-byte calibration block (3× float32 LE: slope, intercept, trim) (required).
|
|
213
266
|
* @returns Promise that resolves when the command is sent.
|
|
214
267
|
*/
|
|
215
268
|
setCalibration = async (curve: Uint8Array): Promise<void> => {
|
|
216
269
|
if (curve.length !== 12) throw new Error("Curve must be 12 bytes")
|
|
217
270
|
|
|
218
|
-
const opcode = (this.commands.SET_CALIBRATION as string).charCodeAt(0)
|
|
271
|
+
const opcode = (this.commands.SET_CALIBRATION as string).charCodeAt(0)
|
|
219
272
|
const payload = new Uint8Array(14)
|
|
220
273
|
|
|
221
274
|
payload[0] = opcode
|
|
@@ -269,6 +322,16 @@ export class Progressor extends Device implements IProgressor {
|
|
|
269
322
|
await this.write("progressor", "tx", typeof cmd === "string" ? cmd : String(cmd), 0)
|
|
270
323
|
}
|
|
271
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Reboots the device immediately.
|
|
327
|
+
* @returns {Promise<void>} A Promise that resolves when the command is sent.
|
|
328
|
+
*/
|
|
329
|
+
reboot = async (): Promise<void> => {
|
|
330
|
+
const opcode = (this.commands.REBOOT as string).charCodeAt(0)
|
|
331
|
+
// Send byte 1 to trigger the reboot.
|
|
332
|
+
await this.write("progressor", "tx", new Uint8Array([opcode, 0, 1]), 0)
|
|
333
|
+
}
|
|
334
|
+
|
|
272
335
|
/**
|
|
273
336
|
* Retrieves error information from the device.
|
|
274
337
|
* @returns {Promise<string | undefined>} A Promise that resolves with the error info text.
|
|
@@ -353,7 +416,7 @@ export class Progressor extends Device implements IProgressor {
|
|
|
353
416
|
else if (kind === ProgressorResponses.RESPONSE_COMMAND) {
|
|
354
417
|
if (!this.writeLast) return
|
|
355
418
|
|
|
356
|
-
let output
|
|
419
|
+
let output: string
|
|
357
420
|
if (this.writeLast === this.commands.GET_BATTERY_VOLTAGE) {
|
|
358
421
|
output = new DataView(payload.buffer, payload.byteOffset, payload.byteLength).getUint32(0, true).toString()
|
|
359
422
|
} else if (this.writeLast === this.commands.GET_FIRMWARE_VERSION) {
|
|
@@ -364,6 +427,9 @@ export class Progressor extends Device implements IProgressor {
|
|
|
364
427
|
output = parseProgressorIdPayload(payload)
|
|
365
428
|
} else if (this.writeLast === this.commands.GET_CALIBRATION) {
|
|
366
429
|
output = parseCalibrationCurvePayload(payload)
|
|
430
|
+
} else if (this.writeLast === this.commands.GET_CALIBRATION_TABLE) {
|
|
431
|
+
this.calibrationTableRecordIndex += 1
|
|
432
|
+
output = parseCalibrationTableRecordPayload(payload, this.calibrationTableRecordIndex)
|
|
367
433
|
} else {
|
|
368
434
|
// Unknown command response: return raw hex
|
|
369
435
|
output = toHex(payload)
|
|
@@ -596,31 +596,39 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
596
596
|
* device.disconnect();
|
|
597
597
|
*/
|
|
598
598
|
disconnect = (): void => {
|
|
599
|
-
|
|
599
|
+
const isConnected = this.isConnected()
|
|
600
|
+
if (isConnected) {
|
|
600
601
|
this.updateTimestamp()
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Remove all notification listeners and stop notifications if possible.
|
|
605
|
+
this.services.forEach((service) => {
|
|
606
|
+
service.characteristics.forEach((char) => {
|
|
607
|
+
if (!char.characteristic || char.id !== "rx") return
|
|
608
|
+
|
|
609
|
+
if (isConnected) {
|
|
610
|
+
// Best effort only: avoid unhandled rejections when the device already disconnected.
|
|
611
|
+
void char.characteristic.stopNotifications().catch(() => undefined)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const listener = this.notificationListeners.get(char.uuid)
|
|
615
|
+
if (listener) {
|
|
616
|
+
char.characteristic.removeEventListener("characteristicvaluechanged", listener)
|
|
617
|
+
this.notificationListeners.delete(char.uuid)
|
|
618
|
+
}
|
|
614
619
|
})
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.
|
|
622
|
-
this.isActive = false
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
// Remove disconnect listener
|
|
623
|
+
this.bluetooth?.removeEventListener("gattserverdisconnected", this.onDisconnectedListener)
|
|
624
|
+
// Safely attempt to disconnect the device's GATT server, if available
|
|
625
|
+
if (this.bluetooth?.gatt?.connected) {
|
|
626
|
+
this.bluetooth.gatt.disconnect()
|
|
623
627
|
}
|
|
628
|
+
// Reset properties
|
|
629
|
+
this.server = undefined
|
|
630
|
+
this.writeLast = null
|
|
631
|
+
this.isActive = false
|
|
624
632
|
}
|
|
625
633
|
|
|
626
634
|
/**
|
|
@@ -890,7 +898,9 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
890
898
|
}
|
|
891
899
|
|
|
892
900
|
for (const service of services) {
|
|
893
|
-
const matchingService = this.services.find(
|
|
901
|
+
const matchingService = this.services.find(
|
|
902
|
+
(boardService) => boardService.uuid.toLowerCase() === service.uuid.toLowerCase(),
|
|
903
|
+
)
|
|
894
904
|
|
|
895
905
|
if (matchingService) {
|
|
896
906
|
// Android bug: Add a small delay before getting characteristics
|
|
@@ -899,11 +909,15 @@ export abstract class Device extends BaseModel implements IDevice {
|
|
|
899
909
|
const characteristics = await service.getCharacteristics()
|
|
900
910
|
|
|
901
911
|
for (const characteristic of matchingService.characteristics) {
|
|
902
|
-
const matchingCharacteristic = characteristics.find(
|
|
912
|
+
const matchingCharacteristic = characteristics.find(
|
|
913
|
+
(char) => char.uuid.toLowerCase() === characteristic.uuid.toLowerCase(),
|
|
914
|
+
)
|
|
903
915
|
|
|
904
916
|
if (matchingCharacteristic) {
|
|
905
917
|
// Find the corresponding characteristic descriptor in the service's characteristics array
|
|
906
|
-
const descriptor = matchingService.characteristics.find(
|
|
918
|
+
const descriptor = matchingService.characteristics.find(
|
|
919
|
+
(char) => char.uuid.toLowerCase() === matchingCharacteristic.uuid.toLowerCase(),
|
|
920
|
+
)
|
|
907
921
|
if (descriptor) {
|
|
908
922
|
// Assign the actual Bluetooth characteristic object to the descriptor so it can be used later
|
|
909
923
|
descriptor.characteristic = matchingCharacteristic
|