@homebridge-plugins/homebridge-sharkiq 2.0.0-beta.2 → 2.0.0-beta.20

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 (47) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/README.md +41 -4
  3. package/config.schema.json +8 -26
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/constants.js.map +1 -1
  6. package/dist/devices/BaseMatterAccessory.d.ts +63 -0
  7. package/dist/devices/BaseMatterAccessory.d.ts.map +1 -0
  8. package/dist/devices/BaseMatterAccessory.js +100 -0
  9. package/dist/devices/BaseMatterAccessory.js.map +1 -0
  10. package/dist/devices/RoboticVacuumAccessory.d.ts +67 -0
  11. package/dist/devices/RoboticVacuumAccessory.d.ts.map +1 -0
  12. package/dist/devices/RoboticVacuumAccessory.js +649 -0
  13. package/dist/devices/RoboticVacuumAccessory.js.map +1 -0
  14. package/dist/devices/index.d.ts +9 -0
  15. package/dist/devices/index.d.ts.map +1 -0
  16. package/dist/devices/index.js +8 -0
  17. package/dist/devices/index.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/platform.d.ts +39 -16
  23. package/dist/platform.d.ts.map +1 -1
  24. package/dist/platform.js +195 -92
  25. package/dist/platform.js.map +1 -1
  26. package/dist/sharkiq-js/room_encoding.d.ts +8 -0
  27. package/dist/sharkiq-js/room_encoding.d.ts.map +1 -0
  28. package/dist/sharkiq-js/room_encoding.js +35 -0
  29. package/dist/sharkiq-js/room_encoding.js.map +1 -0
  30. package/dist/sharkiq-js/sharkiq.d.ts.map +1 -1
  31. package/dist/sharkiq-js/sharkiq.js +2 -20
  32. package/dist/sharkiq-js/sharkiq.js.map +1 -1
  33. package/docs/assets/highlight.css +9 -2
  34. package/docs/index.html +35 -3
  35. package/docs/variables/default.html +2 -1
  36. package/package.json +3 -3
  37. package/test/context.spec.ts +33 -0
  38. package/test/encodeRoomList.spec.ts +25 -0
  39. package/test/polling.spec.ts +74 -0
  40. package/dist/errorHandling.d.ts +0 -19
  41. package/dist/errorHandling.d.ts.map +0 -1
  42. package/dist/errorHandling.js +0 -27
  43. package/dist/errorHandling.js.map +0 -1
  44. package/dist/platformAccessory.d.ts +0 -33
  45. package/dist/platformAccessory.d.ts.map +0 -1
  46. package/dist/platformAccessory.js +0 -384
  47. package/dist/platformAccessory.js.map +0 -1
@@ -0,0 +1,649 @@
1
+ /* global NodeJS */
2
+ import { ERROR_MESSAGES, OperatingModes, PowerModes, Properties } from '../sharkiq-js/properties.js';
3
+ import { BaseMatterAccessory } from './BaseMatterAccessory.js';
4
+ export class RoboticVacuumAccessory extends BaseMatterAccessory {
5
+ activeTimers = [];
6
+ sharkDevice;
7
+ pollInterval;
8
+ constructor(api, log, sharkDevice, pollIntervalMs) {
9
+ const serialNumber = (sharkDevice && (sharkDevice.serial_number || sharkDevice._dsn)) || 'VACUUM-001';
10
+ const displayName = (sharkDevice && (sharkDevice.name || sharkDevice._name)) || 'Robot Vacuum';
11
+ const manufacturer = sharkDevice ? 'Shark' : 'Homebridge Matter';
12
+ const model = (sharkDevice && (sharkDevice.vac_model_number || sharkDevice._vac_model_number)) || 'HB-MATTER-VACUUM-ROBOTIC';
13
+ const firmwareRevision = (sharkDevice && (sharkDevice._firmware_version || 'unknown')) || '2.0.0';
14
+ // Build serviceArea cluster from device rooms (local helper so we don't use `this` before super)
15
+ const serviceAreaCluster = (() => {
16
+ if (!sharkDevice || typeof sharkDevice.get_room_list !== 'function') {
17
+ return {
18
+ supportedMaps: [],
19
+ supportedAreas: [],
20
+ selectedAreas: [],
21
+ };
22
+ }
23
+ try {
24
+ const rooms = sharkDevice.get_room_list() || [];
25
+ const supportedAreas = rooms.map((roomName, idx) => ({
26
+ areaId: idx,
27
+ mapId: null,
28
+ areaInfo: {
29
+ locationInfo: {
30
+ locationName: roomName,
31
+ floorNumber: 0,
32
+ areaType: 0,
33
+ },
34
+ landmarkInfo: null,
35
+ },
36
+ }));
37
+ return {
38
+ supportedMaps: [],
39
+ supportedAreas,
40
+ selectedAreas: supportedAreas.map(a => a.areaId),
41
+ };
42
+ }
43
+ catch {
44
+ return {
45
+ supportedMaps: [],
46
+ supportedAreas: [],
47
+ selectedAreas: [],
48
+ };
49
+ }
50
+ })();
51
+ super(api, log, {
52
+ uuid: api.matter.uuid.generate(serialNumber),
53
+ displayName,
54
+ deviceType: api.matter.deviceTypes.RoboticVacuumCleaner,
55
+ serialNumber,
56
+ manufacturer,
57
+ model,
58
+ firmwareRevision,
59
+ hardwareRevision: '1.0.0',
60
+ // Persist useful metadata in accessory.context so cached accessories retain DSN/model/etc.
61
+ context: {
62
+ serialNumber,
63
+ name: displayName,
64
+ vacModel: model,
65
+ vacSerial: (sharkDevice && (sharkDevice.vac_serial_number || sharkDevice._vac_serial_number)) || null,
66
+ oemModel: (sharkDevice && (sharkDevice.oem_model_number || sharkDevice._oem_model_number)) || null,
67
+ pollIntervalMs: typeof pollIntervalMs === 'number' ? pollIntervalMs : null,
68
+ },
69
+ clusters: {
70
+ // Run Mode: Controls what the vacuum is doing (Idle, Cleaning, Mapping)
71
+ rvcRunMode: {
72
+ supportedModes: [
73
+ { label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] }, // RvcRunMode.ModeTag.Idle
74
+ { label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] }, // RvcRunMode.ModeTag.Cleaning
75
+ { label: 'Mapping', mode: 2, modeTags: [{ value: 16386 }] }, // RvcRunMode.ModeTag.Mapping
76
+ ],
77
+ currentMode: 0,
78
+ },
79
+ // Clean Mode: Controls HOW the vacuum cleans
80
+ // You can combine semantic tags (0-9) with functional tags (16384-16386)
81
+ // Available semantic tags: Auto, Quick, Quiet, LowNoise, LowEnergy, Vacation, Min, Max, Night, Day
82
+ // Available functional tags: DeepClean, Vacuum, Mop
83
+ rvcCleanMode: {
84
+ supportedModes: [
85
+ // Basic functional modes
86
+ { label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] }, // Vacuum
87
+ { label: 'Mop', mode: 1, modeTags: [{ value: 16386 }] }, // Mop
88
+ { label: 'Vacuum & Mop', mode: 2, modeTags: [{ value: 16385 }, { value: 16386 }] }, // Both
89
+ // Deep clean modes
90
+ { label: 'Deep Clean', mode: 3, modeTags: [{ value: 16384 }] }, // DeepClean
91
+ { label: 'Deep Vacuum', mode: 4, modeTags: [{ value: 16384 }, { value: 16385 }] }, // DeepClean + Vacuum
92
+ { label: 'Deep Mop', mode: 5, modeTags: [{ value: 16384 }, { value: 16386 }] }, // DeepClean + Mop
93
+ // Intensity modes
94
+ { label: 'Quick Clean', mode: 6, modeTags: [{ value: 1 }, { value: 16385 }] }, // Quick + Vacuum
95
+ { label: 'Max Clean', mode: 7, modeTags: [{ value: 7 }, { value: 16385 }] }, // Max + Vacuum
96
+ { label: 'Min Clean', mode: 8, modeTags: [{ value: 6 }, { value: 16385 }] }, // Min + Vacuum
97
+ // Quiet modes
98
+ { label: 'Quiet Vacuum', mode: 9, modeTags: [{ value: 2 }, { value: 16385 }] }, // Quiet + Vacuum
99
+ { label: 'Quiet Mop', mode: 10, modeTags: [{ value: 2 }, { value: 16386 }] }, // Quiet + Mop
100
+ { label: 'Night Mode', mode: 11, modeTags: [{ value: 8 }, { value: 16385 }] }, // Night + Vacuum
101
+ // Energy efficient
102
+ { label: 'Eco Vacuum', mode: 12, modeTags: [{ value: 4 }, { value: 16385 }] }, // LowEnergy + Vacuum
103
+ { label: 'Eco Mop', mode: 13, modeTags: [{ value: 4 }, { value: 16386 }] }, // LowEnergy + Mop
104
+ // Auto mode
105
+ { label: 'Auto', mode: 14, modeTags: [{ value: 0 }, { value: 16385 }] }, // Auto + Vacuum
106
+ ],
107
+ currentMode: 0, // start with basic Vacuum
108
+ },
109
+ // Operational State: Current state (Stopped, Running, Paused, Error, etc.)
110
+ rvcOperationalState: {
111
+ operationalStateList: [
112
+ { operationalStateId: 0 }, // stopped (standard label from Matter spec)
113
+ { operationalStateId: 1 }, // running
114
+ { operationalStateId: 2 }, // paused
115
+ { operationalStateId: 3 }, // error
116
+ { operationalStateId: 64 }, // seeking charger
117
+ { operationalStateId: 65 }, // charging
118
+ { operationalStateId: 66 }, // docked
119
+ ],
120
+ operationalState: 66, // start docked
121
+ },
122
+ // Service Area: Room/zone selection for targeted cleaning
123
+ serviceArea: serviceAreaCluster,
124
+ },
125
+ handlers: {
126
+ rvcRunMode: {
127
+ changeToMode: async (request) => this.handleChangeRunMode(request),
128
+ },
129
+ rvcCleanMode: {
130
+ changeToMode: async (request) => this.handleChangeCleanMode(request),
131
+ },
132
+ rvcOperationalState: {
133
+ pause: async () => this.handlePause(),
134
+ stop: async () => this.handleStop(),
135
+ start: async () => this.handleStart(),
136
+ resume: async () => this.handleResume(),
137
+ goHome: async () => this.handleGoHome(),
138
+ },
139
+ serviceArea: {
140
+ selectAreas: async (request) => this.handleSelectAreas(request),
141
+ skipArea: async (request) => this.handleSkipArea(request),
142
+ },
143
+ // Power / Battery information
144
+ power: {
145
+ batteryLevel: 100,
146
+ charging: false,
147
+ },
148
+ // Diagnostics / Error reporting
149
+ diagnostics: {
150
+ errorCode: 0,
151
+ errorText: '',
152
+ },
153
+ },
154
+ });
155
+ this.sharkDevice = sharkDevice;
156
+ // Start polling device state if we have a Shark device and polling is enabled
157
+ if (this.sharkDevice && typeof pollIntervalMs === 'number' && pollIntervalMs > 0) {
158
+ this.startPolling(pollIntervalMs);
159
+ }
160
+ else if (this.sharkDevice && (typeof pollIntervalMs !== 'number' || pollIntervalMs <= 0)) {
161
+ // If pollIntervalMs is omitted, default to 30s
162
+ if (typeof pollIntervalMs !== 'number') {
163
+ this.startPolling();
164
+ }
165
+ }
166
+ this.logInfo('initialized and ready.');
167
+ }
168
+ startPolling(intervalMs = 30000) {
169
+ if (this.pollInterval) {
170
+ return;
171
+ }
172
+ this.pollInterval = setInterval(async () => {
173
+ try {
174
+ // Request a full update
175
+ await this.sharkDevice.update([]);
176
+ // Update run/operating mode
177
+ const opMode = this.sharkDevice.operating_mode?.() ?? null;
178
+ const powerMode = this.sharkDevice.power_mode?.() ?? null;
179
+ const docked = this.sharkDevice.docked_status?.() ?? null;
180
+ // Map operating mode to Matter run/operational state
181
+ if (docked !== null && docked === 1) {
182
+ // Docked
183
+ this.updateOperationalState(66);
184
+ this.updateRunMode(0);
185
+ }
186
+ else if (opMode !== null) {
187
+ if (opMode === OperatingModes.START) {
188
+ this.updateRunMode(1);
189
+ this.updateOperationalState(1);
190
+ }
191
+ else if (opMode === OperatingModes.PAUSE) {
192
+ this.updateOperationalState(2);
193
+ this.updateRunMode(1);
194
+ }
195
+ else if (opMode === OperatingModes.RETURN) {
196
+ this.updateOperationalState(64);
197
+ this.updateRunMode(0);
198
+ }
199
+ else if (opMode === OperatingModes.STOP) {
200
+ this.updateOperationalState(0);
201
+ this.updateRunMode(0);
202
+ }
203
+ }
204
+ // Map power mode to clean mode (best-effort mapping)
205
+ if (powerMode !== null) {
206
+ let cleanMode = 0; // Vacuum
207
+ if (powerMode === PowerModes.ECO) {
208
+ cleanMode = 12; // Eco Vacuum
209
+ }
210
+ else if (powerMode === PowerModes.MAX) {
211
+ cleanMode = 7; // Max Clean
212
+ }
213
+ else {
214
+ cleanMode = 0;
215
+ }
216
+ this.updateCleanMode(cleanMode);
217
+ }
218
+ // Update service areas selection from device room list
219
+ if (typeof this.sharkDevice.get_room_list === 'function') {
220
+ const rooms = this.sharkDevice.get_room_list() || [];
221
+ const ids = rooms.map((_, idx) => idx);
222
+ this.updateSelectedAreas(ids);
223
+ }
224
+ // Update battery level, charging and diagnostics using upstream helpers when available
225
+ try {
226
+ // Prefer convenience properties if present on the wrapper
227
+ let batteryVal = null;
228
+ let chargingVal = null;
229
+ let errVal = null;
230
+ let errText = null;
231
+ if (this.sharkDevice.battery_capacity && typeof this.sharkDevice.battery_capacity === 'function') {
232
+ batteryVal = this.sharkDevice.battery_capacity();
233
+ }
234
+ else if (typeof this.sharkDevice.get_property_value === 'function') {
235
+ batteryVal = this.sharkDevice.get_property_value(Properties.BATTERY_CAPACITY);
236
+ }
237
+ if (this.sharkDevice.charging_status && typeof this.sharkDevice.charging_status === 'function') {
238
+ chargingVal = this.sharkDevice.charging_status();
239
+ }
240
+ else if (typeof this.sharkDevice.get_property_value === 'function') {
241
+ chargingVal = this.sharkDevice.get_property_value(Properties.CHARGING_STATUS);
242
+ }
243
+ // Error code / text
244
+ if (this.sharkDevice.error_code !== undefined && typeof this.sharkDevice.error_code === 'function') {
245
+ errVal = this.sharkDevice.error_code();
246
+ if (this.sharkDevice.error_text && typeof this.sharkDevice.error_text === 'function') {
247
+ errText = this.sharkDevice.error_text();
248
+ }
249
+ }
250
+ else if (typeof this.sharkDevice.get_property_value === 'function') {
251
+ errVal = this.sharkDevice.get_property_value(Properties.ERROR_CODE);
252
+ }
253
+ if (batteryVal !== null && typeof batteryVal !== 'undefined') {
254
+ const pct = Number(batteryVal) || 0;
255
+ // chargingVal may be boolean-like or numeric
256
+ const isCharging = chargingVal === 1 || chargingVal === true;
257
+ await this.updateState('power', { batteryLevel: Math.max(0, Math.min(100, pct)), charging: isCharging });
258
+ this.logInfo(`battery updated: ${pct}% (charging: ${isCharging})`);
259
+ }
260
+ // RSSI (signal strength) mapping if available
261
+ try {
262
+ const rssi = this.sharkDevice.get_property_value
263
+ ? this.sharkDevice.get_property_value(Properties.RSSI)
264
+ : null;
265
+ if (rssi !== null && typeof rssi !== 'undefined') {
266
+ await this.updateState('diagnostics', { ...null, rssi: Number(rssi) });
267
+ this.logInfo(`rssi updated: ${rssi}`);
268
+ }
269
+ }
270
+ catch (e) {
271
+ // ignore
272
+ }
273
+ if (errVal && Number(errVal) > 0) {
274
+ const errCode = Number(errVal);
275
+ // Prefer human friendly text from device wrapper if provided
276
+ const text = errText || ERROR_MESSAGES[errCode] || `Error ${errCode}`;
277
+ // Map severity: treat these as actionable errors (set operational state to Error)
278
+ await this.updateState('diagnostics', { errorCode: errCode, errorText: text, severity: 'critical' });
279
+ this.updateOperationalState(3); // Error
280
+ this.logWarn(`device error: ${errCode} - ${text}`);
281
+ }
282
+ else if (errVal === 0 || errVal === null || typeof errVal === 'undefined') {
283
+ await this.updateState('diagnostics', { errorCode: 0, errorText: '', severity: 'ok' });
284
+ }
285
+ // Clean complete mapping - if device reports a clean complete flag, reflect it
286
+ try {
287
+ const cleanComplete = this.sharkDevice.get_property_value
288
+ ? this.sharkDevice.get_property_value(Properties.CLEAN_COMPLETE)
289
+ : null;
290
+ if (cleanComplete === 1 || cleanComplete === true) {
291
+ // When cleaning completes, set run mode to Idle and operational to Stopped
292
+ this.updateRunMode(0);
293
+ this.updateOperationalState(0);
294
+ this.logInfo('clean complete reported by device - updating state to Idle/Stopped');
295
+ }
296
+ }
297
+ catch (e) {
298
+ // ignore
299
+ }
300
+ }
301
+ catch (e) {
302
+ this.logDebug('Error updating battery/diagnostics:', e);
303
+ }
304
+ }
305
+ catch (e) {
306
+ this.logError('Polling error for Shark device:', e);
307
+ }
308
+ }, intervalMs);
309
+ }
310
+ stopPolling() {
311
+ if (this.pollInterval) {
312
+ clearInterval(this.pollInterval);
313
+ this.pollInterval = undefined;
314
+ }
315
+ }
316
+ // Helper to build serviceArea cluster from Shark device rooms
317
+ buildServiceAreaCluster(sharkDevice) {
318
+ if (!sharkDevice || typeof sharkDevice.get_room_list !== 'function') {
319
+ return {
320
+ supportedMaps: [],
321
+ supportedAreas: [],
322
+ selectedAreas: [],
323
+ };
324
+ }
325
+ try {
326
+ const rooms = sharkDevice.get_room_list() || [];
327
+ const supportedAreas = rooms.map((roomName, idx) => ({
328
+ areaId: idx,
329
+ mapId: null,
330
+ areaInfo: {
331
+ locationInfo: {
332
+ locationName: roomName,
333
+ floorNumber: 0,
334
+ areaType: 0,
335
+ },
336
+ landmarkInfo: null,
337
+ },
338
+ }));
339
+ return {
340
+ supportedMaps: [],
341
+ supportedAreas,
342
+ selectedAreas: supportedAreas.map(a => a.areaId),
343
+ };
344
+ }
345
+ catch (e) {
346
+ return {
347
+ supportedMaps: [],
348
+ supportedAreas: [],
349
+ selectedAreas: [],
350
+ };
351
+ }
352
+ }
353
+ async handleChangeRunMode(request) {
354
+ this.logInfo(`ChangeToMode (run) request received: ${JSON.stringify(request)}`);
355
+ const { newMode } = request;
356
+ const modeStr = ['Idle', 'Cleaning', 'Mapping'][newMode] || `Unknown (mode=${newMode})`;
357
+ this.logInfo(`changing run mode to: ${modeStr}`);
358
+ if (this.sharkDevice) {
359
+ try {
360
+ if (newMode === 1) {
361
+ await this.sharkDevice.set_operating_mode(OperatingModes.START);
362
+ }
363
+ else if (newMode === 0) {
364
+ await this.sharkDevice.set_operating_mode(OperatingModes.STOP);
365
+ }
366
+ else if (newMode === 2) {
367
+ // Mapping - treat as start for now
368
+ await this.sharkDevice.set_operating_mode(OperatingModes.START);
369
+ }
370
+ }
371
+ catch (e) {
372
+ this.logError('Error setting run mode on Shark device:', e);
373
+ }
374
+ }
375
+ // Clear any existing timers
376
+ this.clearTimers();
377
+ if (newMode === 1) {
378
+ // Switching to Cleaning mode - start the vacuum
379
+ this.updateOperationalState(1); // Running
380
+ // Simulate cleaning completion after 15 seconds
381
+ const completionTimer = setTimeout(() => {
382
+ this.logInfo('cleaning completed, returning to dock.');
383
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
384
+ this.returnToDock();
385
+ }, 15000);
386
+ this.activeTimers.push(completionTimer);
387
+ }
388
+ else if (newMode === 0) {
389
+ // Switching to Idle mode - return to dock
390
+ this.returnToDock();
391
+ }
392
+ else if (newMode === 2) {
393
+ // Switching to Mapping mode - start mapping
394
+ this.updateOperationalState(1); // Running
395
+ // Simulate mapping completion after 20 seconds
396
+ const completionTimer = setTimeout(() => {
397
+ this.logInfo('mapping completed, returning to dock.');
398
+ this.returnToDock();
399
+ }, 20000);
400
+ this.activeTimers.push(completionTimer);
401
+ }
402
+ }
403
+ async handleChangeCleanMode(request) {
404
+ this.logInfo(`ChangeToMode (clean) request received: ${JSON.stringify(request)}`);
405
+ const { newMode } = request;
406
+ const modes = [
407
+ 'Vacuum',
408
+ 'Mop',
409
+ 'Vacuum & Mop',
410
+ 'Deep Clean',
411
+ 'Deep Vacuum',
412
+ 'Deep Mop',
413
+ 'Quick Clean',
414
+ 'Max Clean',
415
+ 'Min Clean',
416
+ 'Quiet Vacuum',
417
+ 'Quiet Mop',
418
+ 'Night Mode',
419
+ 'Eco Vacuum',
420
+ 'Eco Mop',
421
+ 'Auto',
422
+ ];
423
+ const modeStr = modes[newMode] || `Unknown (mode=${newMode})`;
424
+ this.logInfo(`changing clean mode to: ${modeStr}`);
425
+ if (this.sharkDevice) {
426
+ // Shark API doesn't expose a direct clean-mode setter here; keep local state and log.
427
+ this.logInfo('Clean mode change requested for Shark device (not implemented in API wrapper).');
428
+ }
429
+ }
430
+ async handlePause() {
431
+ this.logInfo('pausing.');
432
+ if (this.sharkDevice) {
433
+ try {
434
+ await this.sharkDevice.set_operating_mode(OperatingModes.PAUSE);
435
+ }
436
+ catch (e) {
437
+ this.logError('Error pausing Shark device:', e);
438
+ }
439
+ }
440
+ this.clearTimers(); // Clear cleaning completion timer
441
+ this.updateOperationalState(2); // Paused
442
+ }
443
+ async handleStop() {
444
+ this.logInfo('stopping.');
445
+ if (this.sharkDevice) {
446
+ try {
447
+ await this.sharkDevice.set_operating_mode(OperatingModes.STOP);
448
+ }
449
+ catch (e) {
450
+ this.logError('Error stopping Shark device:', e);
451
+ }
452
+ }
453
+ this.clearTimers();
454
+ this.updateRunMode(0); // Reset to Idle
455
+ this.updateOperationalState(0); // Stopped
456
+ }
457
+ async handleStart() {
458
+ this.logInfo('starting (via start command).');
459
+ if (this.sharkDevice) {
460
+ try {
461
+ await this.sharkDevice.set_operating_mode(OperatingModes.START);
462
+ }
463
+ catch (e) {
464
+ this.logError('Error starting Shark device:', e);
465
+ }
466
+ }
467
+ // Clear any existing timers
468
+ this.clearTimers();
469
+ this.updateRunMode(1); // Set to Cleaning mode - this will trigger the run mode handler logic
470
+ this.updateOperationalState(1); // Running
471
+ // Simulate cleaning completion after 15 seconds
472
+ const completionTimer = setTimeout(() => {
473
+ this.logInfo('cleaning completed, returning to dock.');
474
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
475
+ this.returnToDock();
476
+ }, 15000);
477
+ this.activeTimers.push(completionTimer);
478
+ }
479
+ async handleResume() {
480
+ this.logInfo('resuming.');
481
+ if (this.sharkDevice) {
482
+ try {
483
+ await this.sharkDevice.set_operating_mode(OperatingModes.START);
484
+ }
485
+ catch (e) {
486
+ this.logError('Error resuming Shark device:', e);
487
+ }
488
+ }
489
+ // Clear any existing timers
490
+ this.clearTimers();
491
+ this.updateRunMode(1); // Set to Cleaning mode
492
+ this.updateOperationalState(1); // Running
493
+ // Simulate cleaning completion after 10 seconds (shorter since resuming)
494
+ const completionTimer = setTimeout(() => {
495
+ this.logInfo('cleaning completed, returning to dock.');
496
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
497
+ this.returnToDock();
498
+ }, 10000);
499
+ this.activeTimers.push(completionTimer);
500
+ }
501
+ async handleGoHome() {
502
+ this.logInfo('goHome command received.');
503
+ if (this.sharkDevice) {
504
+ try {
505
+ await this.sharkDevice.set_operating_mode(OperatingModes.RETURN);
506
+ }
507
+ catch (e) {
508
+ this.logError('Error sending Shark device home:', e);
509
+ }
510
+ }
511
+ // Clear any existing timers
512
+ this.clearTimers();
513
+ // Defer state updates to ensure handler completes first
514
+ setImmediate(() => {
515
+ // Set to Idle mode since we're ending the cleaning session
516
+ this.updateRunMode(0);
517
+ // Initiate return to dock sequence
518
+ this.returnToDock();
519
+ });
520
+ }
521
+ async handleSelectAreas(request) {
522
+ this.logInfo(`SelectAreas request received: ${JSON.stringify(request)}`);
523
+ const { newAreas } = request;
524
+ if (this.sharkDevice && typeof this.sharkDevice.get_room_list === 'function') {
525
+ try {
526
+ const rooms = this.sharkDevice.get_room_list() || [];
527
+ const selected = newAreas.map((id) => rooms[id]).filter(Boolean);
528
+ this.logInfo(`selecting areas: ${selected.join(', ')}`);
529
+ if (selected.length > 0) {
530
+ await this.sharkDevice.clean_rooms(selected);
531
+ }
532
+ }
533
+ catch (e) {
534
+ this.logError('Error selecting areas on Shark device:', e);
535
+ }
536
+ }
537
+ else {
538
+ const areaNames = newAreas.map((id) => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
539
+ this.logInfo(`selecting areas: ${areaNames.join(', ')}`);
540
+ }
541
+ }
542
+ async handleSkipArea(request) {
543
+ this.logInfo(`SkipArea request received: ${JSON.stringify(request)}`);
544
+ const { skippedArea } = request;
545
+ const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][skippedArea] || `Area ${skippedArea}`;
546
+ this.logInfo(`skipping area: ${areaName}`);
547
+ // Shark API does not provide skipArea; best-effort: log and leave running session intact.
548
+ }
549
+ /**
550
+ * Helper method to clear all active timers
551
+ */
552
+ clearTimers() {
553
+ this.activeTimers.forEach(timer => clearTimeout(timer));
554
+ this.activeTimers = [];
555
+ }
556
+ /**
557
+ * Helper method to initiate return to dock sequence
558
+ * Can be called synchronously from other handlers
559
+ */
560
+ returnToDock() {
561
+ this.logInfo('initiating return to dock sequence.');
562
+ // Defer ALL state updates to ensure handler completes first
563
+ setImmediate(() => {
564
+ // Start seeking charger directly (skip intermediate Stopped state)
565
+ this.updateOperationalState(64); // Seeking Charger
566
+ // After 5 seconds, start charging
567
+ const chargingTimer = setTimeout(() => {
568
+ this.logInfo('reached dock, now charging.');
569
+ this.updateOperationalState(65); // Charging
570
+ // After 3 more seconds, fully docked
571
+ const dockedTimer = setTimeout(() => {
572
+ this.logInfo('fully charged and docked.');
573
+ this.updateOperationalState(66); // Docked
574
+ }, 3000);
575
+ this.activeTimers.push(dockedTimer);
576
+ }, 5000);
577
+ this.activeTimers.push(chargingTimer);
578
+ });
579
+ }
580
+ /**
581
+ * Update Methods - Use these to update the vacuum state from your API/device
582
+ *
583
+ * Since this is a platform accessory, state updates work immediately after registration.
584
+ */
585
+ updateOperationalState(state) {
586
+ this.updateState('rvcOperationalState', { operationalState: state });
587
+ const states = [
588
+ 'Stopped',
589
+ 'Running',
590
+ 'Paused',
591
+ 'Error',
592
+ null,
593
+ null,
594
+ null,
595
+ null,
596
+ ...Array.from({ length: 56 }).fill(null),
597
+ 'Seeking Charger',
598
+ 'Charging',
599
+ 'Docked',
600
+ ];
601
+ this.logInfo(`operational state updated to: ${states[state] || `Unknown (${state})`}`);
602
+ }
603
+ updateRunMode(mode) {
604
+ this.updateState('rvcRunMode', { currentMode: mode });
605
+ const modes = ['Idle', 'Cleaning', 'Mapping'];
606
+ this.logInfo(`run mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
607
+ }
608
+ updateCleanMode(mode) {
609
+ this.updateState('rvcCleanMode', { currentMode: mode });
610
+ const modes = [
611
+ 'Vacuum',
612
+ 'Mop',
613
+ 'Vacuum & Mop',
614
+ 'Deep Clean',
615
+ 'Deep Vacuum',
616
+ 'Deep Mop',
617
+ 'Quick Clean',
618
+ 'Max Clean',
619
+ 'Min Clean',
620
+ 'Quiet Vacuum',
621
+ 'Quiet Mop',
622
+ 'Night Mode',
623
+ 'Eco Vacuum',
624
+ 'Eco Mop',
625
+ 'Auto',
626
+ ];
627
+ this.logInfo(`clean mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
628
+ }
629
+ updateSelectedAreas(areaIds) {
630
+ this.updateState('serviceArea', { selectedAreas: areaIds });
631
+ const areaNames = areaIds.map(id => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
632
+ this.logInfo(`selected areas updated to: ${areaNames.join(', ') || 'All Areas'}`);
633
+ }
634
+ updateCurrentArea(areaId) {
635
+ this.updateState('serviceArea', { currentArea: areaId });
636
+ if (areaId !== null) {
637
+ const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][areaId] || `Area ${areaId}`;
638
+ this.logInfo(`current area updated to: ${areaName}`);
639
+ }
640
+ else {
641
+ this.logInfo('current area cleared');
642
+ }
643
+ }
644
+ updateProgress(progress) {
645
+ this.updateState('serviceArea', { progress });
646
+ this.logInfo(`progress updated: ${progress.length} areas`);
647
+ }
648
+ }
649
+ //# sourceMappingURL=RoboticVacuumAccessory.js.map