@bota-dev/react-native-sdk 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +279 -0
  2. package/lib/commonjs/BotaClient.js +223 -0
  3. package/lib/commonjs/BotaClient.js.map +1 -0
  4. package/lib/commonjs/ble/BleManager.js +494 -0
  5. package/lib/commonjs/ble/BleManager.js.map +1 -0
  6. package/lib/commonjs/ble/constants.js +166 -0
  7. package/lib/commonjs/ble/constants.js.map +1 -0
  8. package/lib/commonjs/ble/index.js +54 -0
  9. package/lib/commonjs/ble/index.js.map +1 -0
  10. package/lib/commonjs/ble/parsers.js +345 -0
  11. package/lib/commonjs/ble/parsers.js.map +1 -0
  12. package/lib/commonjs/index.js +81 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/managers/DeviceManager.js +437 -0
  15. package/lib/commonjs/managers/DeviceManager.js.map +1 -0
  16. package/lib/commonjs/managers/OTAManager.js +227 -0
  17. package/lib/commonjs/managers/OTAManager.js.map +1 -0
  18. package/lib/commonjs/managers/RecordingManager.js +384 -0
  19. package/lib/commonjs/managers/RecordingManager.js.map +1 -0
  20. package/lib/commonjs/managers/index.js +27 -0
  21. package/lib/commonjs/managers/index.js.map +1 -0
  22. package/lib/commonjs/models/Device.js +2 -0
  23. package/lib/commonjs/models/Device.js.map +1 -0
  24. package/lib/commonjs/models/Recording.js +2 -0
  25. package/lib/commonjs/models/Recording.js.map +1 -0
  26. package/lib/commonjs/models/Status.js +6 -0
  27. package/lib/commonjs/models/Status.js.map +1 -0
  28. package/lib/commonjs/models/index.js +39 -0
  29. package/lib/commonjs/models/index.js.map +1 -0
  30. package/lib/commonjs/protocol/ProtocolHandler.js +343 -0
  31. package/lib/commonjs/protocol/ProtocolHandler.js.map +1 -0
  32. package/lib/commonjs/protocol/index.js +13 -0
  33. package/lib/commonjs/protocol/index.js.map +1 -0
  34. package/lib/commonjs/storage/StorageManager.js +333 -0
  35. package/lib/commonjs/storage/StorageManager.js.map +1 -0
  36. package/lib/commonjs/storage/index.js +19 -0
  37. package/lib/commonjs/storage/index.js.map +1 -0
  38. package/lib/commonjs/upload/S3Uploader.js +133 -0
  39. package/lib/commonjs/upload/S3Uploader.js.map +1 -0
  40. package/lib/commonjs/upload/UploadQueue.js +280 -0
  41. package/lib/commonjs/upload/UploadQueue.js.map +1 -0
  42. package/lib/commonjs/upload/index.js +20 -0
  43. package/lib/commonjs/upload/index.js.map +1 -0
  44. package/lib/commonjs/utils/errors.js +187 -0
  45. package/lib/commonjs/utils/errors.js.map +1 -0
  46. package/lib/commonjs/utils/index.js +40 -0
  47. package/lib/commonjs/utils/index.js.map +1 -0
  48. package/lib/commonjs/utils/logger.js +135 -0
  49. package/lib/commonjs/utils/logger.js.map +1 -0
  50. package/lib/commonjs/utils/retry.js +160 -0
  51. package/lib/commonjs/utils/retry.js.map +1 -0
  52. package/lib/module/BotaClient.js +216 -0
  53. package/lib/module/BotaClient.js.map +1 -0
  54. package/lib/module/ble/BleManager.js +484 -0
  55. package/lib/module/ble/BleManager.js.map +1 -0
  56. package/lib/module/ble/constants.js +159 -0
  57. package/lib/module/ble/constants.js.map +1 -0
  58. package/lib/module/ble/index.js +8 -0
  59. package/lib/module/ble/index.js.map +1 -0
  60. package/lib/module/ble/parsers.js +328 -0
  61. package/lib/module/ble/parsers.js.map +1 -0
  62. package/lib/module/index.js +22 -0
  63. package/lib/module/index.js.map +1 -0
  64. package/lib/module/managers/DeviceManager.js +429 -0
  65. package/lib/module/managers/DeviceManager.js.map +1 -0
  66. package/lib/module/managers/OTAManager.js +219 -0
  67. package/lib/module/managers/OTAManager.js.map +1 -0
  68. package/lib/module/managers/RecordingManager.js +376 -0
  69. package/lib/module/managers/RecordingManager.js.map +1 -0
  70. package/lib/module/managers/index.js +8 -0
  71. package/lib/module/managers/index.js.map +1 -0
  72. package/lib/module/models/Device.js +2 -0
  73. package/lib/module/models/Device.js.map +1 -0
  74. package/lib/module/models/Recording.js +2 -0
  75. package/lib/module/models/Recording.js.map +1 -0
  76. package/lib/module/models/Status.js +2 -0
  77. package/lib/module/models/Status.js.map +1 -0
  78. package/lib/module/models/index.js +8 -0
  79. package/lib/module/models/index.js.map +1 -0
  80. package/lib/module/protocol/ProtocolHandler.js +336 -0
  81. package/lib/module/protocol/ProtocolHandler.js.map +1 -0
  82. package/lib/module/protocol/index.js +6 -0
  83. package/lib/module/protocol/index.js.map +1 -0
  84. package/lib/module/storage/StorageManager.js +324 -0
  85. package/lib/module/storage/StorageManager.js.map +1 -0
  86. package/lib/module/storage/index.js +6 -0
  87. package/lib/module/storage/index.js.map +1 -0
  88. package/lib/module/upload/S3Uploader.js +126 -0
  89. package/lib/module/upload/S3Uploader.js.map +1 -0
  90. package/lib/module/upload/UploadQueue.js +272 -0
  91. package/lib/module/upload/UploadQueue.js.map +1 -0
  92. package/lib/module/upload/index.js +7 -0
  93. package/lib/module/upload/index.js.map +1 -0
  94. package/lib/module/utils/errors.js +173 -0
  95. package/lib/module/utils/errors.js.map +1 -0
  96. package/lib/module/utils/index.js +8 -0
  97. package/lib/module/utils/index.js.map +1 -0
  98. package/lib/module/utils/logger.js +129 -0
  99. package/lib/module/utils/logger.js.map +1 -0
  100. package/lib/module/utils/retry.js +149 -0
  101. package/lib/module/utils/retry.js.map +1 -0
  102. package/lib/typescript/src/BotaClient.d.ts +77 -0
  103. package/lib/typescript/src/BotaClient.d.ts.map +1 -0
  104. package/lib/typescript/src/ble/BleManager.d.ts +111 -0
  105. package/lib/typescript/src/ble/BleManager.d.ts.map +1 -0
  106. package/lib/typescript/src/ble/constants.d.ts +111 -0
  107. package/lib/typescript/src/ble/constants.d.ts.map +1 -0
  108. package/lib/typescript/src/ble/index.d.ts +7 -0
  109. package/lib/typescript/src/ble/index.d.ts.map +1 -0
  110. package/lib/typescript/src/ble/parsers.d.ts +100 -0
  111. package/lib/typescript/src/ble/parsers.d.ts.map +1 -0
  112. package/lib/typescript/src/index.d.ts +16 -0
  113. package/lib/typescript/src/index.d.ts.map +1 -0
  114. package/lib/typescript/src/managers/DeviceManager.d.ts +84 -0
  115. package/lib/typescript/src/managers/DeviceManager.d.ts.map +1 -0
  116. package/lib/typescript/src/managers/OTAManager.d.ts +78 -0
  117. package/lib/typescript/src/managers/OTAManager.d.ts.map +1 -0
  118. package/lib/typescript/src/managers/RecordingManager.d.ts +90 -0
  119. package/lib/typescript/src/managers/RecordingManager.d.ts.map +1 -0
  120. package/lib/typescript/src/managers/index.d.ts +7 -0
  121. package/lib/typescript/src/managers/index.d.ts.map +1 -0
  122. package/lib/typescript/src/models/Device.d.ts +139 -0
  123. package/lib/typescript/src/models/Device.d.ts.map +1 -0
  124. package/lib/typescript/src/models/Recording.d.ts +110 -0
  125. package/lib/typescript/src/models/Recording.d.ts.map +1 -0
  126. package/lib/typescript/src/models/Status.d.ts +104 -0
  127. package/lib/typescript/src/models/Status.d.ts.map +1 -0
  128. package/lib/typescript/src/models/index.d.ts +7 -0
  129. package/lib/typescript/src/models/index.d.ts.map +1 -0
  130. package/lib/typescript/src/protocol/ProtocolHandler.d.ts +69 -0
  131. package/lib/typescript/src/protocol/ProtocolHandler.d.ts.map +1 -0
  132. package/lib/typescript/src/protocol/index.d.ts +5 -0
  133. package/lib/typescript/src/protocol/index.d.ts.map +1 -0
  134. package/lib/typescript/src/storage/StorageManager.d.ts +116 -0
  135. package/lib/typescript/src/storage/StorageManager.d.ts.map +1 -0
  136. package/lib/typescript/src/storage/index.d.ts +5 -0
  137. package/lib/typescript/src/storage/index.d.ts.map +1 -0
  138. package/lib/typescript/src/upload/S3Uploader.d.ts +38 -0
  139. package/lib/typescript/src/upload/S3Uploader.d.ts.map +1 -0
  140. package/lib/typescript/src/upload/UploadQueue.d.ts +95 -0
  141. package/lib/typescript/src/upload/UploadQueue.d.ts.map +1 -0
  142. package/lib/typescript/src/upload/index.d.ts +6 -0
  143. package/lib/typescript/src/upload/index.d.ts.map +1 -0
  144. package/lib/typescript/src/utils/errors.d.ts +82 -0
  145. package/lib/typescript/src/utils/errors.d.ts.map +1 -0
  146. package/lib/typescript/src/utils/index.d.ts +7 -0
  147. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  148. package/lib/typescript/src/utils/logger.d.ts +68 -0
  149. package/lib/typescript/src/utils/logger.d.ts.map +1 -0
  150. package/lib/typescript/src/utils/retry.d.ts +53 -0
  151. package/lib/typescript/src/utils/retry.d.ts.map +1 -0
  152. package/package.json +95 -0
  153. package/src/BotaClient.ts +238 -0
  154. package/src/ble/BleManager.ts +573 -0
  155. package/src/ble/constants.ts +158 -0
  156. package/src/ble/index.ts +7 -0
  157. package/src/ble/parsers.ts +395 -0
  158. package/src/index.ts +64 -0
  159. package/src/managers/DeviceManager.ts +545 -0
  160. package/src/managers/OTAManager.ts +263 -0
  161. package/src/managers/RecordingManager.ts +434 -0
  162. package/src/managers/index.ts +12 -0
  163. package/src/models/Device.ts +164 -0
  164. package/src/models/Recording.ts +123 -0
  165. package/src/models/Status.ts +126 -0
  166. package/src/models/index.ts +7 -0
  167. package/src/protocol/ProtocolHandler.ts +459 -0
  168. package/src/protocol/index.ts +5 -0
  169. package/src/storage/StorageManager.ts +343 -0
  170. package/src/storage/index.ts +5 -0
  171. package/src/upload/S3Uploader.ts +164 -0
  172. package/src/upload/UploadQueue.ts +310 -0
  173. package/src/upload/index.ts +6 -0
  174. package/src/utils/errors.ts +310 -0
  175. package/src/utils/index.ts +7 -0
  176. package/src/utils/logger.ts +137 -0
  177. package/src/utils/retry.ts +177 -0
@@ -0,0 +1,545 @@
1
+ /**
2
+ * Device Manager - Handles device discovery, connection, and provisioning
3
+ */
4
+
5
+ import { Buffer } from 'buffer';
6
+ import { Subscription } from 'react-native-ble-plx';
7
+ import EventEmitter from 'eventemitter3';
8
+
9
+ import { getBleManager, BleManager } from '../ble/BleManager';
10
+ import {
11
+ SERVICE_DEVICE_INFO,
12
+ SERVICE_BOTA_PROVISIONING,
13
+ SERVICE_BOTA_CONTROL,
14
+ CHAR_SERIAL_NUMBER,
15
+ CHAR_FIRMWARE_REVISION,
16
+ CHAR_HARDWARE_REVISION,
17
+ CHAR_PAIRING_STATE,
18
+ CHAR_DEVICE_TOKEN,
19
+ CHAR_API_ENDPOINT,
20
+ CHAR_PROVISIONING_RESULT,
21
+ CHAR_DEVICE_STATUS,
22
+ CHAR_TIME_SYNC,
23
+ API_ENDPOINT_PRODUCTION,
24
+ API_ENDPOINT_SANDBOX,
25
+ PROVISIONING_SUCCESS,
26
+ PROVISIONING_INVALID_TOKEN,
27
+ PROVISIONING_STORAGE_ERROR,
28
+ PROVISIONING_CHUNK_ERROR,
29
+ OPERATION_TIMEOUT,
30
+ } from '../ble/constants';
31
+ import {
32
+ parsePairingState,
33
+ parseDeviceStatus,
34
+ createTimeSyncData,
35
+ } from '../ble/parsers';
36
+ import type {
37
+ DiscoveredDevice,
38
+ ConnectedDevice,
39
+ DeviceStatus,
40
+ ScanOptions,
41
+ Environment,
42
+ ProvisioningResult,
43
+ } from '../models/Device';
44
+ import type { DeviceManagerEvents } from '../models/Status';
45
+ import {
46
+ DeviceError,
47
+ ProvisioningError,
48
+ } from '../utils/errors';
49
+ import { logger } from '../utils/logger';
50
+
51
+ const log = logger.tag('DeviceManager');
52
+
53
+ /**
54
+ * Device Manager class
55
+ */
56
+ export class DeviceManager extends EventEmitter<DeviceManagerEvents> {
57
+ private bleManager: BleManager;
58
+ private connectedDevices: Map<string, ConnectedDevice> = new Map();
59
+ private statusSubscriptions: Map<string, Subscription> = new Map();
60
+ private isInitialized = false;
61
+
62
+ constructor() {
63
+ super();
64
+ this.bleManager = getBleManager();
65
+ this.setupBleListeners();
66
+ }
67
+
68
+ /**
69
+ * Initialize the device manager
70
+ */
71
+ initialize(): void {
72
+ if (this.isInitialized) {
73
+ return;
74
+ }
75
+ this.isInitialized = true;
76
+ log.info('DeviceManager initialized');
77
+ }
78
+
79
+ /**
80
+ * Set up BLE event listeners
81
+ */
82
+ private setupBleListeners(): void {
83
+ this.bleManager.on('deviceDiscovered', (device) => {
84
+ this.emit('deviceDiscovered', device);
85
+ });
86
+
87
+ this.bleManager.on('deviceConnected', (deviceId) => {
88
+ const device = this.connectedDevices.get(deviceId);
89
+ if (device) {
90
+ this.emit('deviceConnected', device);
91
+ }
92
+ });
93
+
94
+ this.bleManager.on('deviceDisconnected', (deviceId, error) => {
95
+ const device = this.connectedDevices.get(deviceId);
96
+ if (device) {
97
+ // Update connection state
98
+ device.connectionState = 'disconnected';
99
+ }
100
+ this.connectedDevices.delete(deviceId);
101
+
102
+ // Clean up status subscription
103
+ this.statusSubscriptions.get(deviceId)?.remove();
104
+ this.statusSubscriptions.delete(deviceId);
105
+
106
+ this.emit('deviceDisconnected', deviceId, error);
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Start scanning for Bota devices
112
+ */
113
+ async startScan(options: ScanOptions = {}): Promise<void> {
114
+ log.info('Starting device scan', options as Record<string, unknown>);
115
+ this.emit('scanStarted');
116
+
117
+ try {
118
+ await this.bleManager.startScan(options);
119
+ } catch (error) {
120
+ this.emit('scanError', error as Error);
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Stop scanning for devices
127
+ */
128
+ stopScan(): void {
129
+ log.info('Stopping device scan');
130
+ this.bleManager.stopScan();
131
+ this.emit('scanStopped');
132
+ }
133
+
134
+ /**
135
+ * Get list of discovered devices
136
+ */
137
+ getDiscoveredDevices(): DiscoveredDevice[] {
138
+ return this.bleManager.getDiscoveredDevices();
139
+ }
140
+
141
+ /**
142
+ * Get list of connected devices
143
+ */
144
+ getConnectedDevices(): ConnectedDevice[] {
145
+ return Array.from(this.connectedDevices.values());
146
+ }
147
+
148
+ /**
149
+ * Connect to a discovered device
150
+ */
151
+ async connect(device: DiscoveredDevice): Promise<ConnectedDevice> {
152
+ log.info('Connecting to device', { deviceId: device.id, name: device.name });
153
+
154
+ // Check if already connected
155
+ const existing = this.connectedDevices.get(device.id);
156
+ if (existing && existing.connectionState === 'connected') {
157
+ log.debug('Device already connected', { deviceId: device.id });
158
+ return existing;
159
+ }
160
+
161
+ // Emit connecting state
162
+ this.emit('connectionStateChanged', device.id, 'connecting');
163
+
164
+ try {
165
+ // Connect via BLE manager
166
+ await this.bleManager.connect(device.id);
167
+
168
+ // Read device information
169
+ const serialNumber = await this.readSerialNumber(device.id);
170
+ const firmwareVersion = await this.readFirmwareVersion(device.id);
171
+ const hardwareRevision = await this.readHardwareRevision(device.id);
172
+ const pairingState = await this.readPairingState(device.id);
173
+ const mtu = await this.bleManager.getMtu(device.id);
174
+
175
+ const connectedDevice: ConnectedDevice = {
176
+ id: device.id,
177
+ serialNumber,
178
+ deviceType: device.deviceType,
179
+ firmwareVersion,
180
+ hardwareRevision,
181
+ isProvisioned: pairingState === 'paired',
182
+ connectionState: 'connected',
183
+ mtu,
184
+ };
185
+
186
+ this.connectedDevices.set(device.id, connectedDevice);
187
+
188
+ log.info('Device connected successfully', {
189
+ deviceId: device.id,
190
+ serialNumber,
191
+ isProvisioned: connectedDevice.isProvisioned,
192
+ });
193
+
194
+ this.emit('connectionStateChanged', device.id, 'connected');
195
+
196
+ return connectedDevice;
197
+ } catch (error) {
198
+ this.emit('connectionStateChanged', device.id, 'disconnected');
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Disconnect from a device
205
+ */
206
+ async disconnect(device: ConnectedDevice): Promise<void> {
207
+ log.info('Disconnecting from device', { deviceId: device.id });
208
+
209
+ this.emit('connectionStateChanged', device.id, 'disconnecting');
210
+
211
+ // Clean up status subscription
212
+ this.statusSubscriptions.get(device.id)?.remove();
213
+ this.statusSubscriptions.delete(device.id);
214
+
215
+ await this.bleManager.disconnect(device.id);
216
+ this.connectedDevices.delete(device.id);
217
+
218
+ this.emit('connectionStateChanged', device.id, 'disconnected');
219
+ }
220
+
221
+ /**
222
+ * Check if a device is connected
223
+ */
224
+ isConnected(deviceId: string): boolean {
225
+ return this.bleManager.isConnected(deviceId);
226
+ }
227
+
228
+ /**
229
+ * Provision a device with a token
230
+ */
231
+ async provision(
232
+ device: ConnectedDevice,
233
+ deviceToken: string,
234
+ environment: Environment = 'production'
235
+ ): Promise<void> {
236
+ log.info('Provisioning device', {
237
+ deviceId: device.id,
238
+ environment,
239
+ });
240
+
241
+ if (!this.isConnected(device.id)) {
242
+ throw DeviceError.notConnected(device.id);
243
+ }
244
+
245
+ // Check current pairing state
246
+ const pairingState = await this.readPairingState(device.id);
247
+ if (pairingState === 'paired') {
248
+ log.warn('Device is already provisioned', { deviceId: device.id });
249
+ throw ProvisioningError.alreadyProvisioned(device.id);
250
+ }
251
+
252
+ // Set up provisioning result listener
253
+ const resultPromise = this.waitForProvisioningResult(device.id);
254
+
255
+ try {
256
+ // Write API endpoint
257
+ await this.writeApiEndpoint(device.id, environment);
258
+
259
+ // Write device token (chunked)
260
+ await this.writeDeviceToken(device.id, deviceToken);
261
+
262
+ // Wait for provisioning result
263
+ const result = await resultPromise;
264
+
265
+ if (!result.success) {
266
+ switch (result.error) {
267
+ case 'invalid_token':
268
+ throw ProvisioningError.invalidToken(device.id);
269
+ case 'storage_error':
270
+ throw ProvisioningError.storageError(device.id);
271
+ case 'chunk_error':
272
+ throw ProvisioningError.chunkError(device.id);
273
+ default:
274
+ throw new ProvisioningError(
275
+ 'Provisioning failed',
276
+ 'PROVISIONING_FAILED',
277
+ device.id
278
+ );
279
+ }
280
+ }
281
+
282
+ // Sync time to device
283
+ await this.syncTime(device.id);
284
+
285
+ // Update device state
286
+ device.isProvisioned = true;
287
+
288
+ log.info('Device provisioned successfully', { deviceId: device.id });
289
+ } catch (error) {
290
+ log.error('Provisioning failed', error as Error, { deviceId: device.id });
291
+ throw error;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Check if a device is provisioned
297
+ */
298
+ async isProvisioned(device: ConnectedDevice): Promise<boolean> {
299
+ const pairingState = await this.readPairingState(device.id);
300
+ return pairingState === 'paired';
301
+ }
302
+
303
+ /**
304
+ * Get device status
305
+ */
306
+ async getStatus(device: ConnectedDevice): Promise<DeviceStatus> {
307
+ if (!this.isConnected(device.id)) {
308
+ throw DeviceError.notConnected(device.id);
309
+ }
310
+
311
+ const data = await this.bleManager.readCharacteristic(
312
+ device.id,
313
+ SERVICE_BOTA_CONTROL,
314
+ CHAR_DEVICE_STATUS
315
+ );
316
+
317
+ return parseDeviceStatus(data);
318
+ }
319
+
320
+ /**
321
+ * Subscribe to device status updates
322
+ */
323
+ subscribeToStatus(
324
+ device: ConnectedDevice,
325
+ callback: (status: DeviceStatus) => void
326
+ ): () => void {
327
+ if (!this.isConnected(device.id)) {
328
+ throw DeviceError.notConnected(device.id);
329
+ }
330
+
331
+ // Remove existing subscription
332
+ this.statusSubscriptions.get(device.id)?.remove();
333
+
334
+ const subscription = this.bleManager.subscribeToCharacteristic(
335
+ device.id,
336
+ SERVICE_BOTA_CONTROL,
337
+ CHAR_DEVICE_STATUS,
338
+ (data) => {
339
+ try {
340
+ const status = parseDeviceStatus(data);
341
+ this.emit('deviceStatusUpdated', device.id, status);
342
+ callback(status);
343
+ } catch (error) {
344
+ log.error('Failed to parse status update', error as Error);
345
+ }
346
+ },
347
+ (error) => {
348
+ log.error('Status subscription error', error);
349
+ }
350
+ );
351
+
352
+ this.statusSubscriptions.set(device.id, subscription);
353
+
354
+ return () => {
355
+ subscription.remove();
356
+ this.statusSubscriptions.delete(device.id);
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Sync time to device
362
+ */
363
+ async syncTime(deviceId: string): Promise<void> {
364
+ log.debug('Syncing time to device', { deviceId });
365
+
366
+ const timeSyncData = createTimeSyncData();
367
+
368
+ await this.bleManager.writeCharacteristic(
369
+ deviceId,
370
+ SERVICE_BOTA_CONTROL,
371
+ CHAR_TIME_SYNC,
372
+ timeSyncData
373
+ );
374
+ }
375
+
376
+ // Private helper methods
377
+
378
+ private async readSerialNumber(deviceId: string): Promise<string> {
379
+ const data = await this.bleManager.readCharacteristic(
380
+ deviceId,
381
+ SERVICE_DEVICE_INFO,
382
+ CHAR_SERIAL_NUMBER
383
+ );
384
+ return data.toString('utf8').replace(/\0/g, '');
385
+ }
386
+
387
+ private async readFirmwareVersion(deviceId: string): Promise<string> {
388
+ const data = await this.bleManager.readCharacteristic(
389
+ deviceId,
390
+ SERVICE_DEVICE_INFO,
391
+ CHAR_FIRMWARE_REVISION
392
+ );
393
+ return data.toString('utf8').replace(/\0/g, '');
394
+ }
395
+
396
+ private async readHardwareRevision(deviceId: string): Promise<string | undefined> {
397
+ try {
398
+ const data = await this.bleManager.readCharacteristic(
399
+ deviceId,
400
+ SERVICE_DEVICE_INFO,
401
+ CHAR_HARDWARE_REVISION
402
+ );
403
+ return data.toString('utf8').replace(/\0/g, '');
404
+ } catch {
405
+ return undefined;
406
+ }
407
+ }
408
+
409
+ private async readPairingState(deviceId: string): Promise<'unpaired' | 'pairing' | 'paired' | 'error'> {
410
+ const data = await this.bleManager.readCharacteristic(
411
+ deviceId,
412
+ SERVICE_BOTA_PROVISIONING,
413
+ CHAR_PAIRING_STATE
414
+ );
415
+
416
+ if (data.length < 1) {
417
+ return 'unpaired';
418
+ }
419
+
420
+ return parsePairingState(data[0]);
421
+ }
422
+
423
+ private async writeApiEndpoint(
424
+ deviceId: string,
425
+ environment: Environment
426
+ ): Promise<void> {
427
+ const endpointByte =
428
+ environment === 'production' ? API_ENDPOINT_PRODUCTION : API_ENDPOINT_SANDBOX;
429
+
430
+ await this.bleManager.writeCharacteristic(
431
+ deviceId,
432
+ SERVICE_BOTA_PROVISIONING,
433
+ CHAR_API_ENDPOINT,
434
+ Buffer.from([endpointByte])
435
+ );
436
+ }
437
+
438
+ private async writeDeviceToken(
439
+ deviceId: string,
440
+ token: string
441
+ ): Promise<void> {
442
+ const mtu = await this.bleManager.getMtu(deviceId);
443
+ const maxChunkSize = mtu - 5; // Account for BLE overhead + chunk header
444
+
445
+ const tokenBuffer = Buffer.from(token, 'utf8');
446
+ const totalChunks = Math.ceil(tokenBuffer.length / (maxChunkSize - 2));
447
+
448
+ log.debug('Writing device token', {
449
+ deviceId,
450
+ tokenLength: token.length,
451
+ totalChunks,
452
+ mtu,
453
+ });
454
+
455
+ for (let i = 0; i < totalChunks; i++) {
456
+ const start = i * (maxChunkSize - 2);
457
+ const end = Math.min(start + (maxChunkSize - 2), tokenBuffer.length);
458
+ const chunkData = tokenBuffer.slice(start, end);
459
+
460
+ // Create chunk with header: [chunk_index, total_chunks, ...data]
461
+ const chunk = Buffer.alloc(2 + chunkData.length);
462
+ chunk.writeUInt8(i, 0);
463
+ chunk.writeUInt8(totalChunks, 1);
464
+ chunkData.copy(chunk, 2);
465
+
466
+ await this.bleManager.writeCharacteristic(
467
+ deviceId,
468
+ SERVICE_BOTA_PROVISIONING,
469
+ CHAR_DEVICE_TOKEN,
470
+ chunk
471
+ );
472
+
473
+ log.debug('Wrote token chunk', { chunk: i + 1, total: totalChunks });
474
+ }
475
+ }
476
+
477
+ private waitForProvisioningResult(deviceId: string): Promise<ProvisioningResult> {
478
+ return new Promise((resolve, reject) => {
479
+ const timeout = setTimeout(() => {
480
+ subscription.remove();
481
+ reject(ProvisioningError.timeout(deviceId));
482
+ }, OPERATION_TIMEOUT);
483
+
484
+ const subscription = this.bleManager.subscribeToCharacteristic(
485
+ deviceId,
486
+ SERVICE_BOTA_PROVISIONING,
487
+ CHAR_PROVISIONING_RESULT,
488
+ (data) => {
489
+ clearTimeout(timeout);
490
+ subscription.remove();
491
+
492
+ if (data.length < 1) {
493
+ resolve({ success: false, error: 'unknown' });
494
+ return;
495
+ }
496
+
497
+ const resultCode = data[0];
498
+ switch (resultCode) {
499
+ case PROVISIONING_SUCCESS:
500
+ resolve({ success: true });
501
+ break;
502
+ case PROVISIONING_INVALID_TOKEN:
503
+ resolve({ success: false, error: 'invalid_token' });
504
+ break;
505
+ case PROVISIONING_STORAGE_ERROR:
506
+ resolve({ success: false, error: 'storage_error' });
507
+ break;
508
+ case PROVISIONING_CHUNK_ERROR:
509
+ resolve({ success: false, error: 'chunk_error' });
510
+ break;
511
+ default:
512
+ resolve({ success: false, error: 'unknown' });
513
+ }
514
+ },
515
+ (error) => {
516
+ clearTimeout(timeout);
517
+ subscription.remove();
518
+ reject(new ProvisioningError(
519
+ `Provisioning notification error: ${error.message}`,
520
+ 'NOTIFICATION_ERROR',
521
+ deviceId,
522
+ error
523
+ ));
524
+ }
525
+ );
526
+ });
527
+ }
528
+
529
+ /**
530
+ * Clean up resources
531
+ */
532
+ destroy(): void {
533
+ log.info('Destroying DeviceManager');
534
+
535
+ // Clean up all status subscriptions
536
+ for (const sub of this.statusSubscriptions.values()) {
537
+ sub.remove();
538
+ }
539
+ this.statusSubscriptions.clear();
540
+
541
+ this.connectedDevices.clear();
542
+ this.removeAllListeners();
543
+ this.isInitialized = false;
544
+ }
545
+ }