@homebridge-plugins/homebridge-sharkiq 2.0.0-beta.2 → 2.0.0-beta.21
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/CHANGELOG.md +19 -2
- package/README.md +41 -4
- package/config.schema.json +8 -26
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/devices/BaseMatterAccessory.d.ts +67 -0
- package/dist/devices/BaseMatterAccessory.d.ts.map +1 -0
- package/dist/devices/BaseMatterAccessory.js +153 -0
- package/dist/devices/BaseMatterAccessory.js.map +1 -0
- package/dist/devices/RoboticVacuumAccessory.d.ts +67 -0
- package/dist/devices/RoboticVacuumAccessory.d.ts.map +1 -0
- package/dist/devices/RoboticVacuumAccessory.js +645 -0
- package/dist/devices/RoboticVacuumAccessory.js.map +1 -0
- package/dist/devices/index.d.ts +9 -0
- package/dist/devices/index.d.ts.map +1 -0
- package/dist/devices/index.js +8 -0
- package/dist/devices/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +39 -16
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +195 -92
- package/dist/platform.js.map +1 -1
- package/dist/sharkiq-js/room_encoding.d.ts +8 -0
- package/dist/sharkiq-js/room_encoding.d.ts.map +1 -0
- package/dist/sharkiq-js/room_encoding.js +35 -0
- package/dist/sharkiq-js/room_encoding.js.map +1 -0
- package/dist/sharkiq-js/sharkiq.d.ts.map +1 -1
- package/dist/sharkiq-js/sharkiq.js +2 -20
- package/dist/sharkiq-js/sharkiq.js.map +1 -1
- package/docs/assets/highlight.css +9 -2
- package/docs/index.html +35 -3
- package/docs/variables/default.html +2 -1
- package/package.json +3 -3
- package/test/context.spec.ts +33 -0
- package/test/encodeRoomList.spec.ts +25 -0
- package/test/polling.spec.ts +74 -0
- package/dist/errorHandling.d.ts +0 -19
- package/dist/errorHandling.d.ts.map +0 -1
- package/dist/errorHandling.js +0 -27
- package/dist/errorHandling.js.map +0 -1
- package/dist/platformAccessory.d.ts +0 -33
- package/dist/platformAccessory.d.ts.map +0 -1
- package/dist/platformAccessory.js +0 -384
- package/dist/platformAccessory.js.map +0 -1
|
@@ -0,0 +1,645 @@
|
|
|
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
|
+
// Note: battery/diagnostics updates are sent at runtime from polling via
|
|
144
|
+
// updateState(). The previous implementation placed static objects under
|
|
145
|
+
// `handlers` using non-standard cluster names ('power' / 'diagnostics'),
|
|
146
|
+
// which can cause errors when those clusters are not supported by the
|
|
147
|
+
// Matter server. We avoid registering invalid handlers here; runtime
|
|
148
|
+
// updates will be guarded by BaseMatterAccessory.updateState().
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
this.sharkDevice = sharkDevice;
|
|
152
|
+
// Start polling device state if we have a Shark device and polling is enabled
|
|
153
|
+
if (this.sharkDevice && typeof pollIntervalMs === 'number' && pollIntervalMs > 0) {
|
|
154
|
+
this.startPolling(pollIntervalMs);
|
|
155
|
+
}
|
|
156
|
+
else if (this.sharkDevice && (typeof pollIntervalMs !== 'number' || pollIntervalMs <= 0)) {
|
|
157
|
+
// If pollIntervalMs is omitted, default to 30s
|
|
158
|
+
if (typeof pollIntervalMs !== 'number') {
|
|
159
|
+
this.startPolling();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
this.logInfo('initialized and ready.');
|
|
163
|
+
}
|
|
164
|
+
startPolling(intervalMs = 30000) {
|
|
165
|
+
if (this.pollInterval) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.pollInterval = setInterval(async () => {
|
|
169
|
+
try {
|
|
170
|
+
// Request a full update
|
|
171
|
+
await this.sharkDevice.update([]);
|
|
172
|
+
// Update run/operating mode
|
|
173
|
+
const opMode = this.sharkDevice.operating_mode?.() ?? null;
|
|
174
|
+
const powerMode = this.sharkDevice.power_mode?.() ?? null;
|
|
175
|
+
const docked = this.sharkDevice.docked_status?.() ?? null;
|
|
176
|
+
// Map operating mode to Matter run/operational state
|
|
177
|
+
if (docked !== null && docked === 1) {
|
|
178
|
+
// Docked
|
|
179
|
+
this.updateOperationalState(66);
|
|
180
|
+
this.updateRunMode(0);
|
|
181
|
+
}
|
|
182
|
+
else if (opMode !== null) {
|
|
183
|
+
if (opMode === OperatingModes.START) {
|
|
184
|
+
this.updateRunMode(1);
|
|
185
|
+
this.updateOperationalState(1);
|
|
186
|
+
}
|
|
187
|
+
else if (opMode === OperatingModes.PAUSE) {
|
|
188
|
+
this.updateOperationalState(2);
|
|
189
|
+
this.updateRunMode(1);
|
|
190
|
+
}
|
|
191
|
+
else if (opMode === OperatingModes.RETURN) {
|
|
192
|
+
this.updateOperationalState(64);
|
|
193
|
+
this.updateRunMode(0);
|
|
194
|
+
}
|
|
195
|
+
else if (opMode === OperatingModes.STOP) {
|
|
196
|
+
this.updateOperationalState(0);
|
|
197
|
+
this.updateRunMode(0);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Map power mode to clean mode (best-effort mapping)
|
|
201
|
+
if (powerMode !== null) {
|
|
202
|
+
let cleanMode = 0; // Vacuum
|
|
203
|
+
if (powerMode === PowerModes.ECO) {
|
|
204
|
+
cleanMode = 12; // Eco Vacuum
|
|
205
|
+
}
|
|
206
|
+
else if (powerMode === PowerModes.MAX) {
|
|
207
|
+
cleanMode = 7; // Max Clean
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
cleanMode = 0;
|
|
211
|
+
}
|
|
212
|
+
this.updateCleanMode(cleanMode);
|
|
213
|
+
}
|
|
214
|
+
// Update service areas selection from device room list
|
|
215
|
+
if (typeof this.sharkDevice.get_room_list === 'function') {
|
|
216
|
+
const rooms = this.sharkDevice.get_room_list() || [];
|
|
217
|
+
const ids = rooms.map((_, idx) => idx);
|
|
218
|
+
this.updateSelectedAreas(ids);
|
|
219
|
+
}
|
|
220
|
+
// Update battery level, charging and diagnostics using upstream helpers when available
|
|
221
|
+
try {
|
|
222
|
+
// Prefer convenience properties if present on the wrapper
|
|
223
|
+
let batteryVal = null;
|
|
224
|
+
let chargingVal = null;
|
|
225
|
+
let errVal = null;
|
|
226
|
+
let errText = null;
|
|
227
|
+
if (this.sharkDevice.battery_capacity && typeof this.sharkDevice.battery_capacity === 'function') {
|
|
228
|
+
batteryVal = this.sharkDevice.battery_capacity();
|
|
229
|
+
}
|
|
230
|
+
else if (typeof this.sharkDevice.get_property_value === 'function') {
|
|
231
|
+
batteryVal = this.sharkDevice.get_property_value(Properties.BATTERY_CAPACITY);
|
|
232
|
+
}
|
|
233
|
+
if (this.sharkDevice.charging_status && typeof this.sharkDevice.charging_status === 'function') {
|
|
234
|
+
chargingVal = this.sharkDevice.charging_status();
|
|
235
|
+
}
|
|
236
|
+
else if (typeof this.sharkDevice.get_property_value === 'function') {
|
|
237
|
+
chargingVal = this.sharkDevice.get_property_value(Properties.CHARGING_STATUS);
|
|
238
|
+
}
|
|
239
|
+
// Error code / text
|
|
240
|
+
if (this.sharkDevice.error_code !== undefined && typeof this.sharkDevice.error_code === 'function') {
|
|
241
|
+
errVal = this.sharkDevice.error_code();
|
|
242
|
+
if (this.sharkDevice.error_text && typeof this.sharkDevice.error_text === 'function') {
|
|
243
|
+
errText = this.sharkDevice.error_text();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (typeof this.sharkDevice.get_property_value === 'function') {
|
|
247
|
+
errVal = this.sharkDevice.get_property_value(Properties.ERROR_CODE);
|
|
248
|
+
}
|
|
249
|
+
if (batteryVal !== null && typeof batteryVal !== 'undefined') {
|
|
250
|
+
const pct = Number(batteryVal) || 0;
|
|
251
|
+
// chargingVal may be boolean-like or numeric
|
|
252
|
+
const isCharging = chargingVal === 1 || chargingVal === true;
|
|
253
|
+
await this.updateState('power', { batteryLevel: Math.max(0, Math.min(100, pct)), charging: isCharging });
|
|
254
|
+
this.logInfo(`battery updated: ${pct}% (charging: ${isCharging})`);
|
|
255
|
+
}
|
|
256
|
+
// RSSI (signal strength) mapping if available
|
|
257
|
+
try {
|
|
258
|
+
const rssi = this.sharkDevice.get_property_value
|
|
259
|
+
? this.sharkDevice.get_property_value(Properties.RSSI)
|
|
260
|
+
: null;
|
|
261
|
+
if (rssi !== null && typeof rssi !== 'undefined') {
|
|
262
|
+
await this.updateState('diagnostics', { ...null, rssi: Number(rssi) });
|
|
263
|
+
this.logInfo(`rssi updated: ${rssi}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
// ignore
|
|
268
|
+
}
|
|
269
|
+
if (errVal && Number(errVal) > 0) {
|
|
270
|
+
const errCode = Number(errVal);
|
|
271
|
+
// Prefer human friendly text from device wrapper if provided
|
|
272
|
+
const text = errText || ERROR_MESSAGES[errCode] || `Error ${errCode}`;
|
|
273
|
+
// Map severity: treat these as actionable errors (set operational state to Error)
|
|
274
|
+
await this.updateState('diagnostics', { errorCode: errCode, errorText: text, severity: 'critical' });
|
|
275
|
+
this.updateOperationalState(3); // Error
|
|
276
|
+
this.logWarn(`device error: ${errCode} - ${text}`);
|
|
277
|
+
}
|
|
278
|
+
else if (errVal === 0 || errVal === null || typeof errVal === 'undefined') {
|
|
279
|
+
await this.updateState('diagnostics', { errorCode: 0, errorText: '', severity: 'ok' });
|
|
280
|
+
}
|
|
281
|
+
// Clean complete mapping - if device reports a clean complete flag, reflect it
|
|
282
|
+
try {
|
|
283
|
+
const cleanComplete = this.sharkDevice.get_property_value
|
|
284
|
+
? this.sharkDevice.get_property_value(Properties.CLEAN_COMPLETE)
|
|
285
|
+
: null;
|
|
286
|
+
if (cleanComplete === 1 || cleanComplete === true) {
|
|
287
|
+
// When cleaning completes, set run mode to Idle and operational to Stopped
|
|
288
|
+
this.updateRunMode(0);
|
|
289
|
+
this.updateOperationalState(0);
|
|
290
|
+
this.logInfo('clean complete reported by device - updating state to Idle/Stopped');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
// ignore
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
this.logDebug('Error updating battery/diagnostics:', e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
this.logError('Polling error for Shark device:', e);
|
|
303
|
+
}
|
|
304
|
+
}, intervalMs);
|
|
305
|
+
}
|
|
306
|
+
stopPolling() {
|
|
307
|
+
if (this.pollInterval) {
|
|
308
|
+
clearInterval(this.pollInterval);
|
|
309
|
+
this.pollInterval = undefined;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Helper to build serviceArea cluster from Shark device rooms
|
|
313
|
+
buildServiceAreaCluster(sharkDevice) {
|
|
314
|
+
if (!sharkDevice || typeof sharkDevice.get_room_list !== 'function') {
|
|
315
|
+
return {
|
|
316
|
+
supportedMaps: [],
|
|
317
|
+
supportedAreas: [],
|
|
318
|
+
selectedAreas: [],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const rooms = sharkDevice.get_room_list() || [];
|
|
323
|
+
const supportedAreas = rooms.map((roomName, idx) => ({
|
|
324
|
+
areaId: idx,
|
|
325
|
+
mapId: null,
|
|
326
|
+
areaInfo: {
|
|
327
|
+
locationInfo: {
|
|
328
|
+
locationName: roomName,
|
|
329
|
+
floorNumber: 0,
|
|
330
|
+
areaType: 0,
|
|
331
|
+
},
|
|
332
|
+
landmarkInfo: null,
|
|
333
|
+
},
|
|
334
|
+
}));
|
|
335
|
+
return {
|
|
336
|
+
supportedMaps: [],
|
|
337
|
+
supportedAreas,
|
|
338
|
+
selectedAreas: supportedAreas.map(a => a.areaId),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
return {
|
|
343
|
+
supportedMaps: [],
|
|
344
|
+
supportedAreas: [],
|
|
345
|
+
selectedAreas: [],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async handleChangeRunMode(request) {
|
|
350
|
+
this.logInfo(`ChangeToMode (run) request received: ${JSON.stringify(request)}`);
|
|
351
|
+
const { newMode } = request;
|
|
352
|
+
const modeStr = ['Idle', 'Cleaning', 'Mapping'][newMode] || `Unknown (mode=${newMode})`;
|
|
353
|
+
this.logInfo(`changing run mode to: ${modeStr}`);
|
|
354
|
+
if (this.sharkDevice) {
|
|
355
|
+
try {
|
|
356
|
+
if (newMode === 1) {
|
|
357
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.START);
|
|
358
|
+
}
|
|
359
|
+
else if (newMode === 0) {
|
|
360
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.STOP);
|
|
361
|
+
}
|
|
362
|
+
else if (newMode === 2) {
|
|
363
|
+
// Mapping - treat as start for now
|
|
364
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.START);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
this.logError('Error setting run mode on Shark device:', e);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Clear any existing timers
|
|
372
|
+
this.clearTimers();
|
|
373
|
+
if (newMode === 1) {
|
|
374
|
+
// Switching to Cleaning mode - start the vacuum
|
|
375
|
+
this.updateOperationalState(1); // Running
|
|
376
|
+
// Simulate cleaning completion after 15 seconds
|
|
377
|
+
const completionTimer = setTimeout(() => {
|
|
378
|
+
this.logInfo('cleaning completed, returning to dock.');
|
|
379
|
+
this.updateRunMode(0); // Set to Idle - cleaning session ending
|
|
380
|
+
this.returnToDock();
|
|
381
|
+
}, 15000);
|
|
382
|
+
this.activeTimers.push(completionTimer);
|
|
383
|
+
}
|
|
384
|
+
else if (newMode === 0) {
|
|
385
|
+
// Switching to Idle mode - return to dock
|
|
386
|
+
this.returnToDock();
|
|
387
|
+
}
|
|
388
|
+
else if (newMode === 2) {
|
|
389
|
+
// Switching to Mapping mode - start mapping
|
|
390
|
+
this.updateOperationalState(1); // Running
|
|
391
|
+
// Simulate mapping completion after 20 seconds
|
|
392
|
+
const completionTimer = setTimeout(() => {
|
|
393
|
+
this.logInfo('mapping completed, returning to dock.');
|
|
394
|
+
this.returnToDock();
|
|
395
|
+
}, 20000);
|
|
396
|
+
this.activeTimers.push(completionTimer);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async handleChangeCleanMode(request) {
|
|
400
|
+
this.logInfo(`ChangeToMode (clean) request received: ${JSON.stringify(request)}`);
|
|
401
|
+
const { newMode } = request;
|
|
402
|
+
const modes = [
|
|
403
|
+
'Vacuum',
|
|
404
|
+
'Mop',
|
|
405
|
+
'Vacuum & Mop',
|
|
406
|
+
'Deep Clean',
|
|
407
|
+
'Deep Vacuum',
|
|
408
|
+
'Deep Mop',
|
|
409
|
+
'Quick Clean',
|
|
410
|
+
'Max Clean',
|
|
411
|
+
'Min Clean',
|
|
412
|
+
'Quiet Vacuum',
|
|
413
|
+
'Quiet Mop',
|
|
414
|
+
'Night Mode',
|
|
415
|
+
'Eco Vacuum',
|
|
416
|
+
'Eco Mop',
|
|
417
|
+
'Auto',
|
|
418
|
+
];
|
|
419
|
+
const modeStr = modes[newMode] || `Unknown (mode=${newMode})`;
|
|
420
|
+
this.logInfo(`changing clean mode to: ${modeStr}`);
|
|
421
|
+
if (this.sharkDevice) {
|
|
422
|
+
// Shark API doesn't expose a direct clean-mode setter here; keep local state and log.
|
|
423
|
+
this.logInfo('Clean mode change requested for Shark device (not implemented in API wrapper).');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async handlePause() {
|
|
427
|
+
this.logInfo('pausing.');
|
|
428
|
+
if (this.sharkDevice) {
|
|
429
|
+
try {
|
|
430
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.PAUSE);
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
this.logError('Error pausing Shark device:', e);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
this.clearTimers(); // Clear cleaning completion timer
|
|
437
|
+
this.updateOperationalState(2); // Paused
|
|
438
|
+
}
|
|
439
|
+
async handleStop() {
|
|
440
|
+
this.logInfo('stopping.');
|
|
441
|
+
if (this.sharkDevice) {
|
|
442
|
+
try {
|
|
443
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.STOP);
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
this.logError('Error stopping Shark device:', e);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.clearTimers();
|
|
450
|
+
this.updateRunMode(0); // Reset to Idle
|
|
451
|
+
this.updateOperationalState(0); // Stopped
|
|
452
|
+
}
|
|
453
|
+
async handleStart() {
|
|
454
|
+
this.logInfo('starting (via start command).');
|
|
455
|
+
if (this.sharkDevice) {
|
|
456
|
+
try {
|
|
457
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.START);
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
this.logError('Error starting Shark device:', e);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
// Clear any existing timers
|
|
464
|
+
this.clearTimers();
|
|
465
|
+
this.updateRunMode(1); // Set to Cleaning mode - this will trigger the run mode handler logic
|
|
466
|
+
this.updateOperationalState(1); // Running
|
|
467
|
+
// Simulate cleaning completion after 15 seconds
|
|
468
|
+
const completionTimer = setTimeout(() => {
|
|
469
|
+
this.logInfo('cleaning completed, returning to dock.');
|
|
470
|
+
this.updateRunMode(0); // Set to Idle - cleaning session ending
|
|
471
|
+
this.returnToDock();
|
|
472
|
+
}, 15000);
|
|
473
|
+
this.activeTimers.push(completionTimer);
|
|
474
|
+
}
|
|
475
|
+
async handleResume() {
|
|
476
|
+
this.logInfo('resuming.');
|
|
477
|
+
if (this.sharkDevice) {
|
|
478
|
+
try {
|
|
479
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.START);
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
this.logError('Error resuming Shark device:', e);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Clear any existing timers
|
|
486
|
+
this.clearTimers();
|
|
487
|
+
this.updateRunMode(1); // Set to Cleaning mode
|
|
488
|
+
this.updateOperationalState(1); // Running
|
|
489
|
+
// Simulate cleaning completion after 10 seconds (shorter since resuming)
|
|
490
|
+
const completionTimer = setTimeout(() => {
|
|
491
|
+
this.logInfo('cleaning completed, returning to dock.');
|
|
492
|
+
this.updateRunMode(0); // Set to Idle - cleaning session ending
|
|
493
|
+
this.returnToDock();
|
|
494
|
+
}, 10000);
|
|
495
|
+
this.activeTimers.push(completionTimer);
|
|
496
|
+
}
|
|
497
|
+
async handleGoHome() {
|
|
498
|
+
this.logInfo('goHome command received.');
|
|
499
|
+
if (this.sharkDevice) {
|
|
500
|
+
try {
|
|
501
|
+
await this.sharkDevice.set_operating_mode(OperatingModes.RETURN);
|
|
502
|
+
}
|
|
503
|
+
catch (e) {
|
|
504
|
+
this.logError('Error sending Shark device home:', e);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// Clear any existing timers
|
|
508
|
+
this.clearTimers();
|
|
509
|
+
// Defer state updates to ensure handler completes first
|
|
510
|
+
setImmediate(() => {
|
|
511
|
+
// Set to Idle mode since we're ending the cleaning session
|
|
512
|
+
this.updateRunMode(0);
|
|
513
|
+
// Initiate return to dock sequence
|
|
514
|
+
this.returnToDock();
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
async handleSelectAreas(request) {
|
|
518
|
+
this.logInfo(`SelectAreas request received: ${JSON.stringify(request)}`);
|
|
519
|
+
const { newAreas } = request;
|
|
520
|
+
if (this.sharkDevice && typeof this.sharkDevice.get_room_list === 'function') {
|
|
521
|
+
try {
|
|
522
|
+
const rooms = this.sharkDevice.get_room_list() || [];
|
|
523
|
+
const selected = newAreas.map((id) => rooms[id]).filter(Boolean);
|
|
524
|
+
this.logInfo(`selecting areas: ${selected.join(', ')}`);
|
|
525
|
+
if (selected.length > 0) {
|
|
526
|
+
await this.sharkDevice.clean_rooms(selected);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (e) {
|
|
530
|
+
this.logError('Error selecting areas on Shark device:', e);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
const areaNames = newAreas.map((id) => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
|
|
535
|
+
this.logInfo(`selecting areas: ${areaNames.join(', ')}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async handleSkipArea(request) {
|
|
539
|
+
this.logInfo(`SkipArea request received: ${JSON.stringify(request)}`);
|
|
540
|
+
const { skippedArea } = request;
|
|
541
|
+
const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][skippedArea] || `Area ${skippedArea}`;
|
|
542
|
+
this.logInfo(`skipping area: ${areaName}`);
|
|
543
|
+
// Shark API does not provide skipArea; best-effort: log and leave running session intact.
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Helper method to clear all active timers
|
|
547
|
+
*/
|
|
548
|
+
clearTimers() {
|
|
549
|
+
this.activeTimers.forEach(timer => clearTimeout(timer));
|
|
550
|
+
this.activeTimers = [];
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Helper method to initiate return to dock sequence
|
|
554
|
+
* Can be called synchronously from other handlers
|
|
555
|
+
*/
|
|
556
|
+
returnToDock() {
|
|
557
|
+
this.logInfo('initiating return to dock sequence.');
|
|
558
|
+
// Defer ALL state updates to ensure handler completes first
|
|
559
|
+
setImmediate(() => {
|
|
560
|
+
// Start seeking charger directly (skip intermediate Stopped state)
|
|
561
|
+
this.updateOperationalState(64); // Seeking Charger
|
|
562
|
+
// After 5 seconds, start charging
|
|
563
|
+
const chargingTimer = setTimeout(() => {
|
|
564
|
+
this.logInfo('reached dock, now charging.');
|
|
565
|
+
this.updateOperationalState(65); // Charging
|
|
566
|
+
// After 3 more seconds, fully docked
|
|
567
|
+
const dockedTimer = setTimeout(() => {
|
|
568
|
+
this.logInfo('fully charged and docked.');
|
|
569
|
+
this.updateOperationalState(66); // Docked
|
|
570
|
+
}, 3000);
|
|
571
|
+
this.activeTimers.push(dockedTimer);
|
|
572
|
+
}, 5000);
|
|
573
|
+
this.activeTimers.push(chargingTimer);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Update Methods - Use these to update the vacuum state from your API/device
|
|
578
|
+
*
|
|
579
|
+
* Since this is a platform accessory, state updates work immediately after registration.
|
|
580
|
+
*/
|
|
581
|
+
updateOperationalState(state) {
|
|
582
|
+
this.updateState('rvcOperationalState', { operationalState: state });
|
|
583
|
+
const states = [
|
|
584
|
+
'Stopped',
|
|
585
|
+
'Running',
|
|
586
|
+
'Paused',
|
|
587
|
+
'Error',
|
|
588
|
+
null,
|
|
589
|
+
null,
|
|
590
|
+
null,
|
|
591
|
+
null,
|
|
592
|
+
...Array.from({ length: 56 }).fill(null),
|
|
593
|
+
'Seeking Charger',
|
|
594
|
+
'Charging',
|
|
595
|
+
'Docked',
|
|
596
|
+
];
|
|
597
|
+
this.logInfo(`operational state updated to: ${states[state] || `Unknown (${state})`}`);
|
|
598
|
+
}
|
|
599
|
+
updateRunMode(mode) {
|
|
600
|
+
this.updateState('rvcRunMode', { currentMode: mode });
|
|
601
|
+
const modes = ['Idle', 'Cleaning', 'Mapping'];
|
|
602
|
+
this.logInfo(`run mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
|
|
603
|
+
}
|
|
604
|
+
updateCleanMode(mode) {
|
|
605
|
+
this.updateState('rvcCleanMode', { currentMode: mode });
|
|
606
|
+
const modes = [
|
|
607
|
+
'Vacuum',
|
|
608
|
+
'Mop',
|
|
609
|
+
'Vacuum & Mop',
|
|
610
|
+
'Deep Clean',
|
|
611
|
+
'Deep Vacuum',
|
|
612
|
+
'Deep Mop',
|
|
613
|
+
'Quick Clean',
|
|
614
|
+
'Max Clean',
|
|
615
|
+
'Min Clean',
|
|
616
|
+
'Quiet Vacuum',
|
|
617
|
+
'Quiet Mop',
|
|
618
|
+
'Night Mode',
|
|
619
|
+
'Eco Vacuum',
|
|
620
|
+
'Eco Mop',
|
|
621
|
+
'Auto',
|
|
622
|
+
];
|
|
623
|
+
this.logInfo(`clean mode updated to: ${modes[mode] || `Unknown (${mode})`}`);
|
|
624
|
+
}
|
|
625
|
+
updateSelectedAreas(areaIds) {
|
|
626
|
+
this.updateState('serviceArea', { selectedAreas: areaIds });
|
|
627
|
+
const areaNames = areaIds.map(id => ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][id] || `Area ${id}`);
|
|
628
|
+
this.logInfo(`selected areas updated to: ${areaNames.join(', ') || 'All Areas'}`);
|
|
629
|
+
}
|
|
630
|
+
updateCurrentArea(areaId) {
|
|
631
|
+
this.updateState('serviceArea', { currentArea: areaId });
|
|
632
|
+
if (areaId !== null) {
|
|
633
|
+
const areaName = ['Living Room', 'Kitchen', 'Bedroom', 'Bathroom'][areaId] || `Area ${areaId}`;
|
|
634
|
+
this.logInfo(`current area updated to: ${areaName}`);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
this.logInfo('current area cleared');
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
updateProgress(progress) {
|
|
641
|
+
this.updateState('serviceArea', { progress });
|
|
642
|
+
this.logInfo(`progress updated: ${progress.length} areas`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=RoboticVacuumAccessory.js.map
|