@cecnic1989/homebridge-roborock-mower 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +32 -20
  2. package/config.schema.json +67 -12
  3. package/dist/mower/accessory.d.ts +37 -0
  4. package/dist/mower/accessory.js +134 -0
  5. package/dist/mower/accessory.js.map +1 -0
  6. package/dist/mower/state.d.ts +29 -0
  7. package/dist/mower/state.js +73 -0
  8. package/dist/mower/state.js.map +1 -0
  9. package/dist/platform.d.ts +32 -2
  10. package/dist/platform.js +157 -5
  11. package/dist/platform.js.map +1 -1
  12. package/dist/roborock/crypto.d.ts +11 -0
  13. package/dist/roborock/crypto.js +29 -0
  14. package/dist/roborock/crypto.js.map +1 -0
  15. package/dist/roborock/mower.d.ts +8 -0
  16. package/dist/roborock/mower.js +12 -0
  17. package/dist/roborock/mower.js.map +1 -0
  18. package/dist/roborock/mqtt-client.d.ts +34 -0
  19. package/dist/roborock/mqtt-client.js +102 -0
  20. package/dist/roborock/mqtt-client.js.map +1 -0
  21. package/dist/roborock/rate-limiter.d.ts +14 -0
  22. package/dist/roborock/rate-limiter.js +35 -0
  23. package/dist/roborock/rate-limiter.js.map +1 -0
  24. package/dist/roborock/session-store.d.ts +5 -0
  25. package/dist/roborock/session-store.js +40 -0
  26. package/dist/roborock/session-store.js.map +1 -0
  27. package/dist/roborock/types.d.ts +61 -0
  28. package/dist/roborock/types.js +9 -0
  29. package/dist/roborock/types.js.map +1 -0
  30. package/dist/roborock/v1-protocol.d.ts +10 -0
  31. package/dist/roborock/v1-protocol.js +76 -0
  32. package/dist/roborock/v1-protocol.js.map +1 -0
  33. package/dist/roborock/web-api.d.ts +49 -0
  34. package/dist/roborock/web-api.js +220 -0
  35. package/dist/roborock/web-api.js.map +1 -0
  36. package/dist/ui/server.d.ts +1 -0
  37. package/dist/ui/server.js +65 -0
  38. package/dist/ui/server.js.map +1 -0
  39. package/homebridge-ui/public/index.html +30 -0
  40. package/homebridge-ui/public/index.js +111 -0
  41. package/homebridge-ui/server.js +2 -0
  42. package/package.json +15 -3
package/README.md CHANGED
@@ -9,15 +9,31 @@
9
9
 
10
10
  Homebridge plugin for Roborock RockMow robot lawn mowers. Exposes your mower to Apple Home.
11
11
 
12
- > **Early development.** The plugin installs and loads, but does not yet talk to any mower. Device support is in progress.
12
+ > **Early development.** Status sensors and battery work (tested on a RockMow Z1 / X120H). Controls are next.
13
13
 
14
14
  ## Features
15
15
 
16
- Planned:
16
+ - **State sensors** for Home-app automations: Docked, Leaving, Mowing, Returning (each a contact sensor, live via Roborock's cloud push)
17
+ - **Battery** level, charging state, low-battery flag
18
+ - **Fault indicator** on every sensor when the mower reports an error
17
19
 
18
- - **Mower status** - mowing, docked, charging, paused, error
19
- - **Start / pause / return to dock** controls
20
- - **Battery level** and charging state
20
+ Planned: start / pause / return-to-dock controls.
21
+
22
+ ## Automations
23
+
24
+ Each state is a contact sensor so the Home app can trigger on it ("A Sensor Detects Something"):
25
+
26
+ | Sensor | Opens when | Closes when |
27
+ |---|---|---|
28
+ | **Docked** | it leaves the dock | it is back on the dock (charging or charged) |
29
+ | **Leaving** | a job starts (initializing / undocking) | it begins cutting |
30
+ | **Mowing** | it starts cutting or driving to a zone | cutting stops |
31
+ | **Returning** | it heads back to the dock | it reaches the dock |
32
+
33
+ Example for a dock inside a garage: *Leaving opens → open garage*, *Mowing opens → close garage*, *Returning opens → open garage*, *Docked closes → close garage*.
34
+ Note: the mower starts driving ~1.5 s after "Leaving" opens, so for the outbound trip pair it with a time-based automation on the mowing schedule if the door needs longer.
35
+
36
+ Sensors flip only after a state holds for `sensorDebounceSeconds` (default 3 s); faults and battery update immediately. While the cloud connection is down, sensors show as inactive rather than stale.
21
37
 
22
38
  ## Requirements
23
39
 
@@ -31,26 +47,22 @@ Homebridge UI: **Plugins** → search `@cecnic1989/homebridge-roborock-mower`.
31
47
 
32
48
  ## Configuration
33
49
 
34
- Minimal config:
50
+ 1. Homebridge UI → **Plugins** → **Roborock Mower** → **Settings**.
51
+ 2. Enter your Roborock account email and press **Send code**.
52
+ 3. Enter the verification code Roborock emails you and press **Verify & save**.
53
+ 4. Restart Homebridge.
35
54
 
36
- ```json
37
- {
38
- "platforms": [
39
- {
40
- "platform": "RoborockMower",
41
- "name": "Roborock Mower"
42
- }
43
- ]
44
- }
45
- ```
55
+ Sign-in stores the cloud token in `<homebridge storage>/roborock-mower/session.json` (owner-only permissions); treat it like a password. Use **Sign out** in the settings page to remove it.
46
56
 
47
- Everything else is optional:
57
+ Optional fields:
48
58
 
49
59
  | Field | Default | Description |
50
60
  |---|---|---|
51
- | `pollInterval` | `60` | How often to check the mower, in seconds (min 15, max 3600) |
52
-
53
- The easiest way to set all of this is the Homebridge UI's settings form for the plugin, which lists every option with descriptions.
61
+ | `exposeDocked`, `exposeLeaving`, `exposeMowing`, `exposeReturning` | `true` | Which state sensors to create |
62
+ | `exposeBattery` | `true` | Battery service |
63
+ | `faultIndicator` | `true` | Set StatusFault on the sensors when the mower reports an error |
64
+ | `sensorDebounceSeconds` | `3` | How long a state must hold before its sensor flips (0–60) |
65
+ | `pollInterval` | `3600` | Cloud re-sync interval in seconds (min 900). Live state comes by push; Roborock rate-limits this call, so keep it high |
54
66
 
55
67
  ## Support
56
68
 
@@ -3,27 +3,82 @@
3
3
  "pluginType": "platform",
4
4
  "singular": true,
5
5
  "strictValidation": false,
6
+ "customUi": true,
6
7
  "headerDisplay": "Homebridge plugin for Roborock RockMow robot lawn mowers.",
7
8
  "schema": {
8
9
  "type": "object",
9
10
  "properties": {
10
- "name": {
11
- "title": "Name",
12
- "type": "string",
13
- "default": "Roborock Mower"
14
- },
15
11
  "pollInterval": {
16
- "title": "Poll Interval (seconds)",
12
+ "title": "Cloud refresh interval (seconds)",
13
+ "type": "integer",
14
+ "default": 3600,
15
+ "minimum": 900,
16
+ "maximum": 86400,
17
+ "description": "Live state arrives by push; this only re-syncs with the Roborock cloud as a safety net. Roborock limits this call, keep it at an hour or more."
18
+ },
19
+ "exposeDocked": {
20
+ "title": "Docked sensor",
21
+ "type": "boolean",
22
+ "default": true
23
+ },
24
+ "exposeLeaving": {
25
+ "title": "Leaving sensor",
26
+ "type": "boolean",
27
+ "default": true
28
+ },
29
+ "exposeMowing": {
30
+ "title": "Mowing sensor",
31
+ "type": "boolean",
32
+ "default": true
33
+ },
34
+ "exposeReturning": {
35
+ "title": "Returning sensor",
36
+ "type": "boolean",
37
+ "default": true
38
+ },
39
+ "exposeBattery": {
40
+ "title": "Battery",
41
+ "type": "boolean",
42
+ "default": true
43
+ },
44
+ "faultIndicator": {
45
+ "title": "Fault indicator",
46
+ "type": "boolean",
47
+ "default": true
48
+ },
49
+ "sensorDebounceSeconds": {
50
+ "title": "Sensor debounce (seconds)",
17
51
  "type": "integer",
18
- "default": 60,
19
- "minimum": 15,
20
- "maximum": 3600,
21
- "description": "How often to poll mower status"
52
+ "default": 3,
53
+ "minimum": 0,
54
+ "maximum": 60,
55
+ "description": "How long a state must hold before its sensor flips. Faults and battery are reported immediately."
22
56
  }
23
57
  }
24
58
  },
25
59
  "layout": [
26
- "name",
27
- "pollInterval"
60
+ {
61
+ "type": "fieldset",
62
+ "title": "HomeKit sensors",
63
+ "expandable": false,
64
+ "items": [
65
+ "exposeDocked",
66
+ "exposeLeaving",
67
+ "exposeMowing",
68
+ "exposeReturning",
69
+ "exposeBattery",
70
+ "faultIndicator",
71
+ "sensorDebounceSeconds"
72
+ ]
73
+ },
74
+ {
75
+ "type": "fieldset",
76
+ "title": "Advanced",
77
+ "expandable": true,
78
+ "expanded": false,
79
+ "items": [
80
+ "pollInterval"
81
+ ]
82
+ }
28
83
  ]
29
84
  }
@@ -0,0 +1,37 @@
1
+ import type { API, Logging, PlatformAccessory } from 'homebridge';
2
+ import type { DerivedState } from './state.js';
3
+ export interface SensorOptions {
4
+ docked: boolean;
5
+ leaving: boolean;
6
+ mowing: boolean;
7
+ returning: boolean;
8
+ battery: boolean;
9
+ faultIndicator: boolean;
10
+ debounceSeconds: number;
11
+ }
12
+ export interface DeviceInformation {
13
+ model: string;
14
+ serial?: string;
15
+ firmware?: string;
16
+ }
17
+ type Hap = Pick<API['hap'], 'Service' | 'Characteristic'>;
18
+ export declare class MowerAccessory {
19
+ private readonly hap;
20
+ readonly accessory: PlatformAccessory;
21
+ private readonly log;
22
+ private readonly options;
23
+ private readonly sensors;
24
+ private readonly battery?;
25
+ private readonly applied;
26
+ private readonly pending;
27
+ private last;
28
+ constructor(hap: Hap, accessory: PlatformAccessory, log: Logging, options: SensorOptions);
29
+ setInformation(info: DeviceInformation): void;
30
+ update(state: DerivedState): void;
31
+ setOnline(online: boolean): void;
32
+ private schedule;
33
+ private apply;
34
+ private pushFault;
35
+ private pushBattery;
36
+ }
37
+ export {};
@@ -0,0 +1,134 @@
1
+ const SENSOR_LABELS = { docked: 'Docked', leaving: 'Leaving', mowing: 'Mowing', returning: 'Returning' };
2
+ const SENSOR_KEYS = Object.keys(SENSOR_LABELS);
3
+ // Home-app automations trigger on "opens"/"closes". Docked closes when the mower is home;
4
+ // the activity sensors open when their activity starts, so "Leaving opens → open the garage" reads naturally.
5
+ function contactValue(key, active, c) {
6
+ const detected = c.ContactSensorState.CONTACT_DETECTED;
7
+ const notDetected = c.ContactSensorState.CONTACT_NOT_DETECTED;
8
+ if (key === 'docked') {
9
+ return active ? detected : notDetected;
10
+ }
11
+ return active ? notDetected : detected;
12
+ }
13
+ export class MowerAccessory {
14
+ hap;
15
+ accessory;
16
+ log;
17
+ options;
18
+ sensors = new Map();
19
+ battery;
20
+ applied = new Map();
21
+ pending = new Map();
22
+ last = {};
23
+ constructor(hap, accessory, log, options) {
24
+ this.hap = hap;
25
+ this.accessory = accessory;
26
+ this.log = log;
27
+ this.options = options;
28
+ const { Service, Characteristic } = hap;
29
+ if (!accessory.getService(Service.AccessoryInformation)) {
30
+ accessory.addService(Service.AccessoryInformation);
31
+ }
32
+ for (const key of SENSOR_KEYS) {
33
+ const existing = accessory.getServiceById(Service.ContactSensor, key);
34
+ if (!options[key]) {
35
+ if (existing) {
36
+ accessory.removeService(existing);
37
+ }
38
+ continue;
39
+ }
40
+ const name = `${accessory.displayName} ${SENSOR_LABELS[key]}`;
41
+ const service = existing ?? accessory.addService(Service.ContactSensor, name, key);
42
+ service.setCharacteristic(Characteristic.Name, name);
43
+ service.setCharacteristic(Characteristic.ConfiguredName, name);
44
+ this.sensors.set(key, service);
45
+ }
46
+ const existingBattery = accessory.getService(Service.Battery);
47
+ if (options.battery) {
48
+ this.battery = existingBattery ?? accessory.addService(Service.Battery, `${accessory.displayName} Battery`);
49
+ }
50
+ else if (existingBattery) {
51
+ accessory.removeService(existingBattery);
52
+ }
53
+ }
54
+ setInformation(info) {
55
+ const { Service, Characteristic } = this.hap;
56
+ this.accessory.getService(Service.AccessoryInformation)
57
+ ?.setCharacteristic(Characteristic.Manufacturer, 'Roborock')
58
+ .setCharacteristic(Characteristic.Model, info.model)
59
+ .setCharacteristic(Characteristic.SerialNumber, info.serial ?? this.accessory.UUID)
60
+ .setCharacteristic(Characteristic.FirmwareRevision, info.firmware ?? '0');
61
+ }
62
+ update(state) {
63
+ for (const key of this.sensors.keys()) {
64
+ this.schedule(key, state[key]);
65
+ }
66
+ this.pushFault(state.fault);
67
+ this.pushBattery(state);
68
+ }
69
+ setOnline(online) {
70
+ for (const service of this.sensors.values()) {
71
+ service.updateCharacteristic(this.hap.Characteristic.StatusActive, online);
72
+ }
73
+ }
74
+ // Activity flags must hold for debounceSeconds before HomeKit sees them; the first value is applied immediately.
75
+ schedule(key, value) {
76
+ const pending = this.pending.get(key);
77
+ if (this.applied.get(key) === value) {
78
+ if (pending) {
79
+ clearTimeout(pending.timer);
80
+ this.pending.delete(key);
81
+ }
82
+ return;
83
+ }
84
+ if (pending?.value === value) {
85
+ return;
86
+ }
87
+ if (pending) {
88
+ clearTimeout(pending.timer);
89
+ }
90
+ if (!this.applied.has(key) || this.options.debounceSeconds <= 0) {
91
+ this.apply(key, value);
92
+ return;
93
+ }
94
+ const timer = setTimeout(() => {
95
+ this.pending.delete(key);
96
+ this.apply(key, value);
97
+ }, this.options.debounceSeconds * 1000);
98
+ this.pending.set(key, { value, timer });
99
+ }
100
+ apply(key, value) {
101
+ this.applied.set(key, value);
102
+ this.sensors.get(key)?.updateCharacteristic(this.hap.Characteristic.ContactSensorState, contactValue(key, value, this.hap.Characteristic));
103
+ this.log.debug(`${this.accessory.displayName}: ${SENSOR_LABELS[key]} ${value ? 'on' : 'off'}`);
104
+ }
105
+ pushFault(fault) {
106
+ if (!this.options.faultIndicator || this.last.fault === fault) {
107
+ return;
108
+ }
109
+ this.last.fault = fault;
110
+ const c = this.hap.Characteristic;
111
+ for (const service of this.sensors.values()) {
112
+ service.updateCharacteristic(c.StatusFault, fault ? c.StatusFault.GENERAL_FAULT : c.StatusFault.NO_FAULT);
113
+ }
114
+ }
115
+ pushBattery(state) {
116
+ if (!this.battery) {
117
+ return;
118
+ }
119
+ const c = this.hap.Characteristic;
120
+ if (state.battery !== undefined && state.battery !== this.last.battery) {
121
+ this.last.battery = state.battery;
122
+ this.battery.updateCharacteristic(c.BatteryLevel, state.battery);
123
+ }
124
+ if (state.lowBattery !== this.last.lowBattery) {
125
+ this.last.lowBattery = state.lowBattery;
126
+ this.battery.updateCharacteristic(c.StatusLowBattery, state.lowBattery ? c.StatusLowBattery.BATTERY_LEVEL_LOW : c.StatusLowBattery.BATTERY_LEVEL_NORMAL);
127
+ }
128
+ if (state.charging !== this.last.charging) {
129
+ this.last.charging = state.charging;
130
+ this.battery.updateCharacteristic(c.ChargingState, state.charging ? c.ChargingState.CHARGING : c.ChargingState.NOT_CHARGING);
131
+ }
132
+ }
133
+ }
134
+ //# sourceMappingURL=accessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessory.js","sourceRoot":"","sources":["../../src/mower/accessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export declare const DPS: {
2
+ readonly ERROR_CODE: 120;
3
+ readonly BATTERY: 121;
4
+ readonly MOW_TYPE: 122;
5
+ readonly MOW_STATE: 123;
6
+ readonly CHARGE_STATE: 127;
7
+ readonly DOCK_STATE: 128;
8
+ readonly CHARGE_TYPE: 129;
9
+ readonly MOW_START_TYPE: 132;
10
+ readonly MOW_PROGRESS: 139;
11
+ readonly OFF_DOCK_NO_TASK_STATUS: 143;
12
+ };
13
+ export declare const MOW_STATE_NAMES: Record<number, string>;
14
+ export interface DerivedState {
15
+ docked: boolean;
16
+ leaving: boolean;
17
+ mowing: boolean;
18
+ returning: boolean;
19
+ charging: boolean;
20
+ paused: boolean;
21
+ fault: boolean;
22
+ battery?: number;
23
+ lowBattery: boolean;
24
+ mowState?: number;
25
+ errorCode: number;
26
+ }
27
+ export type Dps = Record<number, unknown>;
28
+ export declare function deriveMowerState(dps: Dps): DerivedState;
29
+ export declare function describeMowState(code: number | undefined): string;
@@ -0,0 +1,73 @@
1
+ // DPS ids and state codes from python-roborock (RoborockMowerDataProtocol / RoborockMowerStateCode),
2
+ // cross-checked against a RockMow a282 capture (test/fixtures/dps-sequence.json).
3
+ export const DPS = {
4
+ ERROR_CODE: 120,
5
+ BATTERY: 121,
6
+ MOW_TYPE: 122,
7
+ MOW_STATE: 123,
8
+ CHARGE_STATE: 127,
9
+ DOCK_STATE: 128,
10
+ CHARGE_TYPE: 129,
11
+ MOW_START_TYPE: 132,
12
+ MOW_PROGRESS: 139,
13
+ OFF_DOCK_NO_TASK_STATUS: 143,
14
+ };
15
+ export const MOW_STATE_NAMES = {
16
+ 0: 'idle',
17
+ 1: 'map_initializing', 2: 'map_undocking', 3: 'map_undock_fault', 4: 'map_locating', 5: 'map_prepare_boundary',
18
+ 6: 'map_prepare_island', 7: 'map_prepare_path', 8: 'map_boundary', 9: 'map_island', 10: 'map_path',
19
+ 11: 'map_boundary_auto', 12: 'map_erasing', 13: 'map_save', 14: 'map_wait', 15: 'map_recoverable_fault',
20
+ 16: 'map_fault', 17: 'map_emergency_stop', 18: 'map_waiting_fault',
21
+ 51: 'mow_initializing', 52: 'mow_undocking', 53: 'mow_locating', 54: 'mow_adjust_cutter', 55: 'mow_zig_zag',
22
+ 56: 'mow_edge', 57: 'mow_goto', 58: 'mow_suspend', 59: 'mow_recoverable_fault', 60: 'mow_fault',
23
+ 61: 'mow_docked_rainfall', 62: 'mow_docked_do_not_disturb', 63: 'mow_docked_low_battery', 64: 'mow_wait',
24
+ 65: 'mow_prepare_remote', 66: 'mow_remote', 67: 'mow_emergency_stop', 68: 'mow_docked_manual', 69: 'mow_dock_fault',
25
+ 70: 'mow_remote_undocking', 71: 'mow_to_dock_initializing', 72: 'mow_to_dock_locating', 73: 'mow_to_dock_recoverable_fault',
26
+ 74: 'mow_to_dock_fault', 75: 'mow_to_dock_emergency_stop', 76: 'mow_to_dock_charging', 77: 'mow_to_dock_charge_completed',
27
+ 101: 'free', 102: 'free_initializing', 103: 'free_locating', 104: 'free_docked_manual', 105: 'free_docked_mow_end',
28
+ 106: 'free_docked_plan_end', 107: 'free_emergency_stop', 108: 'free_recoverable_fault', 109: 'free_fault',
29
+ 151: 'charge_charging', 152: 'charge_completed', 153: 'charge_waiting', 154: 'charge_fault',
30
+ };
31
+ const LEAVING_STATES = new Set([51, 52, 53, 54]);
32
+ const MOWING_STATES = new Set([55, 56, 57, 64, 65, 66, 70]);
33
+ const RETURNING_STATES = new Set([71, 72, 73, 74, 75]);
34
+ // Bare idle (0) is deliberately not here: the mower reports 0 off the dock between a task ending and 143 being set.
35
+ const DOCKED_STATES = new Set([61, 62, 63, 68, 76, 77, 104, 105, 106, 151, 152, 153]);
36
+ const CHARGING_STATES = new Set([76, 151]);
37
+ const PAUSED_STATES = new Set([17, 58, 67, 75, 107]);
38
+ const FAULT_STATES = new Set([3, 15, 16, 59, 60, 69, 73, 74, 108, 109, 154]);
39
+ const CHARGE_STATE_ON_DOCK = new Set([1, 2, 3]); // charging, completed, waiting
40
+ const LOW_BATTERY_PERCENT = 20;
41
+ function num(value) {
42
+ const n = typeof value === 'number' ? value : Number(value);
43
+ return value === undefined || value === null || Number.isNaN(n) ? undefined : n;
44
+ }
45
+ export function deriveMowerState(dps) {
46
+ const mowState = num(dps[DPS.MOW_STATE]);
47
+ const chargeState = num(dps[DPS.CHARGE_STATE]);
48
+ const offDock = num(dps[DPS.OFF_DOCK_NO_TASK_STATUS]) ?? 0;
49
+ const battery = num(dps[DPS.BATTERY]);
50
+ const errorCode = num(dps[DPS.ERROR_CODE]) ?? 0;
51
+ const state = mowState ?? -1;
52
+ const docked = (chargeState !== undefined && CHARGE_STATE_ON_DOCK.has(chargeState)) || DOCKED_STATES.has(state);
53
+ return {
54
+ docked,
55
+ leaving: LEAVING_STATES.has(state),
56
+ mowing: MOWING_STATES.has(state),
57
+ returning: !docked && (RETURNING_STATES.has(state) || offDock !== 0),
58
+ charging: chargeState === 1 || CHARGING_STATES.has(state),
59
+ paused: PAUSED_STATES.has(state),
60
+ fault: errorCode !== 0 || FAULT_STATES.has(state),
61
+ battery,
62
+ lowBattery: battery !== undefined && battery <= LOW_BATTERY_PERCENT,
63
+ mowState,
64
+ errorCode,
65
+ };
66
+ }
67
+ export function describeMowState(code) {
68
+ if (code === undefined) {
69
+ return 'unknown';
70
+ }
71
+ return MOW_STATE_NAMES[code] ?? `unknown(${code})`;
72
+ }
73
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/mower/state.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,46 @@
1
1
  import type { API, Characteristic, DynamicPlatformPlugin, Logging, PlatformAccessory, PlatformConfig, Service } from 'homebridge';
2
+ import type mqtt from 'mqtt';
3
+ import type { StoredSession } from './roborock/types.js';
2
4
  export interface RoborockMowerConfig extends PlatformConfig {
3
5
  pollInterval?: number;
6
+ exposeDocked?: boolean;
7
+ exposeLeaving?: boolean;
8
+ exposeMowing?: boolean;
9
+ exposeReturning?: boolean;
10
+ exposeBattery?: boolean;
11
+ faultIndicator?: boolean;
12
+ sensorDebounceSeconds?: number;
13
+ }
14
+ export interface PlatformDeps {
15
+ fetch?: typeof globalThis.fetch;
16
+ readSession?: (storagePath: string) => Promise<StoredSession | undefined>;
17
+ connectMqtt?: typeof mqtt.connect;
18
+ now?: () => number;
4
19
  }
5
20
  export declare class RoborockMowerPlatform implements DynamicPlatformPlugin {
6
21
  readonly log: Logging;
7
22
  readonly config: PlatformConfig;
8
23
  readonly api: API;
24
+ private readonly deps;
9
25
  readonly Service: typeof Service;
10
26
  readonly Characteristic: typeof Characteristic;
11
27
  readonly accessories: Map<string, PlatformAccessory>;
12
28
  readonly pluginConfig: RoborockMowerConfig;
13
- constructor(log: Logging, config: PlatformConfig, api: API);
29
+ private webApi?;
30
+ private mqtt?;
31
+ private homeId?;
32
+ private session?;
33
+ private reconcileTimer?;
34
+ private readonly mowers;
35
+ constructor(log: Logging, config: PlatformConfig, api: API, deps?: PlatformDeps);
14
36
  configureAccessory(accessory: PlatformAccessory): void;
15
- private discoverDevices;
37
+ private sensorOptions;
38
+ private start;
39
+ private stop;
40
+ private syncMowers;
41
+ private applyDps;
42
+ private startMqtt;
43
+ private pollSeconds;
44
+ private scheduleReconcile;
45
+ private reconcile;
16
46
  }