@dg-kit/protocol 0.1.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 ADDED
@@ -0,0 +1,36 @@
1
+ # @dg-kit/protocol
2
+
3
+ Transport-agnostic BLE protocol adapters for DG-Lab Coyote 2.0 (V2) and Coyote 3.0 (V3).
4
+
5
+ ## What it does
6
+
7
+ This package implements the **byte-level protocol and tick state machine** for both Coyote generations:
8
+
9
+ - 100 ms write loop (B0 packets on V3, strength + dual-wave writes on V2)
10
+ - Wave playback state machine (loop / one-shot, 25 ms frame grid)
11
+ - Strength absolute / relative / clamp logic
12
+ - Burst-with-auto-restore
13
+ - Emergency stop with stale-notification suppression
14
+ - Sequence + ack handling on V3
15
+
16
+ ## What it doesn't do
17
+
18
+ It doesn't open a BLE connection itself. Pass it characteristic handles via the `BluetoothRemoteGATTCharacteristicLike` interface and it'll drive the device. This way the same protocol code runs in:
19
+
20
+ - **Browsers** via `@dg-kit/transport-webbluetooth`
21
+ - **Node.js** via `@dg-mcp/device-noble` (DG-MCP) or any other BLE backend
22
+
23
+ ## Public API
24
+
25
+ ```ts
26
+ import {
27
+ CoyoteProtocolAdapter, // facade: auto-routes V2/V3 by device.name prefix
28
+ CoyoteV2ProtocolAdapter,
29
+ CoyoteV3ProtocolAdapter,
30
+ V2_DEVICE_NAME_PREFIX,
31
+ V3_DEVICE_NAME_PREFIX,
32
+ COYOTE_REQUEST_DEVICE_OPTIONS,
33
+ } from '@dg-kit/protocol';
34
+ ```
35
+
36
+ Most consumers want `CoyoteProtocolAdapter` — it picks V2 or V3 based on the connecting device's name prefix.
package/dist/base.d.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { type Channel, type DeviceCommand, type DeviceCommandResult, type DeviceState, type WaveFrame } from '@dg-kit/core';
2
+ import type { BluetoothDeviceLike, BluetoothRemoteGATTCharacteristicLike, BluetoothRemoteGATTServerLike } from './types.js';
3
+ export type StateListener = (state: DeviceState) => void;
4
+ export interface WebBluetoothAvailability {
5
+ supported: boolean;
6
+ reason?: string;
7
+ }
8
+ export interface WebBluetoothConnectionContext {
9
+ device: BluetoothDeviceLike;
10
+ server: BluetoothRemoteGATTServerLike;
11
+ }
12
+ export interface WebBluetoothProtocolAdapter {
13
+ onConnected(context: WebBluetoothConnectionContext): Promise<void>;
14
+ onDisconnected(): Promise<void>;
15
+ getState(): DeviceState;
16
+ execute(command: DeviceCommand): Promise<DeviceCommandResult>;
17
+ emergencyStop(): Promise<void>;
18
+ subscribe(listener: StateListener): () => void;
19
+ }
20
+ export interface ChannelWaveState {
21
+ waveformId?: string;
22
+ frames: WaveFrame[] | null;
23
+ index: number;
24
+ loop: boolean;
25
+ active: boolean;
26
+ }
27
+ export type Quad = [number, number, number, number];
28
+ export type WaveStep = {
29
+ freq: number;
30
+ int: number;
31
+ };
32
+ export declare const INACTIVE_FREQ: Quad;
33
+ export declare const INACTIVE_INT: Quad;
34
+ export declare const SILENT_WAVE_STEP: WaveStep;
35
+ export declare abstract class BaseCoyoteProtocolAdapter implements WebBluetoothProtocolAdapter {
36
+ private readonly listeners;
37
+ protected readonly waveState: Record<Channel, ChannelWaveState>;
38
+ protected readonly burstRestores: Map<Channel, NodeJS.Timeout>;
39
+ protected state: DeviceState;
40
+ protected batteryChar: BluetoothRemoteGATTCharacteristicLike | null;
41
+ private tickWorker;
42
+ private tickInterval;
43
+ private tickInFlight;
44
+ protected tickPaused: boolean;
45
+ protected suppressStaleStopStrengthNotifications: boolean;
46
+ subscribe(listener: StateListener): () => void;
47
+ onDisconnected(): Promise<void>;
48
+ protected resetConnectionState(options: {
49
+ emit: boolean;
50
+ }): Promise<void>;
51
+ getState(): DeviceState;
52
+ execute(command: DeviceCommand): Promise<DeviceCommandResult>;
53
+ emergencyStop(): Promise<void>;
54
+ protected setConnectedDevice(device: BluetoothDeviceLike): void;
55
+ protected startProtocolLoop(): void;
56
+ protected resetWaveState(): void;
57
+ protected setWave(channel: Channel, waveformId: string, frames: WaveFrame[], loop: boolean): void;
58
+ protected clearWave(channel: Channel): void;
59
+ protected runBurst(channel: Channel, strength: number, durationMs: number): void;
60
+ protected cancelBurstRestore(channel: Channel): void;
61
+ protected onTick(): Promise<void>;
62
+ protected waitForTickIdle(): Promise<void>;
63
+ protected advanceWaveStep(channel: Channel): WaveStep | null;
64
+ protected advanceWavePacket(channel: Channel): {
65
+ freq: Quad;
66
+ int: Quad;
67
+ };
68
+ protected clamp(value: number, min: number, max: number): number;
69
+ protected toInt(value: unknown, fallback?: number): number;
70
+ protected readBattery(): Promise<void>;
71
+ protected shouldIgnoreStaleStopStrengthNotification(strengthA: number, strengthB: number): boolean;
72
+ protected writeCharacteristicValue(characteristic: BluetoothRemoteGATTCharacteristicLike, value: ArrayBufferView | ArrayBuffer, options?: {
73
+ preferResponse?: boolean;
74
+ }): Promise<void>;
75
+ protected emit(): void;
76
+ private startTickLoop;
77
+ private stopTickLoop;
78
+ private createTickWorker;
79
+ protected abstract disconnectProtocol(): Promise<void>;
80
+ abstract onConnected(context: WebBluetoothConnectionContext): Promise<void>;
81
+ protected abstract resetProtocolState(): void;
82
+ protected abstract resetEmergencyStopState(): void;
83
+ protected abstract setAbsoluteStrength(channel: Channel, value: number): void;
84
+ protected abstract adjustStrength(channel: Channel, delta: number): void;
85
+ protected abstract performTick(): Promise<void>;
86
+ protected abstract writeEmergencyStopPacket(): Promise<void>;
87
+ }
88
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"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"}
package/dist/base.js ADDED
@@ -0,0 +1,338 @@
1
+ import { createEmptyDeviceState, } from '@dg-kit/core';
2
+ export const INACTIVE_FREQ = [0, 0, 0, 0];
3
+ export const INACTIVE_INT = [0, 0, 0, 101];
4
+ export const SILENT_WAVE_STEP = { freq: 10, int: 0 };
5
+ export class BaseCoyoteProtocolAdapter {
6
+ listeners = new Set();
7
+ waveState = {
8
+ A: { frames: null, index: 0, loop: false, active: false },
9
+ B: { frames: null, index: 0, loop: false, active: false },
10
+ };
11
+ burstRestores = new Map();
12
+ state = createEmptyDeviceState();
13
+ batteryChar = null;
14
+ tickWorker = null;
15
+ tickInterval = null;
16
+ tickInFlight = false;
17
+ tickPaused = false;
18
+ suppressStaleStopStrengthNotifications = false;
19
+ subscribe(listener) {
20
+ this.listeners.add(listener);
21
+ return () => {
22
+ this.listeners.delete(listener);
23
+ };
24
+ }
25
+ async onDisconnected() {
26
+ await this.resetConnectionState({ emit: true });
27
+ }
28
+ async resetConnectionState(options) {
29
+ this.tickPaused = false;
30
+ this.suppressStaleStopStrengthNotifications = false;
31
+ this.stopTickLoop();
32
+ this.cancelBurstRestore('A');
33
+ this.cancelBurstRestore('B');
34
+ await this.disconnectProtocol();
35
+ const previousDeviceName = this.state.deviceName;
36
+ const previousAddress = this.state.address;
37
+ this.batteryChar = null;
38
+ this.resetProtocolState();
39
+ this.state = {
40
+ ...createEmptyDeviceState(),
41
+ deviceName: previousDeviceName,
42
+ address: previousAddress,
43
+ };
44
+ if (options.emit) {
45
+ this.emit();
46
+ }
47
+ }
48
+ getState() {
49
+ return {
50
+ ...this.state,
51
+ waveActiveA: this.waveState.A.active,
52
+ waveActiveB: this.waveState.B.active,
53
+ currentWaveA: this.waveState.A.waveformId,
54
+ currentWaveB: this.waveState.B.waveformId,
55
+ };
56
+ }
57
+ async execute(command) {
58
+ if (!this.state.connected) {
59
+ throw new Error('设备未连接');
60
+ }
61
+ if (command.type !== 'emergencyStop') {
62
+ this.suppressStaleStopStrengthNotifications = false;
63
+ }
64
+ switch (command.type) {
65
+ case 'start':
66
+ this.setAbsoluteStrength(command.channel, command.strength);
67
+ this.setWave(command.channel, command.waveform.id, command.waveform.frames, command.loop);
68
+ break;
69
+ case 'stop':
70
+ if (command.channel) {
71
+ this.setAbsoluteStrength(command.channel, 0);
72
+ this.clearWave(command.channel);
73
+ }
74
+ else {
75
+ this.setAbsoluteStrength('A', 0);
76
+ this.setAbsoluteStrength('B', 0);
77
+ this.clearWave('A');
78
+ this.clearWave('B');
79
+ }
80
+ break;
81
+ case 'adjustStrength':
82
+ this.adjustStrength(command.channel, command.delta);
83
+ break;
84
+ case 'changeWave':
85
+ this.setWave(command.channel, command.waveform.id, command.waveform.frames, command.loop);
86
+ break;
87
+ case 'burst':
88
+ this.runBurst(command.channel, command.strength, command.durationMs);
89
+ break;
90
+ case 'emergencyStop':
91
+ await this.emergencyStop();
92
+ break;
93
+ }
94
+ this.emit();
95
+ return { state: this.getState() };
96
+ }
97
+ async emergencyStop() {
98
+ this.tickPaused = true;
99
+ this.suppressStaleStopStrengthNotifications = true;
100
+ this.stopTickLoop();
101
+ await this.waitForTickIdle();
102
+ this.cancelBurstRestore('A');
103
+ this.cancelBurstRestore('B');
104
+ this.clearWave('A');
105
+ this.clearWave('B');
106
+ this.state.strengthA = 0;
107
+ this.state.strengthB = 0;
108
+ this.resetEmergencyStopState();
109
+ await this.writeEmergencyStopPacket();
110
+ this.emit();
111
+ this.tickPaused = false;
112
+ if (this.state.connected) {
113
+ this.startTickLoop();
114
+ }
115
+ }
116
+ setConnectedDevice(device) {
117
+ this.state = {
118
+ ...createEmptyDeviceState(),
119
+ connected: true,
120
+ deviceName: device.name ?? '',
121
+ address: device.id ?? '',
122
+ limitA: 200,
123
+ limitB: 200,
124
+ };
125
+ }
126
+ startProtocolLoop() {
127
+ this.startTickLoop();
128
+ this.emit();
129
+ }
130
+ resetWaveState() {
131
+ this.waveState.A = { frames: null, index: 0, loop: false, active: false };
132
+ this.waveState.B = { frames: null, index: 0, loop: false, active: false };
133
+ }
134
+ setWave(channel, waveformId, frames, loop) {
135
+ this.waveState[channel] = {
136
+ waveformId,
137
+ frames: frames.map((frame) => [frame[0], frame[1]]),
138
+ index: 0,
139
+ loop,
140
+ active: true,
141
+ };
142
+ }
143
+ clearWave(channel) {
144
+ this.waveState[channel] = {
145
+ waveformId: undefined,
146
+ frames: null,
147
+ index: 0,
148
+ loop: false,
149
+ active: false,
150
+ };
151
+ }
152
+ runBurst(channel, strength, durationMs) {
153
+ this.cancelBurstRestore(channel);
154
+ const previous = channel === 'A' ? this.state.strengthA : this.state.strengthB;
155
+ this.setAbsoluteStrength(channel, strength);
156
+ const timer = setTimeout(() => {
157
+ const current = channel === 'A' ? this.state.strengthA : this.state.strengthB;
158
+ const target = Math.min(current, previous);
159
+ this.setAbsoluteStrength(channel, target);
160
+ this.burstRestores.delete(channel);
161
+ this.emit();
162
+ }, Math.max(100, durationMs));
163
+ this.burstRestores.set(channel, timer);
164
+ }
165
+ cancelBurstRestore(channel) {
166
+ const timer = this.burstRestores.get(channel);
167
+ if (timer) {
168
+ clearTimeout(timer);
169
+ this.burstRestores.delete(channel);
170
+ }
171
+ }
172
+ async onTick() {
173
+ if (this.tickPaused || this.tickInFlight || !this.state.connected) {
174
+ return;
175
+ }
176
+ this.tickInFlight = true;
177
+ try {
178
+ if (this.tickPaused || !this.state.connected) {
179
+ return;
180
+ }
181
+ await this.performTick();
182
+ if (!this.tickPaused) {
183
+ this.emit();
184
+ }
185
+ }
186
+ catch {
187
+ // GATT disconnected mid-tick; suppress and let disconnect handler clean up.
188
+ }
189
+ finally {
190
+ this.tickInFlight = false;
191
+ }
192
+ }
193
+ async waitForTickIdle() {
194
+ while (this.tickInFlight) {
195
+ await new Promise((resolve) => setTimeout(resolve, 0));
196
+ }
197
+ }
198
+ advanceWaveStep(channel) {
199
+ const current = this.waveState[channel];
200
+ if (!current.active || !current.frames || current.frames.length === 0) {
201
+ return null;
202
+ }
203
+ const length = current.frames.length;
204
+ if (current.index >= length) {
205
+ if (current.loop) {
206
+ current.index = 0;
207
+ }
208
+ else {
209
+ current.active = false;
210
+ return null;
211
+ }
212
+ }
213
+ const frame = current.frames[current.index];
214
+ if (!frame) {
215
+ current.active = false;
216
+ return null;
217
+ }
218
+ current.index += 1;
219
+ if (current.index >= length && !current.loop) {
220
+ current.active = false;
221
+ }
222
+ const [rawFreq, rawInt] = frame;
223
+ return {
224
+ freq: this.clamp(rawFreq, SILENT_WAVE_STEP.freq, 240),
225
+ int: this.clamp(rawInt, 0, 100),
226
+ };
227
+ }
228
+ advanceWavePacket(channel) {
229
+ const first = this.advanceWaveStep(channel);
230
+ if (!first) {
231
+ return { freq: [...INACTIVE_FREQ], int: [...INACTIVE_INT] };
232
+ }
233
+ const freq = [first.freq, SILENT_WAVE_STEP.freq, SILENT_WAVE_STEP.freq, SILENT_WAVE_STEP.freq];
234
+ const int = [first.int, SILENT_WAVE_STEP.int, SILENT_WAVE_STEP.int, SILENT_WAVE_STEP.int];
235
+ for (let index = 1; index < 4; index++) {
236
+ const next = this.advanceWaveStep(channel);
237
+ if (!next)
238
+ break;
239
+ freq[index] = next.freq;
240
+ int[index] = next.int;
241
+ }
242
+ return {
243
+ freq: freq,
244
+ int: int,
245
+ };
246
+ }
247
+ clamp(value, min, max) {
248
+ const number = Number(value);
249
+ if (!Number.isFinite(number))
250
+ return min;
251
+ return Math.max(min, Math.min(max, Math.round(number)));
252
+ }
253
+ toInt(value, fallback = 0) {
254
+ const number = typeof value === 'number' ? value : Number(value);
255
+ return Number.isFinite(number) ? Math.round(number) : fallback;
256
+ }
257
+ async readBattery() {
258
+ if (!this.batteryChar)
259
+ return;
260
+ try {
261
+ const value = await this.batteryChar.readValue();
262
+ this.state.battery = value.getUint8(0);
263
+ this.emit();
264
+ }
265
+ catch {
266
+ this.state.battery = 0;
267
+ }
268
+ }
269
+ shouldIgnoreStaleStopStrengthNotification(strengthA, strengthB) {
270
+ return this.suppressStaleStopStrengthNotifications && (strengthA !== 0 || strengthB !== 0);
271
+ }
272
+ async writeCharacteristicValue(characteristic, value, options = {}) {
273
+ const attempts = options.preferResponse
274
+ ? [
275
+ characteristic.writeValueWithResponse?.bind(characteristic),
276
+ characteristic.writeValueWithoutResponse?.bind(characteristic),
277
+ characteristic.writeValue?.bind(characteristic),
278
+ ]
279
+ : [
280
+ characteristic.writeValueWithoutResponse?.bind(characteristic),
281
+ characteristic.writeValueWithResponse?.bind(characteristic),
282
+ characteristic.writeValue?.bind(characteristic),
283
+ ];
284
+ let lastError = null;
285
+ for (const attempt of attempts) {
286
+ if (!attempt)
287
+ continue;
288
+ try {
289
+ await attempt(value);
290
+ return;
291
+ }
292
+ catch (error) {
293
+ lastError = error;
294
+ }
295
+ }
296
+ throw lastError ?? new Error('Bluetooth characteristic is not writable');
297
+ }
298
+ emit() {
299
+ const snapshot = this.getState();
300
+ for (const listener of this.listeners) {
301
+ listener(snapshot);
302
+ }
303
+ }
304
+ startTickLoop() {
305
+ if (this.tickWorker || this.tickInterval) {
306
+ return;
307
+ }
308
+ try {
309
+ this.tickWorker = this.createTickWorker();
310
+ this.tickWorker.onmessage = () => {
311
+ void this.onTick();
312
+ };
313
+ this.tickWorker.postMessage('start');
314
+ }
315
+ catch {
316
+ this.tickInterval = setInterval(() => {
317
+ void this.onTick();
318
+ }, 100);
319
+ }
320
+ }
321
+ stopTickLoop() {
322
+ if (this.tickWorker) {
323
+ this.tickWorker.postMessage('stop');
324
+ this.tickWorker.terminate();
325
+ this.tickWorker = null;
326
+ }
327
+ if (this.tickInterval) {
328
+ clearInterval(this.tickInterval);
329
+ this.tickInterval = null;
330
+ }
331
+ }
332
+ createTickWorker() {
333
+ const code = 'let timer;onmessage=(event)=>{if(event.data==="start"){if(timer)return;timer=setInterval(()=>postMessage(1),100);}else{clearInterval(timer);timer=null;}};';
334
+ const blob = new Blob([code], { type: 'application/javascript' });
335
+ return new Worker(URL.createObjectURL(blob));
336
+ }
337
+ }
338
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../src/base.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GAMvB,MAAM,cAAc,CAAC;AAuCtB,MAAM,CAAC,MAAM,aAAa,GAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,YAAY,GAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAa,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAE/D,MAAM,OAAgB,yBAAyB;IAC5B,SAAS,GAAG,IAAI,GAAG,EAAiB,CAAC;IACnC,SAAS,GAAsC;QAChE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACzD,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;KAC1D,CAAC;IACiB,aAAa,GAAG,IAAI,GAAG,EAA0C,CAAC;IAE3E,KAAK,GAAgB,sBAAsB,EAAE,CAAC;IAC9C,WAAW,GAAiD,IAAI,CAAC;IAEnE,UAAU,GAAkB,IAAI,CAAC;IACjC,YAAY,GAA0C,IAAI,CAAC;IAC3D,YAAY,GAAG,KAAK,CAAC;IAEnB,UAAU,GAAG,KAAK,CAAC;IACnB,sCAAsC,GAAG,KAAK,CAAC;IAEzD,SAAS,CAAC,QAAuB;QAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,IAAI,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAES,KAAK,CAAC,oBAAoB,CAAC,OAA0B;QAC7D,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,sCAAsC,GAAG,KAAK,CAAC;QACpD,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAE7B,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAEhC,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;QACjD,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG;YACX,GAAG,sBAAsB,EAAE;YAC3B,UAAU,EAAE,kBAAkB;YAC9B,OAAO,EAAE,eAAe;SACzB,CAAC;QACF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO;YACL,GAAG,IAAI,CAAC,KAAK;YACb,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YACpC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;YACpC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;YACzC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;SAC1C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACrC,IAAI,CAAC,sCAAsC,GAAG,KAAK,CAAC;QACtD,CAAC;QAED,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,OAAO;gBACV,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC5D,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC1F,MAAM;YACR,KAAK,MAAM;gBACT,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBACpB,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC7C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAClC,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBACjC,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBACjC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACpB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACtB,CAAC;gBACD,MAAM;YACR,KAAK,gBAAgB;gBACnB,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACpD,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC1F,MAAM;YACR,KAAK,OAAO;gBACV,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;gBACrE,MAAM;YACR,KAAK,eAAe;gBAClB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC3B,MAAM;QACV,CAAC;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,sCAAsC,GAAG,IAAI,CAAC;QACnD,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAE7B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAE/B,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QAEZ,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAES,kBAAkB,CAAC,MAA2B;QACtD,IAAI,CAAC,KAAK,GAAG;YACX,GAAG,sBAAsB,EAAE;YAC3B,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;YAC7B,OAAO,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,GAAG;SACZ,CAAC;IACJ,CAAC;IAES,iBAAiB;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAES,cAAc;QACtB,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC1E,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5E,CAAC;IAES,OAAO,CACf,OAAgB,EAChB,UAAkB,EAClB,MAAmB,EACnB,IAAa;QAEb,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG;YACxB,UAAU;YACV,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9D,KAAK,EAAE,CAAC;YACR,IAAI;YACJ,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;IAES,SAAS,CAAC,OAAgB;QAClC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG;YACxB,UAAU,EAAE,SAAS;YACrB,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,CAAC;YACR,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,KAAK;SACd,CAAC;IACJ,CAAC;IAES,QAAQ,CAAC,OAAgB,EAAE,QAAgB,EAAE,UAAkB;QACvE,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;QAC/E,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAE5C,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE;YACH,MAAM,OAAO,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;YAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3C,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAC1B,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAES,kBAAkB,CAAC,OAAgB;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAES,KAAK,CAAC,MAAM;QACpB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YAClE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC7C,OAAO;YACT,CAAC;YAED,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;QAC9E,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC5B,CAAC;IACH,CAAC;IAES,KAAK,CAAC,eAAe;QAC7B,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;YACzB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAES,eAAe,CAAC,OAAgB;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;QACrC,IAAI,OAAO,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjB,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;gBACvB,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;QACnB,IAAI,OAAO,CAAC,KAAK,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7C,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;QACzB,CAAC;QAED,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QAChC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC;YACrD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC;SAChC,CAAC;IACJ,CAAC;IAES,iBAAiB,CAAC,OAAgB;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,aAAa,CAAS,EAAE,GAAG,EAAE,CAAC,GAAG,YAAY,CAAS,EAAE,CAAC;QAC9E,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC/F,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAE1F,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,IAAI;gBAAE,MAAM;YACjB,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YACxB,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;QACxB,CAAC;QAED,OAAO;YACL,IAAI,EAAE,IAAY;YAClB,GAAG,EAAE,GAAW;SACjB,CAAC;IACJ,CAAC;IAES,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,GAAG,CAAC;QACzC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAES,KAAK,CAAC,KAAc,EAAE,QAAQ,GAAG,CAAC;QAC1C,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjE,CAAC;IAES,KAAK,CAAC,WAAW;QACzB,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YACjD,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAES,yCAAyC,CACjD,SAAiB,EACjB,SAAiB;QAEjB,OAAO,IAAI,CAAC,sCAAsC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,CAAC;IAC7F,CAAC;IAES,KAAK,CAAC,wBAAwB,CACtC,cAAqD,EACrD,KAAoC,EACpC,UAAwC,EAAE;QAE1C,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc;YACrC,CAAC,CAAC;gBACE,cAAc,CAAC,sBAAsB,EAAE,IAAI,CAAC,cAAc,CAAC;gBAC3D,cAAc,CAAC,yBAAyB,EAAE,IAAI,CAAC,cAAc,CAAC;gBAC9D,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC;aAChD;YACH,CAAC,CAAC;gBACE,cAAc,CAAC,yBAAyB,EAAE,IAAI,CAAC,cAAc,CAAC;gBAC9D,cAAc,CAAC,sBAAsB,EAAE,IAAI,CAAC,cAAc,CAAC;gBAC3D,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC;aAChD,CAAC;QAEN,IAAI,SAAS,GAAY,IAAI,CAAC;QAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,CAAC;YACpB,CAAC;QACH,CAAC;QAED,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC3E,CAAC;IAES,IAAI;QACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAEO,aAAa;QACnB,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1C,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE;gBAC/B,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,CAAC,CAAC;YACF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;gBACnC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACjC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,MAAM,IAAI,GACR,4JAA4J,CAAC;QAC/J,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAClE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;CAUF"}
@@ -0,0 +1,18 @@
1
+ import type { RequestDeviceOptionsLike } from './types.js';
2
+ export declare const V3_DEVICE_NAME_PREFIX = "47L121";
3
+ export declare const V3_SENSOR_NAME_PREFIX = "47L120";
4
+ export declare const V3_PRIMARY_SERVICE = "0000180c-0000-1000-8000-00805f9b34fb";
5
+ export declare const V3_WRITE_CHAR = "0000150a-0000-1000-8000-00805f9b34fb";
6
+ export declare const V3_NOTIFY_CHAR = "0000150b-0000-1000-8000-00805f9b34fb";
7
+ export declare const V3_BATTERY_SERVICE = "0000180a-0000-1000-8000-00805f9b34fb";
8
+ export declare const V3_BATTERY_CHAR = "00001500-0000-1000-8000-00805f9b34fb";
9
+ export declare const V2_DEVICE_NAME_PREFIX = "D-LAB ESTIM";
10
+ export declare const v2Uuid: (short: string) => string;
11
+ export declare const V2_PRIMARY_SERVICE: string;
12
+ export declare const V2_STRENGTH_CHAR: string;
13
+ export declare const V2_WAVE_A_CHAR: string;
14
+ export declare const V2_WAVE_B_CHAR: string;
15
+ export declare const V2_BATTERY_SERVICE: string;
16
+ export declare const V2_BATTERY_CHAR: string;
17
+ export declare const COYOTE_REQUEST_DEVICE_OPTIONS: RequestDeviceOptionsLike;
18
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAC9C,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAC9C,eAAO,MAAM,kBAAkB,yCAAyC,CAAC;AACzE,eAAO,MAAM,aAAa,yCAAyC,CAAC;AACpE,eAAO,MAAM,cAAc,yCAAyC,CAAC;AACrE,eAAO,MAAM,kBAAkB,yCAAyC,CAAC;AACzE,eAAO,MAAM,eAAe,yCAAyC,CAAC;AAGtE,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AACnD,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,KAAG,MAAoD,CAAC;AAC5F,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AACjD,eAAO,MAAM,gBAAgB,QAAiB,CAAC;AAC/C,eAAO,MAAM,cAAc,QAAiB,CAAC;AAC7C,eAAO,MAAM,cAAc,QAAiB,CAAC;AAC7C,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AACjD,eAAO,MAAM,eAAe,QAAiB,CAAC;AAE9C,eAAO,MAAM,6BAA6B,EAAE,wBAY3C,CAAC"}
@@ -0,0 +1,31 @@
1
+ // V3 (Coyote 3.0)
2
+ export const V3_DEVICE_NAME_PREFIX = '47L121';
3
+ export const V3_SENSOR_NAME_PREFIX = '47L120';
4
+ export const V3_PRIMARY_SERVICE = '0000180c-0000-1000-8000-00805f9b34fb';
5
+ export const V3_WRITE_CHAR = '0000150a-0000-1000-8000-00805f9b34fb';
6
+ export const V3_NOTIFY_CHAR = '0000150b-0000-1000-8000-00805f9b34fb';
7
+ export const V3_BATTERY_SERVICE = '0000180a-0000-1000-8000-00805f9b34fb';
8
+ export const V3_BATTERY_CHAR = '00001500-0000-1000-8000-00805f9b34fb';
9
+ // V2 (Coyote 2.0)
10
+ export const V2_DEVICE_NAME_PREFIX = 'D-LAB ESTIM';
11
+ export const v2Uuid = (short) => `955a${short}-0fe2-f5aa-a094-84b8d4f3e8ad`;
12
+ export const V2_PRIMARY_SERVICE = v2Uuid('180b');
13
+ export const V2_STRENGTH_CHAR = v2Uuid('1504');
14
+ export const V2_WAVE_A_CHAR = v2Uuid('1505');
15
+ export const V2_WAVE_B_CHAR = v2Uuid('1506');
16
+ export const V2_BATTERY_SERVICE = v2Uuid('180a');
17
+ export const V2_BATTERY_CHAR = v2Uuid('1500');
18
+ export const COYOTE_REQUEST_DEVICE_OPTIONS = {
19
+ filters: [
20
+ { namePrefix: V3_DEVICE_NAME_PREFIX },
21
+ { namePrefix: V3_SENSOR_NAME_PREFIX },
22
+ { namePrefix: V2_DEVICE_NAME_PREFIX },
23
+ ],
24
+ optionalServices: [
25
+ V3_PRIMARY_SERVICE,
26
+ V3_BATTERY_SERVICE,
27
+ V2_PRIMARY_SERVICE,
28
+ V2_BATTERY_SERVICE,
29
+ ],
30
+ };
31
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,kBAAkB;AAClB,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAC9C,MAAM,CAAC,MAAM,kBAAkB,GAAG,sCAAsC,CAAC;AACzE,MAAM,CAAC,MAAM,aAAa,GAAG,sCAAsC,CAAC;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,sCAAsC,CAAC;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,sCAAsC,CAAC;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAEtE,kBAAkB;AAClB,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AACnD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,OAAO,KAAK,8BAA8B,CAAC;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAC7C,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AACjD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAE9C,MAAM,CAAC,MAAM,6BAA6B,GAA6B;IACrE,OAAO,EAAE;QACP,EAAE,UAAU,EAAE,qBAAqB,EAAE;QACrC,EAAE,UAAU,EAAE,qBAAqB,EAAE;QACrC,EAAE,UAAU,EAAE,qBAAqB,EAAE;KACtC;IACD,gBAAgB,EAAE;QAChB,kBAAkB;QAClB,kBAAkB;QAClB,kBAAkB;QAClB,kBAAkB;KACnB;CACF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { DeviceCommand, DeviceCommandResult, DeviceState } from '@dg-kit/core';
2
+ import { type StateListener, type WebBluetoothConnectionContext, type WebBluetoothProtocolAdapter } from './base.js';
3
+ /**
4
+ * Auto-routing protocol adapter. Picks the V2 or V3 implementation based on
5
+ * the connecting device's name prefix; defaults to V3.
6
+ */
7
+ export declare class CoyoteProtocolAdapter implements WebBluetoothProtocolAdapter {
8
+ private readonly listeners;
9
+ private activeProtocol;
10
+ private unsubscribeActiveProtocol;
11
+ constructor();
12
+ subscribe(listener: StateListener): () => void;
13
+ onConnected(context: WebBluetoothConnectionContext): Promise<void>;
14
+ onDisconnected(): Promise<void>;
15
+ getState(): DeviceState;
16
+ execute(command: DeviceCommand): Promise<DeviceCommandResult>;
17
+ emergencyStop(): Promise<void>;
18
+ private createProtocol;
19
+ private bindActiveProtocol;
20
+ private emit;
21
+ }
22
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EACjC,MAAM,WAAW,CAAC;AAInB;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,2BAA2B;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,cAAc,CAA8D;IACpF,OAAO,CAAC,yBAAyB,CAA6B;;IAM9D,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAOxC,WAAW,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBlE,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrC,QAAQ,IAAI,WAAW;IAIjB,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI7D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,IAAI;CAKb"}
package/dist/facade.js ADDED
@@ -0,0 +1,76 @@
1
+ import { V2_DEVICE_NAME_PREFIX } from './constants.js';
2
+ import {} from './base.js';
3
+ import { CoyoteV2ProtocolAdapter } from './v2.js';
4
+ import { CoyoteV3ProtocolAdapter } from './v3.js';
5
+ /**
6
+ * Auto-routing protocol adapter. Picks the V2 or V3 implementation based on
7
+ * the connecting device's name prefix; defaults to V3.
8
+ */
9
+ export class CoyoteProtocolAdapter {
10
+ listeners = new Set();
11
+ activeProtocol = new CoyoteV3ProtocolAdapter();
12
+ unsubscribeActiveProtocol = null;
13
+ constructor() {
14
+ this.bindActiveProtocol(this.activeProtocol);
15
+ }
16
+ subscribe(listener) {
17
+ this.listeners.add(listener);
18
+ return () => {
19
+ this.listeners.delete(listener);
20
+ };
21
+ }
22
+ async onConnected(context) {
23
+ const nextProtocol = this.createProtocol(context);
24
+ await nextProtocol.onConnected(context);
25
+ const previousProtocol = this.activeProtocol;
26
+ if (previousProtocol !== nextProtocol) {
27
+ try {
28
+ await previousProtocol.onDisconnected();
29
+ }
30
+ catch (error) {
31
+ try {
32
+ await nextProtocol.onDisconnected();
33
+ }
34
+ catch {
35
+ // ignore cleanup failure; preserve the original disconnect error
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+ this.bindActiveProtocol(nextProtocol);
41
+ this.emit(nextProtocol.getState());
42
+ }
43
+ async onDisconnected() {
44
+ await this.activeProtocol.onDisconnected();
45
+ }
46
+ getState() {
47
+ return this.activeProtocol.getState();
48
+ }
49
+ async execute(command) {
50
+ return this.activeProtocol.execute(command);
51
+ }
52
+ async emergencyStop() {
53
+ await this.activeProtocol.emergencyStop();
54
+ }
55
+ createProtocol(context) {
56
+ const name = context.device.name ?? '';
57
+ return name.startsWith(V2_DEVICE_NAME_PREFIX)
58
+ ? new CoyoteV2ProtocolAdapter()
59
+ : new CoyoteV3ProtocolAdapter();
60
+ }
61
+ bindActiveProtocol(protocol) {
62
+ this.unsubscribeActiveProtocol?.();
63
+ this.activeProtocol = protocol;
64
+ this.unsubscribeActiveProtocol = protocol.subscribe((state) => {
65
+ if (this.activeProtocol === protocol) {
66
+ this.emit(state);
67
+ }
68
+ });
69
+ }
70
+ emit(state) {
71
+ for (const listener of this.listeners) {
72
+ listener(state);
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=facade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade.js","sourceRoot":"","sources":["../src/facade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from './constants.js';
2
+ export * from './types.js';
3
+ export { BaseCoyoteProtocolAdapter, INACTIVE_FREQ, INACTIVE_INT, SILENT_WAVE_STEP, type ChannelWaveState, type Quad, type StateListener, type WaveStep, type WebBluetoothAvailability, type WebBluetoothConnectionContext, type WebBluetoothProtocolAdapter, } from './base.js';
4
+ export { CoyoteV2ProtocolAdapter } from './v2.js';
5
+ export { CoyoteV3ProtocolAdapter } from './v3.js';
6
+ export { CoyoteProtocolAdapter } from './facade.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,yBAAyB,EACzB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,GACjC,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './constants.js';
2
+ export * from './types.js';
3
+ export { BaseCoyoteProtocolAdapter, INACTIVE_FREQ, INACTIVE_INT, SILENT_WAVE_STEP, } from './base.js';
4
+ export { CoyoteV2ProtocolAdapter } from './v2.js';
5
+ export { CoyoteV3ProtocolAdapter } from './v3.js';
6
+ export { CoyoteProtocolAdapter } from './facade.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,yBAAyB,EACzB,aAAa,EACb,YAAY,EACZ,gBAAgB,GAQjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Minimal BLE characteristic shape needed by the protocol adapters.
3
+ *
4
+ * Both Web Bluetooth's `BluetoothRemoteGATTCharacteristic` and a Noble
5
+ * `Characteristic` shim must satisfy this interface for the protocol code to
6
+ * drive them.
7
+ */
8
+ export interface BluetoothRequestFilterLike {
9
+ namePrefix?: string;
10
+ }
11
+ export interface RequestDeviceOptionsLike {
12
+ filters?: BluetoothRequestFilterLike[];
13
+ optionalServices?: string[];
14
+ }
15
+ export interface BluetoothRemoteGATTCharacteristicLike extends EventTarget {
16
+ value: DataView | null;
17
+ writeValueWithoutResponse?(value: ArrayBufferView | ArrayBuffer): Promise<void>;
18
+ writeValueWithResponse?(value: ArrayBufferView | ArrayBuffer): Promise<void>;
19
+ writeValue?(value: ArrayBufferView | ArrayBuffer): Promise<void>;
20
+ readValue(): Promise<DataView>;
21
+ startNotifications(): Promise<BluetoothRemoteGATTCharacteristicLike>;
22
+ stopNotifications(): Promise<BluetoothRemoteGATTCharacteristicLike>;
23
+ }
24
+ export interface BluetoothRemoteGATTServiceLike {
25
+ getCharacteristic(characteristic: string): Promise<BluetoothRemoteGATTCharacteristicLike>;
26
+ }
27
+ export interface BluetoothRemoteGATTServerLike {
28
+ connected: boolean;
29
+ getPrimaryService(service: string): Promise<BluetoothRemoteGATTServiceLike>;
30
+ }
31
+ export interface BluetoothRemoteGATTLike {
32
+ connected: boolean;
33
+ connect(): Promise<BluetoothRemoteGATTServerLike>;
34
+ disconnect(): void;
35
+ }
36
+ export interface BluetoothDeviceLike extends EventTarget {
37
+ id?: string;
38
+ name?: string;
39
+ gatt?: BluetoothRemoteGATTLike;
40
+ }
41
+ export interface BluetoothLike {
42
+ requestDevice(options: RequestDeviceOptionsLike): Promise<BluetoothDeviceLike>;
43
+ }
44
+ export interface NavigatorBluetoothLike {
45
+ bluetooth?: BluetoothLike;
46
+ }
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACvC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,qCAAsC,SAAQ,WAAW;IACxE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,yBAAyB,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,sBAAsB,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,UAAU,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,kBAAkB,IAAI,OAAO,CAAC,qCAAqC,CAAC,CAAC;IACrE,iBAAiB,IAAI,OAAO,CAAC,qCAAqC,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,8BAA8B;IAC7C,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,qCAAqC,CAAC,CAAC;CAC3F;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;CAC7E;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,IAAI,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAClD,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,uBAAuB,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/v2.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Channel } from '@dg-kit/core';
2
+ import { BaseCoyoteProtocolAdapter, type WebBluetoothConnectionContext } from './base.js';
3
+ import type { BluetoothRemoteGATTCharacteristicLike } from './types.js';
4
+ export declare class CoyoteV2ProtocolAdapter extends BaseCoyoteProtocolAdapter {
5
+ protected v2StrengthChar: BluetoothRemoteGATTCharacteristicLike | null;
6
+ protected v2WaveAChar: BluetoothRemoteGATTCharacteristicLike | null;
7
+ protected v2WaveBChar: BluetoothRemoteGATTCharacteristicLike | null;
8
+ protected pendingStrA: number;
9
+ protected pendingStrB: number;
10
+ onConnected(context: WebBluetoothConnectionContext): Promise<void>;
11
+ protected disconnectProtocol(): Promise<void>;
12
+ protected resetProtocolState(): void;
13
+ protected resetEmergencyStopState(): void;
14
+ protected setAbsoluteStrength(channel: Channel, value: number): void;
15
+ protected adjustStrength(channel: Channel, delta: number): void;
16
+ protected performTick(): Promise<void>;
17
+ protected writeEmergencyStopPacket(): Promise<void>;
18
+ private encodeV2Strength;
19
+ private encodeV2Wave;
20
+ private decodeV3Freq;
21
+ private waveFrameToV2;
22
+ readonly handleV2StrengthNotification: (event: Event) => void;
23
+ }
24
+ //# sourceMappingURL=v2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../src/v2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAS5C,OAAO,EAAE,yBAAyB,EAAE,KAAK,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1F,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,uBAAwB,SAAQ,yBAAyB;IACpE,SAAS,CAAC,cAAc,EAAE,qCAAqC,GAAG,IAAI,CAAQ;IAC9E,SAAS,CAAC,WAAW,EAAE,qCAAqC,GAAG,IAAI,CAAQ;IAC3E,SAAS,CAAC,WAAW,EAAE,qCAAqC,GAAG,IAAI,CAAQ;IAC3E,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,WAAW,SAAK;IAEpB,WAAW,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;cA8CxD,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnD,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAMpC,SAAS,CAAC,uBAAuB,IAAI,IAAI;IAKzC,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAWpE,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;cAQ/C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;cAgD5B,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAWzD,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;IAUrB,QAAQ,CAAC,4BAA4B,GAAI,OAAO,KAAK,KAAG,IAAI,CAkB1D;CACH"}
package/dist/v2.js ADDED
@@ -0,0 +1,180 @@
1
+ import { V2_BATTERY_CHAR, V2_BATTERY_SERVICE, V2_PRIMARY_SERVICE, V2_STRENGTH_CHAR, V2_WAVE_A_CHAR, V2_WAVE_B_CHAR, } from './constants.js';
2
+ import { BaseCoyoteProtocolAdapter } from './base.js';
3
+ export class CoyoteV2ProtocolAdapter extends BaseCoyoteProtocolAdapter {
4
+ v2StrengthChar = null;
5
+ v2WaveAChar = null;
6
+ v2WaveBChar = null;
7
+ pendingStrA = 0;
8
+ pendingStrB = 0;
9
+ async onConnected(context) {
10
+ await this.resetConnectionState({ emit: false });
11
+ try {
12
+ this.setConnectedDevice(context.device);
13
+ const primaryService = await context.server.getPrimaryService(V2_PRIMARY_SERVICE);
14
+ this.v2StrengthChar = await primaryService.getCharacteristic(V2_STRENGTH_CHAR);
15
+ this.v2WaveAChar = await primaryService.getCharacteristic(V2_WAVE_A_CHAR);
16
+ this.v2WaveBChar = await primaryService.getCharacteristic(V2_WAVE_B_CHAR);
17
+ await this.v2StrengthChar.startNotifications();
18
+ this.v2StrengthChar.addEventListener('characteristicvaluechanged', this.handleV2StrengthNotification);
19
+ try {
20
+ const batteryService = await context.server.getPrimaryService(V2_BATTERY_SERVICE);
21
+ this.batteryChar = await batteryService.getCharacteristic(V2_BATTERY_CHAR);
22
+ await this.readBattery();
23
+ }
24
+ catch {
25
+ this.state.battery = 0;
26
+ }
27
+ }
28
+ catch (error) {
29
+ await this.resetConnectionState({ emit: false });
30
+ throw error;
31
+ }
32
+ try {
33
+ if (!this.v2StrengthChar) {
34
+ throw new Error('V2 strength characteristic unavailable');
35
+ }
36
+ if (!this.v2WaveAChar || !this.v2WaveBChar) {
37
+ throw new Error('V2 wave characteristics unavailable');
38
+ }
39
+ this.resetProtocolState();
40
+ await this.writeCharacteristicValue(this.v2StrengthChar, this.encodeV2Strength(0, 0), {
41
+ preferResponse: true,
42
+ });
43
+ this.startProtocolLoop();
44
+ }
45
+ catch (error) {
46
+ await this.resetConnectionState({ emit: false });
47
+ throw error;
48
+ }
49
+ }
50
+ async disconnectProtocol() {
51
+ if (this.v2StrengthChar) {
52
+ this.v2StrengthChar.removeEventListener('characteristicvaluechanged', this.handleV2StrengthNotification);
53
+ try {
54
+ await this.v2StrengthChar.stopNotifications();
55
+ }
56
+ catch {
57
+ // ignore
58
+ }
59
+ }
60
+ this.v2StrengthChar = null;
61
+ this.v2WaveAChar = null;
62
+ this.v2WaveBChar = null;
63
+ }
64
+ resetProtocolState() {
65
+ this.pendingStrA = 0;
66
+ this.pendingStrB = 0;
67
+ this.resetWaveState();
68
+ }
69
+ resetEmergencyStopState() {
70
+ this.pendingStrA = 0;
71
+ this.pendingStrB = 0;
72
+ }
73
+ setAbsoluteStrength(channel, value) {
74
+ const next = this.clamp(value, 0, 200);
75
+ if (channel === 'A') {
76
+ this.pendingStrA = next;
77
+ this.state.strengthA = next;
78
+ }
79
+ else {
80
+ this.pendingStrB = next;
81
+ this.state.strengthB = next;
82
+ }
83
+ }
84
+ adjustStrength(channel, delta) {
85
+ const next = channel === 'A'
86
+ ? this.clamp(this.state.strengthA + this.toInt(delta), 0, 200)
87
+ : this.clamp(this.state.strengthB + this.toInt(delta), 0, 200);
88
+ this.setAbsoluteStrength(channel, next);
89
+ }
90
+ async performTick() {
91
+ if (!this.v2StrengthChar)
92
+ return;
93
+ const strengthA = Math.min(this.pendingStrA, this.state.limitA);
94
+ const strengthB = Math.min(this.pendingStrB, this.state.limitB);
95
+ await this.writeCharacteristicValue(this.v2StrengthChar, this.encodeV2Strength(strengthA, strengthB), { preferResponse: true });
96
+ this.state.strengthA = strengthA;
97
+ this.state.strengthB = strengthB;
98
+ // V2 wave chars are crossed: PWM_A34 (v2WaveAChar) drives B, PWM_B34 drives A.
99
+ if (this.v2WaveBChar) {
100
+ const next = this.advanceWaveStep('A');
101
+ if (!next) {
102
+ await this.writeCharacteristicValue(this.v2WaveBChar, this.encodeV2Wave(0, 0, 0), {
103
+ preferResponse: true,
104
+ });
105
+ }
106
+ else {
107
+ const params = this.waveFrameToV2(next.freq, next.int);
108
+ await this.writeCharacteristicValue(this.v2WaveBChar, this.encodeV2Wave(params.x, params.y, params.z), { preferResponse: true });
109
+ }
110
+ }
111
+ if (this.v2WaveAChar) {
112
+ const next = this.advanceWaveStep('B');
113
+ if (!next) {
114
+ await this.writeCharacteristicValue(this.v2WaveAChar, this.encodeV2Wave(0, 0, 0), {
115
+ preferResponse: true,
116
+ });
117
+ }
118
+ else {
119
+ const params = this.waveFrameToV2(next.freq, next.int);
120
+ await this.writeCharacteristicValue(this.v2WaveAChar, this.encodeV2Wave(params.x, params.y, params.z), { preferResponse: true });
121
+ }
122
+ }
123
+ }
124
+ async writeEmergencyStopPacket() {
125
+ if (!this.v2StrengthChar)
126
+ return;
127
+ try {
128
+ await this.writeCharacteristicValue(this.v2StrengthChar, this.encodeV2Strength(0, 0), {
129
+ preferResponse: true,
130
+ });
131
+ }
132
+ catch {
133
+ // ignore best effort
134
+ }
135
+ }
136
+ encodeV2Strength(a, b) {
137
+ const valueA = Math.round((this.clamp(a, 0, 200) * 2047) / 200);
138
+ const valueB = Math.round((this.clamp(b, 0, 200) * 2047) / 200);
139
+ const combined = (valueA << 11) | valueB;
140
+ return new Uint8Array([(combined >> 16) & 0xff, (combined >> 8) & 0xff, combined & 0xff]);
141
+ }
142
+ encodeV2Wave(x, y, z) {
143
+ const packed = ((z & 0x1f) << 15) | ((y & 0x3ff) << 5) | (x & 0x1f);
144
+ return new Uint8Array([(packed >> 16) & 0xff, (packed >> 8) & 0xff, packed & 0xff]);
145
+ }
146
+ decodeV3Freq(encoded) {
147
+ if (encoded <= 100)
148
+ return encoded;
149
+ if (encoded <= 200)
150
+ return (encoded - 100) * 5 + 100;
151
+ return (encoded - 200) * 10 + 600;
152
+ }
153
+ waveFrameToV2(freq, intensity) {
154
+ const periodMs = this.decodeV3Freq(freq);
155
+ const x = this.clamp(Math.round(Math.sqrt(periodMs / 1000) * 15), 1, 31);
156
+ return {
157
+ x,
158
+ y: this.clamp(periodMs - x, 0, 1023),
159
+ z: this.clamp(Math.round((intensity * 31) / 100), 0, 31),
160
+ };
161
+ }
162
+ handleV2StrengthNotification = (event) => {
163
+ const target = event.target;
164
+ const value = target?.value;
165
+ if (!value || value.byteLength < 3)
166
+ return;
167
+ const raw = (value.getUint8(0) << 16) | (value.getUint8(1) << 8) | value.getUint8(2);
168
+ const rawA = (raw >> 11) & 0x7ff;
169
+ const rawB = raw & 0x7ff;
170
+ const nextStrengthA = Math.round((rawA * 200) / 2047);
171
+ const nextStrengthB = Math.round((rawB * 200) / 2047);
172
+ if (this.shouldIgnoreStaleStopStrengthNotification(nextStrengthA, nextStrengthB)) {
173
+ return;
174
+ }
175
+ this.state.strengthA = nextStrengthA;
176
+ this.state.strengthB = nextStrengthB;
177
+ this.emit();
178
+ };
179
+ }
180
+ //# sourceMappingURL=v2.js.map
package/dist/v2.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2.js","sourceRoot":"","sources":["../src/v2.ts"],"names":[],"mappings":"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"}
package/dist/v3.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import type { Channel } from '@dg-kit/core';
2
+ import { BaseCoyoteProtocolAdapter, type WebBluetoothConnectionContext } from './base.js';
3
+ import type { BluetoothRemoteGATTCharacteristicLike } from './types.js';
4
+ export declare class CoyoteV3ProtocolAdapter extends BaseCoyoteProtocolAdapter {
5
+ protected writeChar: BluetoothRemoteGATTCharacteristicLike | null;
6
+ protected notifyChar: BluetoothRemoteGATTCharacteristicLike | null;
7
+ protected seq: number;
8
+ protected pendingMode: number;
9
+ protected pendingStrA: number;
10
+ protected pendingStrB: number;
11
+ protected awaitingAck: boolean;
12
+ onConnected(context: WebBluetoothConnectionContext): Promise<void>;
13
+ protected disconnectProtocol(): Promise<void>;
14
+ protected resetProtocolState(): void;
15
+ protected resetEmergencyStopState(): void;
16
+ protected setAbsoluteStrength(channel: Channel, value: number): void;
17
+ protected adjustStrength(channel: Channel, delta: number): void;
18
+ protected performTick(): Promise<void>;
19
+ protected writeEmergencyStopPacket(): Promise<void>;
20
+ private buildB0;
21
+ private buildImmediateAbsoluteStrengthPacket;
22
+ private buildBF;
23
+ private writeBF;
24
+ private nextSeq;
25
+ readonly handleV3Notification: (event: Event) => void;
26
+ }
27
+ //# sourceMappingURL=v3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../src/v3.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ5C,OAAO,EACL,yBAAyB,EAGzB,KAAK,6BAA6B,EACnC,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,YAAY,CAAC;AAExE,qBAAa,uBAAwB,SAAQ,yBAAyB;IACpE,SAAS,CAAC,SAAS,EAAE,qCAAqC,GAAG,IAAI,CAAQ;IACzE,SAAS,CAAC,UAAU,EAAE,qCAAqC,GAAG,IAAI,CAAQ;IAE1E,SAAS,CAAC,GAAG,SAAK;IAClB,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,WAAW,UAAS;IAExB,WAAW,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC;cAkCxD,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcnD,SAAS,CAAC,kBAAkB,IAAI,IAAI;IASpC,SAAS,CAAC,uBAAuB,IAAI,IAAI;IAQzC,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAcpE,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;cAoB/C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;cAK5B,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYzD,OAAO,CAAC,OAAO;IA8Bf,OAAO,CAAC,oCAAoC;IAa5C,OAAO,CAAC,OAAO;YAYD,OAAO;IAKrB,OAAO,CAAC,OAAO;IAIf,QAAQ,CAAC,oBAAoB,GAAI,OAAO,KAAK,KAAG,IAAI,CAuBlD;CACH"}
package/dist/v3.js ADDED
@@ -0,0 +1,193 @@
1
+ import { V3_BATTERY_CHAR, V3_BATTERY_SERVICE, V3_NOTIFY_CHAR, V3_PRIMARY_SERVICE, V3_WRITE_CHAR, } from './constants.js';
2
+ import { BaseCoyoteProtocolAdapter, INACTIVE_FREQ, INACTIVE_INT, } from './base.js';
3
+ export class CoyoteV3ProtocolAdapter extends BaseCoyoteProtocolAdapter {
4
+ writeChar = null;
5
+ notifyChar = null;
6
+ seq = 0;
7
+ pendingMode = 0;
8
+ pendingStrA = 0;
9
+ pendingStrB = 0;
10
+ awaitingAck = false;
11
+ async onConnected(context) {
12
+ await this.resetConnectionState({ emit: false });
13
+ try {
14
+ this.setConnectedDevice(context.device);
15
+ const primaryService = await context.server.getPrimaryService(V3_PRIMARY_SERVICE);
16
+ this.writeChar = await primaryService.getCharacteristic(V3_WRITE_CHAR);
17
+ this.notifyChar = await primaryService.getCharacteristic(V3_NOTIFY_CHAR);
18
+ await this.notifyChar.startNotifications();
19
+ this.notifyChar.addEventListener('characteristicvaluechanged', this.handleV3Notification);
20
+ try {
21
+ const batteryService = await context.server.getPrimaryService(V3_BATTERY_SERVICE);
22
+ this.batteryChar = await batteryService.getCharacteristic(V3_BATTERY_CHAR);
23
+ await this.readBattery();
24
+ }
25
+ catch {
26
+ this.state.battery = 0;
27
+ }
28
+ this.resetProtocolState();
29
+ if (!this.writeChar || !this.notifyChar) {
30
+ throw new Error('V3 transport characteristics unavailable');
31
+ }
32
+ await this.writeBF(this.state.limitA, this.state.limitB);
33
+ this.pendingMode = (3 << 2) | 3;
34
+ this.pendingStrA = 0;
35
+ this.pendingStrB = 0;
36
+ this.startProtocolLoop();
37
+ }
38
+ catch (error) {
39
+ await this.resetConnectionState({ emit: false });
40
+ throw error;
41
+ }
42
+ }
43
+ async disconnectProtocol() {
44
+ if (this.notifyChar) {
45
+ this.notifyChar.removeEventListener('characteristicvaluechanged', this.handleV3Notification);
46
+ try {
47
+ await this.notifyChar.stopNotifications();
48
+ }
49
+ catch {
50
+ // ignore
51
+ }
52
+ }
53
+ this.writeChar = null;
54
+ this.notifyChar = null;
55
+ }
56
+ resetProtocolState() {
57
+ this.seq = 0;
58
+ this.pendingMode = 0;
59
+ this.pendingStrA = 0;
60
+ this.pendingStrB = 0;
61
+ this.awaitingAck = false;
62
+ this.resetWaveState();
63
+ }
64
+ resetEmergencyStopState() {
65
+ this.pendingStrA = 0;
66
+ this.pendingStrB = 0;
67
+ this.awaitingAck = false;
68
+ this.seq = 0;
69
+ this.pendingMode = 0;
70
+ }
71
+ setAbsoluteStrength(channel, value) {
72
+ const next = this.clamp(value, 0, 200);
73
+ if (channel === 'A') {
74
+ this.pendingStrA = next;
75
+ this.state.strengthA = next;
76
+ this.pendingMode = (this.pendingMode & 0x03) | (3 << 2);
77
+ }
78
+ else {
79
+ this.pendingStrB = next;
80
+ this.state.strengthB = next;
81
+ this.pendingMode = (this.pendingMode & 0x0c) | 3;
82
+ }
83
+ }
84
+ adjustStrength(channel, delta) {
85
+ const next = channel === 'A'
86
+ ? this.clamp(this.state.strengthA + this.toInt(delta), 0, 200)
87
+ : this.clamp(this.state.strengthB + this.toInt(delta), 0, 200);
88
+ const signedDelta = this.toInt(delta, 0);
89
+ const mode = signedDelta >= 0 ? 1 : 2;
90
+ const magnitude = this.clamp(Math.abs(signedDelta), 0, 200);
91
+ if (channel === 'A') {
92
+ this.pendingStrA = magnitude;
93
+ this.state.strengthA = next;
94
+ this.pendingMode = (this.pendingMode & 0x03) | (mode << 2);
95
+ }
96
+ else {
97
+ this.pendingStrB = magnitude;
98
+ this.state.strengthB = next;
99
+ this.pendingMode = (this.pendingMode & 0x0c) | mode;
100
+ }
101
+ }
102
+ async performTick() {
103
+ if (!this.writeChar)
104
+ return;
105
+ await this.writeCharacteristicValue(this.writeChar, this.buildB0());
106
+ }
107
+ async writeEmergencyStopPacket() {
108
+ if (!this.writeChar)
109
+ return;
110
+ try {
111
+ await this.writeCharacteristicValue(this.writeChar, this.buildImmediateAbsoluteStrengthPacket(0, 0));
112
+ }
113
+ catch {
114
+ // ignore best effort
115
+ }
116
+ }
117
+ buildB0() {
118
+ const buffer = new Uint8Array(20);
119
+ buffer[0] = 0xb0;
120
+ let modeNibble = 0;
121
+ const strengthA = this.pendingStrA;
122
+ const strengthB = this.pendingStrB;
123
+ if (!this.awaitingAck && this.pendingMode !== 0) {
124
+ this.seq = this.nextSeq();
125
+ modeNibble = this.pendingMode;
126
+ this.awaitingAck = true;
127
+ this.pendingMode = 0;
128
+ }
129
+ buffer[1] = ((this.seq & 0x0f) << 4) | (modeNibble & 0x0f);
130
+ buffer[2] = this.clamp(strengthA, 0, 200);
131
+ buffer[3] = this.clamp(strengthB, 0, 200);
132
+ const channelA = this.advanceWavePacket('A');
133
+ const channelB = this.advanceWavePacket('B');
134
+ buffer.set(channelA.freq, 4);
135
+ buffer.set(channelA.int, 8);
136
+ buffer.set(channelB.freq, 12);
137
+ buffer.set(channelB.int, 16);
138
+ return buffer;
139
+ }
140
+ buildImmediateAbsoluteStrengthPacket(strengthA, strengthB) {
141
+ const buffer = new Uint8Array(20);
142
+ buffer[0] = 0xb0;
143
+ buffer[1] = 0x0f;
144
+ buffer[2] = this.clamp(strengthA, 0, 200);
145
+ buffer[3] = this.clamp(strengthB, 0, 200);
146
+ buffer.set(INACTIVE_FREQ, 4);
147
+ buffer.set(INACTIVE_INT, 8);
148
+ buffer.set(INACTIVE_FREQ, 12);
149
+ buffer.set(INACTIVE_INT, 16);
150
+ return buffer;
151
+ }
152
+ buildBF(limitA, limitB) {
153
+ const buffer = new Uint8Array(7);
154
+ buffer[0] = 0xbf;
155
+ buffer[1] = this.clamp(limitA, 0, 200);
156
+ buffer[2] = this.clamp(limitB, 0, 200);
157
+ buffer[3] = 160;
158
+ buffer[4] = 160;
159
+ buffer[5] = 0;
160
+ buffer[6] = 0;
161
+ return buffer;
162
+ }
163
+ async writeBF(limitA, limitB) {
164
+ if (!this.writeChar)
165
+ return;
166
+ await this.writeCharacteristicValue(this.writeChar, this.buildBF(limitA, limitB));
167
+ }
168
+ nextSeq() {
169
+ return this.seq >= 15 ? 1 : this.seq + 1;
170
+ }
171
+ handleV3Notification = (event) => {
172
+ const target = event.target;
173
+ const value = target?.value;
174
+ if (!value || value.byteLength < 4)
175
+ return;
176
+ if (value.getUint8(0) !== 0xb1)
177
+ return;
178
+ const ackSeq = value.getUint8(1);
179
+ const nextStrengthA = value.getUint8(2);
180
+ const nextStrengthB = value.getUint8(3);
181
+ if (this.shouldIgnoreStaleStopStrengthNotification(nextStrengthA, nextStrengthB)) {
182
+ return;
183
+ }
184
+ this.state.strengthA = nextStrengthA;
185
+ this.state.strengthB = nextStrengthB;
186
+ if (this.awaitingAck && ackSeq === this.seq) {
187
+ this.awaitingAck = false;
188
+ this.seq = 0;
189
+ }
190
+ this.emit();
191
+ };
192
+ }
193
+ //# sourceMappingURL=v3.js.map
package/dist/v3.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3.js","sourceRoot":"","sources":["../src/v3.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@dg-kit/protocol",
3
+ "version": "0.1.0",
4
+ "description": "DG-Lab Coyote V2 / V3 BLE protocol adapters (transport-agnostic)",
5
+ "license": "MIT",
6
+ "author": "0xNullAI",
7
+ "homepage": "https://github.com/0xNullAI/DG-Kit/tree/main/packages/protocol",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/0xNullAI/DG-Kit.git",
11
+ "directory": "packages/protocol"
12
+ },
13
+ "type": "module",
14
+ "main": "./src/index.ts",
15
+ "types": "./src/index.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./src/index.ts",
19
+ "import": "./src/index.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@dg-kit/core": "0.1.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit"
43
+ },
44
+ "keywords": [
45
+ "dg-lab",
46
+ "coyote",
47
+ "ble",
48
+ "bluetooth",
49
+ "protocol"
50
+ ]
51
+ }