@homebridge-plugins/homebridge-sharkiq 2.0.0-beta.12 → 2.0.0-beta.13

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.
@@ -1,86 +1,125 @@
1
- import { TIMEOUTS } from '../constants.js';
2
- import { OperatingModes, PowerModes, Properties } from '../sharkiq-js/sharkiq.js';
3
- const MatterAccessoryEventTypes = globalThis.MatterAccessoryEventTypes || {};
4
- // Simplified RoboticVacuumAccessory implementation based on the template
5
- export class RoboticVacuumAccessory {
6
- api;
7
- log;
8
- device;
9
- isReady = false;
10
- publishedAccessory = null;
11
- pollIntervalId = null;
12
- dockedDelay = 0;
13
- dockedUpdateInterval = TIMEOUTS.DEFAULT_DOCKED_UPDATE_INTERVAL;
14
- constructor(api, log, device) {
15
- this.api = api;
16
- this.log = log;
17
- this.device = device;
18
- }
19
- // Simple retry helper with exponential backoff
20
- async retry(fn, attempts = 3, initialDelay = 500) {
21
- let lastError;
22
- let delay = initialDelay;
23
- for (let i = 0; i < attempts; i++) {
24
- try {
25
- return await fn();
26
- }
27
- catch (e) {
28
- lastError = e;
29
- this.log.debug(`Retry ${i + 1}/${attempts} failed:`, e);
30
- if (i < attempts - 1) {
31
- await new Promise(res => setTimeout(res, delay));
32
- delay *= 2;
33
- }
34
- }
35
- }
36
- throw lastError;
37
- }
38
- toAccessory() {
39
- const serialNumber = this.device._dsn && this.device._dsn.toString ? this.device._dsn.toString() : String(this.device._dsn);
40
- const uuid = this.api.matter.uuid.generate(serialNumber);
41
- const deviceType = this.api.matter.deviceTypes.RoboticVacuumCleaner;
42
- const acc = {
43
- uuid,
44
- displayName: this.device._name || `Robot Vacuum ${serialNumber}`,
45
- deviceType,
1
+ /* global NodeJS */
2
+ import { BaseMatterAccessory } from './BaseMatterAccessory.js';
3
+ export class RoboticVacuumAccessory extends BaseMatterAccessory {
4
+ activeTimers = [];
5
+ constructor(api, log) {
6
+ const serialNumber = 'VACUUM-001';
7
+ super(api, log, {
8
+ uuid: api.matter.uuid.generate(serialNumber),
9
+ displayName: 'Robot Vacuum',
10
+ deviceType: api.matter.deviceTypes.RoboticVacuumCleaner,
46
11
  serialNumber,
47
- manufacturer: 'Shark',
48
- model: this.device._vac_model_number || 'SharkIQ',
49
- firmwareRevision: this.device._firmware_version || '1.0.0',
12
+ manufacturer: 'Homebridge Matter',
13
+ model: 'HB-MATTER-VACUUM-ROBOTIC',
14
+ firmwareRevision: '2.0.0',
50
15
  hardwareRevision: '1.0.0',
51
16
  clusters: {
17
+ // Run Mode: Controls what the vacuum is doing (Idle, Cleaning, Mapping)
52
18
  rvcRunMode: {
53
19
  supportedModes: [
54
- { label: 'Idle', mode: 0 },
55
- { label: 'Cleaning', mode: 1 },
56
- { label: 'Mapping', mode: 2 },
20
+ { label: 'Idle', mode: 0, modeTags: [{ value: 16384 }] }, // RvcRunMode.ModeTag.Idle
21
+ { label: 'Cleaning', mode: 1, modeTags: [{ value: 16385 }] }, // RvcRunMode.ModeTag.Cleaning
22
+ { label: 'Mapping', mode: 2, modeTags: [{ value: 16386 }] }, // RvcRunMode.ModeTag.Mapping
57
23
  ],
58
24
  currentMode: 0,
59
25
  },
26
+ // Clean Mode: Controls HOW the vacuum cleans
27
+ // You can combine semantic tags (0-9) with functional tags (16384-16386)
28
+ // Available semantic tags: Auto, Quick, Quiet, LowNoise, LowEnergy, Vacation, Min, Max, Night, Day
29
+ // Available functional tags: DeepClean, Vacuum, Mop
60
30
  rvcCleanMode: {
61
31
  supportedModes: [
62
- { label: 'Vacuum', mode: 0 },
63
- { label: 'Mop', mode: 1 },
64
- { label: 'Vacuum & Mop', mode: 2 },
32
+ // Basic functional modes
33
+ { label: 'Vacuum', mode: 0, modeTags: [{ value: 16385 }] }, // Vacuum
34
+ { label: 'Mop', mode: 1, modeTags: [{ value: 16386 }] }, // Mop
35
+ { label: 'Vacuum & Mop', mode: 2, modeTags: [{ value: 16385 }, { value: 16386 }] }, // Both
36
+ // Deep clean modes
37
+ { label: 'Deep Clean', mode: 3, modeTags: [{ value: 16384 }] }, // DeepClean
38
+ { label: 'Deep Vacuum', mode: 4, modeTags: [{ value: 16384 }, { value: 16385 }] }, // DeepClean + Vacuum
39
+ { label: 'Deep Mop', mode: 5, modeTags: [{ value: 16384 }, { value: 16386 }] }, // DeepClean + Mop
40
+ // Intensity modes
41
+ { label: 'Quick Clean', mode: 6, modeTags: [{ value: 1 }, { value: 16385 }] }, // Quick + Vacuum
42
+ { label: 'Max Clean', mode: 7, modeTags: [{ value: 7 }, { value: 16385 }] }, // Max + Vacuum
43
+ { label: 'Min Clean', mode: 8, modeTags: [{ value: 6 }, { value: 16385 }] }, // Min + Vacuum
44
+ // Quiet modes
45
+ { label: 'Quiet Vacuum', mode: 9, modeTags: [{ value: 2 }, { value: 16385 }] }, // Quiet + Vacuum
46
+ { label: 'Quiet Mop', mode: 10, modeTags: [{ value: 2 }, { value: 16386 }] }, // Quiet + Mop
47
+ { label: 'Night Mode', mode: 11, modeTags: [{ value: 8 }, { value: 16385 }] }, // Night + Vacuum
48
+ // Energy efficient
49
+ { label: 'Eco Vacuum', mode: 12, modeTags: [{ value: 4 }, { value: 16385 }] }, // LowEnergy + Vacuum
50
+ { label: 'Eco Mop', mode: 13, modeTags: [{ value: 4 }, { value: 16386 }] }, // LowEnergy + Mop
51
+ // Auto mode
52
+ { label: 'Auto', mode: 14, modeTags: [{ value: 0 }, { value: 16385 }] }, // Auto + Vacuum
65
53
  ],
66
- currentMode: 0,
54
+ currentMode: 0, // start with basic Vacuum
67
55
  },
56
+ // Operational State: Current state (Stopped, Running, Paused, Error, etc.)
68
57
  rvcOperationalState: {
69
58
  operationalStateList: [
70
- { operationalStateId: 0, operationalStateLabel: 'Stopped' },
71
- { operationalStateId: 1, operationalStateLabel: 'Running' },
72
- { operationalStateId: 2, operationalStateLabel: 'Paused' },
73
- { operationalStateId: 64, operationalStateLabel: 'Seeking Charger' },
74
- { operationalStateId: 65, operationalStateLabel: 'Charging' },
75
- { operationalStateId: 66, operationalStateLabel: 'Docked' },
59
+ { operationalStateId: 0 }, // stopped (standard label from Matter spec)
60
+ { operationalStateId: 1 }, // running
61
+ { operationalStateId: 2 }, // paused
62
+ { operationalStateId: 3 }, // error
63
+ { operationalStateId: 64 }, // seeking charger
64
+ { operationalStateId: 65 }, // charging
65
+ { operationalStateId: 66 }, // docked
76
66
  ],
77
- operationalState: 0,
67
+ operationalState: 66, // start docked
78
68
  },
69
+ // Service Area: Room/zone selection for targeted cleaning
79
70
  serviceArea: {
80
- supportedMaps: [],
81
- supportedAreas: [],
82
- selectedAreas: [],
83
- currentArea: null,
71
+ supportedMaps: [], // empty array - we don't use map features
72
+ supportedAreas: [
73
+ {
74
+ areaId: 0,
75
+ mapId: null, // required: must be null when supportedMaps is empty
76
+ areaInfo: {
77
+ locationInfo: {
78
+ locationName: 'Living Room',
79
+ floorNumber: 0,
80
+ areaType: 7, // AreaNamespaceTag.LivingRoom
81
+ },
82
+ landmarkInfo: null,
83
+ },
84
+ },
85
+ {
86
+ areaId: 1,
87
+ mapId: null,
88
+ areaInfo: {
89
+ locationInfo: {
90
+ locationName: 'Kitchen',
91
+ floorNumber: 0,
92
+ areaType: 10, // AreaNamespaceTag.Kitchen
93
+ },
94
+ landmarkInfo: null,
95
+ },
96
+ },
97
+ {
98
+ areaId: 2,
99
+ mapId: null,
100
+ areaInfo: {
101
+ locationInfo: {
102
+ locationName: 'Bedroom',
103
+ floorNumber: 0,
104
+ areaType: 2, // AreaNamespaceTag.Bedroom
105
+ },
106
+ landmarkInfo: null,
107
+ },
108
+ },
109
+ {
110
+ areaId: 3,
111
+ mapId: null,
112
+ areaInfo: {
113
+ locationInfo: {
114
+ locationName: 'Bathroom',
115
+ floorNumber: 0,
116
+ areaType: 6, // AreaNamespaceTag.Bathroom
117
+ },
118
+ landmarkInfo: null,
119
+ },
120
+ },
121
+ ],
122
+ selectedAreas: [0, 1, 2, 3], // all areas selected by default
84
123
  },
85
124
  },
86
125
  handlers: {
@@ -91,9 +130,9 @@ export class RoboticVacuumAccessory {
91
130
  changeToMode: async (request) => this.handleChangeCleanMode(request),
92
131
  },
93
132
  rvcOperationalState: {
94
- start: async () => this.handleStart(),
95
- stop: async () => this.handleStop(),
96
133
  pause: async () => this.handlePause(),
134
+ stop: async () => this.handleStop(),
135
+ start: async () => this.handleStart(),
97
136
  resume: async () => this.handleResume(),
98
137
  goHome: async () => this.handleGoHome(),
99
138
  },
@@ -102,266 +141,235 @@ export class RoboticVacuumAccessory {
102
141
  skipArea: async (request) => this.handleSkipArea(request),
103
142
  },
104
143
  },
105
- };
106
- return acc;
144
+ });
145
+ this.logInfo('initialized and ready.');
107
146
  }
108
- // Attach event listeners to the exact published Matter accessory object
109
- attachToPublishedAccessory(accessory) {
110
- // Keep a reference to the exact published accessory object so we can update clusters later
111
- this.publishedAccessory = accessory;
112
- try {
113
- accessory._eventEmitter?.on(MatterAccessoryEventTypes.READY, async (port) => {
114
- this.isReady = true;
115
- this.log.info(`Matter accessory ready on port ${port} for ${accessory.displayName}`);
116
- // When ready, perform an initial sync to populate state
117
- try {
118
- await this.performInitialSync();
119
- // Start periodic polling after initial sync
120
- this.startPolling();
121
- }
122
- catch (e) {
123
- this.log.debug('performInitialSync error', e);
124
- }
125
- });
126
- accessory._eventEmitter?.on(MatterAccessoryEventTypes.COMMISSIONED, () => {
127
- this.log.info(`Matter accessory commissioned: ${accessory.displayName}`);
128
- });
129
- accessory._eventEmitter?.on(MatterAccessoryEventTypes.DECOMMISSIONED, () => {
130
- this.log.warn(`Matter accessory decommissioned: ${accessory.displayName}`);
131
- });
132
- }
133
- catch (e) {
134
- this.log.debug('attachToPublishedAccessory error', e);
147
+ async handleChangeRunMode(request) {
148
+ this.logInfo(`ChangeToMode (run) request received: ${JSON.stringify(request)}`);
149
+ const { newMode } = request;
150
+ const modeStr = ['Idle', 'Cleaning', 'Mapping'][newMode] || `Unknown (mode=${newMode})`;
151
+ this.logInfo(`changing run mode to: ${modeStr}`);
152
+ // TODO: await myVacuumAPI.setRunMode(newMode)
153
+ // Clear any existing timers
154
+ this.clearTimers();
155
+ if (newMode === 1) {
156
+ // Switching to Cleaning mode - start the vacuum
157
+ this.updateOperationalState(1); // Running
158
+ // Simulate cleaning completion after 15 seconds
159
+ const completionTimer = setTimeout(() => {
160
+ this.logInfo('cleaning completed, returning to dock.');
161
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
162
+ this.returnToDock();
163
+ }, 15000);
164
+ this.activeTimers.push(completionTimer);
165
+ }
166
+ else if (newMode === 0) {
167
+ // Switching to Idle mode - return to dock
168
+ this.returnToDock();
169
+ }
170
+ else if (newMode === 2) {
171
+ // Switching to Mapping mode - start mapping
172
+ this.updateOperationalState(1); // Running
173
+ // Simulate mapping completion after 20 seconds
174
+ const completionTimer = setTimeout(() => {
175
+ this.logInfo('mapping completed, returning to dock.');
176
+ this.returnToDock();
177
+ }, 20000);
178
+ this.activeTimers.push(completionTimer);
135
179
  }
136
180
  }
137
- // Perform initial device sync once the Matter accessory is ready
138
- async performInitialSync() {
139
- try {
140
- this.log.debug('Performing initial device sync for Matter accessory', this.device._dsn);
141
- if (typeof this.device.update === 'function') {
142
- await this.retry(() => this.device.update());
143
- }
144
- // Update Matter clusters with current device state (power mode, docked status, etc.)
145
- await this.updateOperationalState();
146
- await this.updateRunMode();
147
- await this.updateCleanMode();
148
- await this.updateSelectedAreas();
149
- await this.updateProgress();
150
- }
151
- catch (e) {
152
- this.log.debug('performInitialSync error', e);
153
- }
181
+ async handleChangeCleanMode(request) {
182
+ this.logInfo(`ChangeToMode (clean) request received: ${JSON.stringify(request)}`);
183
+ const { newMode } = request;
184
+ const modes = [
185
+ 'Vacuum',
186
+ 'Mop',
187
+ 'Vacuum & Mop',
188
+ 'Deep Clean',
189
+ 'Deep Vacuum',
190
+ 'Deep Mop',
191
+ 'Quick Clean',
192
+ 'Max Clean',
193
+ 'Min Clean',
194
+ 'Quiet Vacuum',
195
+ 'Quiet Mop',
196
+ 'Night Mode',
197
+ 'Eco Vacuum',
198
+ 'Eco Mop',
199
+ 'Auto',
200
+ ];
201
+ const modeStr = modes[newMode] || `Unknown (mode=${newMode})`;
202
+ this.logInfo(`changing clean mode to: ${modeStr}`);
203
+ // TODO: await myVacuumAPI.setCleanMode(newMode)
154
204
  }
155
- // Start periodic polling for device state
156
- startPolling() {
157
- if (this.pollIntervalId) {
158
- return;
159
- }
160
- const interval = this.dockedUpdateInterval || TIMEOUTS.DEFAULT_DOCKED_UPDATE_INTERVAL;
161
- this.pollIntervalId = setInterval(async () => {
162
- try {
163
- const maybeDelay = await this.retry(() => this.device.update([Properties.DOCKED_STATUS, Properties.OPERATING_MODE, Properties.POWER_MODE]));
164
- if (typeof maybeDelay === 'number') {
165
- this.dockedDelay = maybeDelay;
166
- }
167
- await this.updateOperationalState();
168
- await this.updateRunMode();
169
- await this.updateCleanMode();
170
- await this.updateSelectedAreas();
171
- await this.updateProgress();
172
- }
173
- catch (e) {
174
- this.log.debug('Polling update error', e);
175
- }
176
- }, interval + this.dockedDelay);
205
+ async handlePause() {
206
+ this.logInfo('pausing.');
207
+ // TODO: await myVacuumAPI.pause()
208
+ this.clearTimers(); // Clear cleaning completion timer
209
+ this.updateOperationalState(2); // Paused
177
210
  }
178
- // Stop periodic polling
179
- stopPolling() {
180
- if (this.pollIntervalId) {
181
- clearInterval(this.pollIntervalId);
182
- this.pollIntervalId = null;
183
- }
211
+ async handleStop() {
212
+ this.logInfo('stopping.');
213
+ // TODO: await myVacuumAPI.stop()
214
+ this.clearTimers();
215
+ this.updateRunMode(0); // Reset to Idle
216
+ this.updateOperationalState(0); // Stopped
184
217
  }
185
- // Helper to set cluster values defensively on the published accessory object
186
- setClusterValue(clusterName, prop, value) {
187
- try {
188
- if (!this.publishedAccessory) {
189
- return;
190
- }
191
- // If accessory exposes an update method, prefer it
192
- if (typeof this.publishedAccessory.updateCluster === 'function') {
193
- this.publishedAccessory.updateCluster(clusterName, { [prop]: value });
194
- return;
195
- }
196
- // Otherwise, update the clusters object directly
197
- if (this.publishedAccessory.clusters && this.publishedAccessory.clusters[clusterName]) {
198
- this.publishedAccessory.clusters[clusterName][prop] = value;
199
- }
200
- }
201
- catch (e) {
202
- this.log.debug('setClusterValue error', e);
203
- }
218
+ async handleStart() {
219
+ this.logInfo('starting (via start command).');
220
+ // TODO: await myVacuumAPI.start()
221
+ // Clear any existing timers
222
+ this.clearTimers();
223
+ this.updateRunMode(1); // Set to Cleaning mode - this will trigger the run mode handler logic
224
+ this.updateOperationalState(1); // Running
225
+ // Simulate cleaning completion after 15 seconds
226
+ const completionTimer = setTimeout(() => {
227
+ this.logInfo('cleaning completed, returning to dock.');
228
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
229
+ this.returnToDock();
230
+ }, 15000);
231
+ this.activeTimers.push(completionTimer);
204
232
  }
205
- // Update operational state cluster from device state
206
- async updateOperationalState() {
207
- try {
208
- await this.device.update();
209
- const mode = this.device.operating_mode();
210
- // Map operating modes to RVC operationalState values
211
- // 0: Stopped, 1: Running, 2: Paused, 64: Seeking Charger, 65: Charging, 66: Docked
212
- let opState = 0;
213
- if (mode === OperatingModes.START) {
214
- opState = 1;
215
- }
216
- else if (mode === OperatingModes.STOP) {
217
- opState = 2;
218
- }
219
- // Docked status overrides when docked
220
- const docked = this.device.docked_status && this.device.docked_status() === 1;
221
- if (docked) {
222
- opState = 66;
223
- }
224
- this.setClusterValue('rvcOperationalState', 'operationalState', opState);
225
- }
226
- catch (e) {
227
- this.log.debug('updateOperationalState error', e);
228
- }
233
+ async handleResume() {
234
+ this.logInfo('resuming.');
235
+ // TODO: await myVacuumAPI.resume()
236
+ // Clear any existing timers
237
+ this.clearTimers();
238
+ this.updateRunMode(1); // Set to Cleaning mode
239
+ this.updateOperationalState(1); // Running
240
+ // Simulate cleaning completion after 10 seconds (shorter since resuming)
241
+ const completionTimer = setTimeout(() => {
242
+ this.logInfo('cleaning completed, returning to dock.');
243
+ this.updateRunMode(0); // Set to Idle - cleaning session ending
244
+ this.returnToDock();
245
+ }, 10000);
246
+ this.activeTimers.push(completionTimer);
229
247
  }
230
- // Update run mode cluster (Idle / Cleaning / Mapping)
231
- async updateRunMode() {
232
- try {
233
- await this.device.update();
234
- const mode = this.device.operating_mode();
235
- let runMode = 0;
236
- if (mode === OperatingModes.START) {
237
- runMode = 1;
238
- }
239
- this.setClusterValue('rvcRunMode', 'currentMode', runMode);
240
- }
241
- catch (e) {
242
- this.log.debug('updateRunMode error', e);
243
- }
248
+ async handleGoHome() {
249
+ this.logInfo('goHome command received.');
250
+ // TODO: await myVacuumAPI.goHome()
251
+ // Clear any existing timers
252
+ this.clearTimers();
253
+ // Defer state updates to ensure handler completes first
254
+ setImmediate(() => {
255
+ // Set to Idle mode since we're ending the cleaning session
256
+ this.updateRunMode(0);
257
+ // Initiate return to dock sequence
258
+ this.returnToDock();
259
+ });
244
260
  }
245
- // Update clean mode cluster (Vacuum / Mop / Vacuum & Mop)
246
- async updateCleanMode() {
247
- try {
248
- await this.device.update();
249
- const power = this.device.power_mode && this.device.power_mode();
250
- // Best-effort mapping: treat power mode as clean mode placeholder
251
- let cleanMode = 0;
252
- if (power === PowerModes.MAX) {
253
- cleanMode = 0;
254
- }
255
- this.setClusterValue('rvcCleanMode', 'currentMode', cleanMode);
256
- }
257
- catch (e) {
258
- this.log.debug('updateCleanMode error', e);
259
- }
261
+ async handleSelectAreas(request) {
262
+ this.logInfo(`SelectAreas request received: ${JSON.stringify(request)}`);
263
+ const { newAreas } = request;
264
+ const areaNames = newAreas.map((id) => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
265
+ this.logInfo(`selecting areas: ${areaNames.join(', ')}`);
266
+ // TODO: await myVacuumAPI.selectAreas(newAreas)
260
267
  }
261
- // Update selected areas / supported areas cluster
262
- async updateSelectedAreas() {
263
- try {
264
- // If device exposes map/area info, use it. Otherwise leave defaults.
265
- const maps = this.device.maps || [];
266
- const areas = this.device.areas || [];
267
- this.setClusterValue('serviceArea', 'supportedMaps', maps);
268
- this.setClusterValue('serviceArea', 'supportedAreas', areas);
269
- // No selectedAreas by default
270
- }
271
- catch (e) {
272
- this.log.debug('updateSelectedAreas error', e);
273
- }
268
+ async handleSkipArea(request) {
269
+ this.logInfo(`SkipArea request received: ${JSON.stringify(request)}`);
270
+ const { skippedArea } = request;
271
+ const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][skippedArea] || `Area ${skippedArea}`;
272
+ this.logInfo(`skipping area: ${areaName}`);
273
+ // TODO: await myVacuumAPI.skipArea(skippedArea)
274
274
  }
275
- // Update progress / current area cluster
276
- async updateProgress() {
277
- try {
278
- await this.device.update();
279
- const currentArea = this.device.current_area || null;
280
- this.setClusterValue('serviceArea', 'currentArea', currentArea);
281
- }
282
- catch (e) {
283
- this.log.debug('updateProgress error', e);
284
- }
275
+ /**
276
+ * Helper method to clear all active timers
277
+ */
278
+ clearTimers() {
279
+ this.activeTimers.forEach(timer => clearTimeout(timer));
280
+ this.activeTimers = [];
285
281
  }
286
- // Handler implementations delegate to the underlying SharkIqVacuum device
287
- async handleStart() {
288
- this.log.info('Matter -> Start');
289
- try {
290
- await this.retry(() => this.device.clean_rooms([]));
291
- }
292
- catch (e) {
293
- this.log.debug('handleStart error', e);
294
- }
282
+ /**
283
+ * Helper method to initiate return to dock sequence
284
+ * Can be called synchronously from other handlers
285
+ */
286
+ returnToDock() {
287
+ this.logInfo('initiating return to dock sequence.');
288
+ // Defer ALL state updates to ensure handler completes first
289
+ setImmediate(() => {
290
+ // Start seeking charger directly (skip intermediate Stopped state)
291
+ this.updateOperationalState(64); // Seeking Charger
292
+ // After 5 seconds, start charging
293
+ const chargingTimer = setTimeout(() => {
294
+ this.logInfo('reached dock, now charging.');
295
+ this.updateOperationalState(65); // Charging
296
+ // After 3 more seconds, fully docked
297
+ const dockedTimer = setTimeout(() => {
298
+ this.logInfo('fully charged and docked.');
299
+ this.updateOperationalState(66); // Docked
300
+ }, 3000);
301
+ this.activeTimers.push(dockedTimer);
302
+ }, 5000);
303
+ this.activeTimers.push(chargingTimer);
304
+ });
295
305
  }
296
- async handleStop() {
297
- this.log.info('Matter -> Stop');
298
- try {
299
- await this.retry(() => this.device.cancel_clean());
300
- }
301
- catch (e) {
302
- this.log.debug('handleStop error', e);
303
- }
306
+ /**
307
+ * Update Methods - Use these to update the vacuum state from your API/device
308
+ *
309
+ * Since this is a platform accessory, state updates work immediately after registration.
310
+ */
311
+ updateOperationalState(state) {
312
+ this.updateState('rvcOperationalState', { operationalState: state });
313
+ const states = [
314
+ 'Stopped',
315
+ 'Running',
316
+ 'Paused',
317
+ 'Error',
318
+ null,
319
+ null,
320
+ null,
321
+ null,
322
+ ...Array.from({ length: 56 }).fill(null),
323
+ 'Seeking Charger',
324
+ 'Charging',
325
+ 'Docked',
326
+ ];
327
+ this.logInfo(`operational state updated to: ${states[state] || `Unknown (${state})`}`);
304
328
  }
305
- async handlePause() {
306
- this.log.info('Matter -> Pause');
307
- try {
308
- await this.retry(() => this.device.set_operating_mode(this.device.OperatingModes?.STOP ?? 2));
309
- }
310
- catch (e) {
311
- this.log.debug('handlePause error', e);
312
- }
329
+ updateRunMode(mode) {
330
+ this.updateState('rvcRunMode', { currentMode: mode });
331
+ const modes = ['Idle', 'Cleaning', 'Mapping'];
332
+ this.logInfo(`run mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
313
333
  }
314
- async handleResume() {
315
- this.log.info('Matter -> Resume');
316
- try {
317
- await this.retry(() => this.device.set_operating_mode(this.device.OperatingModes?.START ?? 1));
318
- }
319
- catch (e) {
320
- this.log.debug('handleResume error', e);
321
- }
334
+ updateCleanMode(mode) {
335
+ this.updateState('rvcCleanMode', { currentMode: mode });
336
+ const modes = [
337
+ 'Vacuum',
338
+ 'Mop',
339
+ 'Vacuum & Mop',
340
+ 'Deep Clean',
341
+ 'Deep Vacuum',
342
+ 'Deep Mop',
343
+ 'Quick Clean',
344
+ 'Max Clean',
345
+ 'Min Clean',
346
+ 'Quiet Vacuum',
347
+ 'Quiet Mop',
348
+ 'Night Mode',
349
+ 'Eco Vacuum',
350
+ 'Eco Mop',
351
+ 'Auto',
352
+ ];
353
+ this.logInfo(`clean mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
322
354
  }
323
- async handleGoHome() {
324
- this.log.info('Matter -> GoHome');
325
- try {
326
- await this.retry(() => this.device.cancel_clean());
327
- }
328
- catch (e) {
329
- this.log.debug('handleGoHome error', e);
330
- }
355
+ updateSelectedAreas(areaIds) {
356
+ this.updateState('serviceArea', { selectedAreas: areaIds });
357
+ const areaNames = areaIds.map(id => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
358
+ this.logInfo(`selected areas updated to: ${areaNames.join(', ') || 'All Areas'}`);
331
359
  }
332
- async handleChangeRunMode(request) {
333
- this.log.info('Matter -> Change Run Mode', request);
334
- const newMode = request?.newMode;
335
- try {
336
- if (newMode === 1) {
337
- await this.retry(() => this.device.clean_rooms([]));
338
- }
339
- else if (newMode === 0) {
340
- await this.retry(() => this.device.cancel_clean());
341
- }
360
+ updateCurrentArea(areaId) {
361
+ this.updateState('serviceArea', { currentArea: areaId });
362
+ if (areaId !== null) {
363
+ const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][areaId] || `Area ${areaId}`;
364
+ this.logInfo(`current area updated to: ${areaName}`);
342
365
  }
343
- catch (e) {
344
- this.log.debug('handleChangeRunMode error', e);
366
+ else {
367
+ this.logInfo('current area cleared');
345
368
  }
346
369
  }
347
- async handleChangeCleanMode(request) {
348
- this.log.info('Matter -> Change Clean Mode', request);
349
- // Best-effort: implement if device supports clean mode change
350
- }
351
- async handleSelectAreas(request) {
352
- this.log.info('Matter -> Select Areas', request);
353
- const areas = request?.newAreas;
354
- if (Array.isArray(areas) && areas.length > 0) {
355
- try {
356
- await this.retry(() => this.device.clean_rooms(areas));
357
- }
358
- catch (e) {
359
- this.log.debug('handleSelectAreas error', e);
360
- }
361
- }
362
- }
363
- async handleSkipArea(request) {
364
- this.log.info('Matter -> Skip Area', request);
370
+ updateProgress(progress) {
371
+ this.updateState('serviceArea', { progress });
372
+ this.logInfo(`progress updated: ${progress.length} areas`);
365
373
  }
366
374
  }
367
375
  //# sourceMappingURL=RoboticVacuumAccessory.js.map