homebridge-roborock-vacuum 1.6.2 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,3 +72,42 @@ Follow these steps to install the plugin:
72
72
  Use the Homebridge UI settings page to sign in and configure the plugin. To exclude vacuums from HomeKit, add their Roborock device IDs to **Skipped Device IDs**.
73
73
 
74
74
  When Homebridge restarts, matching devices will be skipped during discovery. If a skipped device already exists in HomeKit as a cached accessory, the plugin will remove it from Homebridge.
75
+
76
+ ## Current Room → MQTT (optional telemetry)
77
+
78
+ The plugin can publish the room a vacuum is currently cleaning to a local MQTT broker, for use in external automations (e.g. lighting that follows the vacuum room to room). This is **telemetry only** — it is not exposed to HomeKit — and is **off by default**. It reuses the `mqtt` dependency the plugin already ships, so it adds nothing when disabled.
79
+
80
+ Enable it under **Current Room → MQTT** in the settings UI, or in `config.json`:
81
+
82
+ ```json
83
+ "currentRoomMqtt": {
84
+ "enabled": true,
85
+ "brokerUrl": "mqtt://127.0.0.1:1883",
86
+ "topic": "homebridge/roborock/{duid}/current_room",
87
+ "cleaningPollSeconds": 10
88
+ }
89
+ ```
90
+
91
+ - **topic** is a template. `{duid}` and `{name}` (the device name, slugified) are substituted. If the template contains neither token, `/{duid}` is appended automatically so multiple vacuums never publish to the same topic.
92
+ - **cleaningPollSeconds** is how often status is polled while the vacuum is actively cleaning (it polls slowly otherwise). The poll only runs while the feature is enabled.
93
+
94
+ A retained JSON message is published whenever the room changes:
95
+
96
+ ```json
97
+ {
98
+ "segment_id": 16,
99
+ "room": "Kitchen",
100
+ "state": 5,
101
+ "target_segment_id": 17,
102
+ "target_room": "Hallway",
103
+ "in_cleaning": 1,
104
+ "ts": 1718524800000
105
+ }
106
+ ```
107
+
108
+ - **segment_id / room** — the room currently being cleaned. `segment_id` is `-1` (and `room` `null`) when docked/idle, or transiently while the robot relocalizes; use `state` to distinguish.
109
+ - **target_segment_id / target_room** — the next room the robot is heading to (populated during transitions), so a consumer can pre-light it. `-1`/`null` when steady or unknown.
110
+ - **in_cleaning** — the device's own flag; `0` once a clean has concluded even while the robot returns to the dock or empties, which `state` alone does not always distinguish.
111
+ - Room names are resolved from the robot's saved map; name your rooms in the Roborock app for them to appear.
112
+
113
+ > Validated on a Roborock Qrevo (`roborock.vacuum.a185`). On models that don't populate `cleaning_info`, the payload degrades gracefully to `segment_id: -1` / `room: null`.
@@ -40,6 +40,38 @@
40
40
  "description": "When enabled, debug messages will be written to the log.",
41
41
  "type": "boolean",
42
42
  "default": false
43
+ },
44
+ "currentRoomMqtt": {
45
+ "title": "Current Room → MQTT",
46
+ "type": "object",
47
+ "description": "Publish the room each vacuum is currently cleaning to a local MQTT broker (telemetry only; not exposed to HomeKit). Off by default.",
48
+ "properties": {
49
+ "enabled": {
50
+ "title": "Enabled",
51
+ "type": "boolean",
52
+ "default": false,
53
+ "description": "Publish the current cleaning room to MQTT."
54
+ },
55
+ "brokerUrl": {
56
+ "title": "Broker URL",
57
+ "type": "string",
58
+ "default": "mqtt://127.0.0.1:1883",
59
+ "description": "MQTT broker URL."
60
+ },
61
+ "topic": {
62
+ "title": "Topic Template",
63
+ "type": "string",
64
+ "default": "homebridge/roborock/{duid}/current_room",
65
+ "description": "Topic to publish the retained current-room JSON to. Supports {duid} and {name} tokens; if neither is present, /{duid} is appended automatically so multiple vacuums don't collide."
66
+ },
67
+ "cleaningPollSeconds": {
68
+ "title": "Cleaning Poll Interval (s)",
69
+ "type": "integer",
70
+ "default": 10,
71
+ "minimum": 5,
72
+ "description": "How often to poll status while actively cleaning, to catch room changes."
73
+ }
74
+ }
43
75
  }
44
76
  },
45
77
  "required": ["email"]
package/dist/platform.js CHANGED
@@ -74,6 +74,7 @@ class RoborockPlatform {
74
74
  log: this.log,
75
75
  userData: decryptedSession,
76
76
  storagePath: storagePath,
77
+ currentRoomMqtt: this.platformConfig.currentRoomMqtt,
77
78
  });
78
79
  /**
79
80
  * When this event is fired it means Homebridge has restored all cached accessories from disk.
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;;;AAWA,0EAAyD;AAEzD,sDAA8C;AAE9C,yCAAwD;AAExD,qCAA0C;AAE1C,MAAM,YAAY,GAAG,SAAS,CAAC;AAC/B,IAAI,sBAAsB,GAAG,KAAK,CAAC;AAEnC,SAAS,+BAA+B;IACtC,IAAI,sBAAsB,EAAE,CAAC;QAC3B,OAAO;IACT,CAAC;IAED,sBAAsB,GAAG,IAAI,CAAC;IAE9B,MAAM,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,OAAO,CAAC,WAAW,GAAG,CAAC,CACrB,OAAuB,EACvB,IAAa,EACb,IAAa,EACb,IAAe,EACT,EAAE;QACR,MAAM,WAAW,GACf,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,IAAI,OAAO;YAClE,CAAC,CAAC,MAAM,CAAE,OAA6B,CAAC,IAAI,CAAC;YAC7C,CAAC,CAAC,IAAI,CAAC;QAEX,IAAI,WAAW,KAAK,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,aAAa,GAAG,IAAI,CAAC;gBACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kFAAkF,CACnF,CAAC;YACJ,CAAC;YACD,OAAO;QACT,CAAC;QAEA,mBAAoD,CACnD,OAAO,EACP,IAAI,EACJ,IAAI,EACJ,IAAI,CACL,CAAC;IACJ,CAAC,CAA+B,CAAC;AACnC,CAAC;AAED,+BAA+B,EAAE,CAAC;AAElC,MAAM,QAAQ,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC,QAAQ,CAAC;AAEhE;;;GAGG;AACH,MAAqB,gBAAgB;IAgBnC;;;;;;;OAOG;IACH,YACE,gBAAwB,EACxB,MAAsB,EACL,GAAQ;QAAR,QAAG,GAAH,GAAG,CAAK;QA1BX,YAAO,GAAmB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;QAC/C,mBAAc,GAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QAE9B,4CAA4C;QAC3B,gBAAW,GAAgC,EAAE,CAAC;QAC9C,8BAAyB,GAAgC,EAAE,CAAC;QAC5D,YAAO,GAA8B,EAAE,CAAC;QAqBvD,IAAI,CAAC,cAAc,GAAG,MAAgC,CAAC;QACvD,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,CAC7B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CACrD,CAAC;QAEF,6BAA6B;QAC7B,IAAI,CAAC,GAAG,GAAG,IAAI,gBAAsB,CACnC,gBAAgB,EAChB,IAAI,CAAC,cAAc,CAAC,SAAS,CAC9B,CAAC;QACF,2CAA2C;QAE3C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC;QAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,cAAc;YACzD,CAAC,CAAC,IAAA,uBAAc,EAAC,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,WAAW,CAAC;YACjE,CAAC,CAAC,IAAI,CAAC;QAET,IAAI,CAAC,WAAW,GAAG,IAAI,QAAQ,CAAC;YAC9B,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,OAAO;YAChB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,WAAW;SACzB,CAAC,CAAC;QAEH;;;;;WAKG;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,2DAAgC,GAAG,EAAE;YAC9C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACtE,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,qCAAoB,GAAG,EAAE;YAClC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YAEnC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;YACjC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACtC,IAAI,CAAC,+BAA+B,EAAE,CAAC;QACvC,MAAM,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,mDAAmD;gBACjD,qEAAqE,CACxE,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;YACzE,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,sDAAsD;gBACpD,2DAA2D,CAC9D,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,UAAU,EAAE,EAAE,QAAQ;YACrD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,wBAAwB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAExE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;YAC5B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACjC,mCAAmC;YACnC,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAAC,SAAoC;QACrD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,SAAS,CAAC,WAAW,eAAe,CAAC,CAAC;QAE1E,IAAI,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,cAAc,SAAS,CAAC,WAAW,MAAM,SAAS,CAAC,OAAO,kBAAkB;gBAC1E,wEAAwE,CAC3E,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACjC,OAAO;QACT,CAAC;QAED,0DAA0D;QAC1D,gCAAgC;QAEhC,IAAI,CAAC;YACH,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CACjC,CAAC;YACF,IAAI,iBAAiB,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,cAAc,SAAS,CAAC,WAAW,+BAA+B;oBAChE,8CAA8C,CACjD,CAAC;gBACF,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,sCAAsC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,iBAAiB,CAAC,KAAa;QAC7B,2CAA2C;QAC3C,OAAO,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC9C,CAAC;IAED,cAAc,CAAC,KAA4C;QACzD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAErE,OAAO,OAAO;aACX,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;aACjC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,iBAAiB,CAAC,QAAiB;QACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,YAAY,MAAM,CAAC,EAAE,CAAC;YAClE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAEO,8BAA8B;QACpC,KAAK,MAAM,eAAe,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACpD,IAAI,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpD,IAAI,CAAC,yBAAyB,CAC5B,eAAe,EACf,wCAAwC,CACzC,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAEO,+BAA+B;QACrC,KAAK,MAAM,eAAe,IAAI,CAAC,GAAG,IAAI,CAAC,yBAAyB,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,yBAAyB,CAC5B,eAAe,EACf,0EAA0E,CAC3E,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,yBAAyB,CAC/B,SAAoC,EACpC,MAAc;QAEd,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,uBAAuB,SAAS,CAAC,WAAW,MAAM,SAAS,CAAC,OAAO,KAAK,MAAM,GAAG,CAClF,CAAC;QAEF,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,sBAAW,EAAE,wBAAa,EAAE;gBACjE,SAAS;aACV,CAAC,CAAC;YACH,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,+BAA+B,SAAS,CAAC,WAAW,MAAM,SAAS,CAAC,OAAO,MAAM,KAAK,EAAE,CACzF,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAEO,sBAAsB,CAAC,SAAoC;QACjE,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;QAC7C,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACxE,IAAI,aAAa,KAAK,CAAC,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAE/C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC;YAElB,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC;gBAChC,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC,OAAO,CAAC,UAAU,MAAM;oBACvD,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;oBACvB,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;oBACvB,IAAI,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;oBAEhE,IAAI,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;wBACjC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,oBAAoB,IAAI,MAAM,IAAI,2CAA2C,CAC9E,CAAC;wBAEF,OAAO;oBACT,CAAC;oBAED,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;wBACnC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,MAAM,IAAI,qBAAqB,CAAC,CAAC;wBAE9D,OAAO;oBACT,CAAC;oBAED,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBAErD,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAC7C,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CACvC,CAAC;oBAEF,IAAI,iBAAiB,KAAK,SAAS,EAAE,CAAC;wBACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,wBAAwB,iBAAiB,CAAC,WAAW,IAAI;4BACvD,IAAI,IAAI,eAAe,CAC1B,CAAC;wBAEF,kEAAkE;wBAClE,wCAAwC;wBACxC,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC;wBACjC,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;wBAExD,0DAA0D;wBAE1D,IAAI,CAAC,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;oBAClD,CAAC;yBAAM,CAAC;wBACN,wDAAwD;wBAExD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,qBAAqB,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC;wBACvD,4DAA4D;wBAC5D,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAC9C,IAAI,EACJ,IAAI,CACL,CAAC;wBAEF,yEAAyE;wBACzE,wEAAwE;wBACxE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;wBAEzB,8DAA8D;wBAC9D,+CAA+C;wBAC/C,IAAI,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;wBAExC,sCAAsC;wBACtC,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,sBAAW,EAAE,wBAAa,EAAE;4BAC/D,SAAS;yBACV,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YAED,oFAAoF;YACpF,wEAAwE;YACxE,KAAK,MAAM,eAAe,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBACpD,IAAI,eAAe,CAAC,OAAO,EAAE,CAAC;oBAC5B,IAAI,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;wBACpD,IAAI,CAAC,yBAAyB,CAC5B,eAAe,EACf,wCAAwC,CACzC,CAAC;wBACF,SAAS;oBACX,CAAC;oBAED,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,mBAAmB,CACjD,eAAe,CAAC,OAAO,CACxB,CAAC;oBAEF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;wBACzB,4EAA4E;wBAC5E,IAAI,CAAC,yBAAyB,CAC5B,eAAe,EACf,2DAA2D,CAC5D,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,6CAA6C;gBAC3C,0CAA0C,CAC7C,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,uBAAuB,CAAC,SAAoC;QAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,0BAAuB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAClE,CAAC;CACF;AAhWD,mCAgWC"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
package/dist/ui/index.js CHANGED
@@ -8,6 +8,7 @@ const path_1 = __importDefault(require("path"));
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const crypto_2 = require("../crypto");
10
10
  const roborockAuth = require("../../roborockLib/lib/roborockAuth");
11
+ const roborockHome = require("../../roborockLib/lib/roborockHome");
11
12
  class RoborockUiServer {
12
13
  constructor(HomebridgePluginUiServer) {
13
14
  this.homebridgePluginUiServer = new HomebridgePluginUiServer();
@@ -17,6 +18,7 @@ class RoborockUiServer {
17
18
  this.homebridgePluginUiServer.onRequest("/auth/verify-2fa-code", this.verifyTwoFactorCode.bind(this));
18
19
  this.homebridgePluginUiServer.onRequest("/auth/login", this.loginWithPassword.bind(this));
19
20
  this.homebridgePluginUiServer.onRequest("/auth/logout", this.logout.bind(this));
21
+ this.homebridgePluginUiServer.onRequest("/devices/list", this.listDevices.bind(this));
20
22
  this.homebridgePluginUiServer.ready();
21
23
  }
22
24
  getStoragePath() {
@@ -181,6 +183,35 @@ class RoborockUiServer {
181
183
  }
182
184
  return { ok: true, message: "Logged out. Token cleared." };
183
185
  }
186
+ async listDevices(payload) {
187
+ if (!payload.encryptedToken) {
188
+ return { ok: false, message: "Log in first to load your devices." };
189
+ }
190
+ const userData = (0, crypto_2.decryptSession)(payload.encryptedToken, this.getStoragePath());
191
+ if (!userData) {
192
+ return {
193
+ ok: false,
194
+ message: "Saved login could not be read. Please log in again.",
195
+ };
196
+ }
197
+ try {
198
+ const clientID = await this.getClientId();
199
+ const devices = await roborockHome.fetchDevices({
200
+ baseURL: payload.baseURL || "usiot.roborock.com",
201
+ username: payload.email,
202
+ clientID,
203
+ userData,
204
+ });
205
+ return { ok: true, devices };
206
+ }
207
+ catch (error) {
208
+ console.error("Device list request failed:", (error === null || error === void 0 ? void 0 : error.message) || error);
209
+ return {
210
+ ok: false,
211
+ message: (error === null || error === void 0 ? void 0 : error.message) || "Failed to load devices.",
212
+ };
213
+ }
214
+ }
184
215
  buildNonce() {
185
216
  return crypto_1.default
186
217
  .randomBytes(12)
@@ -191,7 +222,7 @@ class RoborockUiServer {
191
222
  }
192
223
  }
193
224
  // IMPORTANT: Use Function constructor to create a dynamic import that TypeScript won't transform
194
- //
225
+ //
195
226
  // Background: @homebridge/plugin-ui-utils v2+ is a pure ES module that cannot be loaded with require()
196
227
  // in Node.js 18+. Normally we would use `await import('@homebridge/plugin-ui-utils')`, but because
197
228
  // this project uses TypeScript with "module": "commonjs" in tsconfig.json, TypeScript transforms
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"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"}
@@ -39,21 +39,33 @@
39
39
  <div class="field">
40
40
  <div class="field-header">
41
41
  <span>Skipped Device IDs</span>
42
- <button
43
- id="add-skip-device"
44
- class="icon-button"
45
- type="button"
46
- title="Add skipped device"
47
- aria-label="Add skipped device"
48
- >
49
- +
50
- </button>
42
+ <div class="field-actions">
43
+ <button
44
+ id="load-devices"
45
+ class="secondary btn-small"
46
+ type="button"
47
+ title="Load devices from your account"
48
+ >
49
+ Load devices
50
+ </button>
51
+ <button
52
+ id="add-skip-device"
53
+ class="icon-button"
54
+ type="button"
55
+ title="Add skipped device"
56
+ aria-label="Add skipped device"
57
+ >
58
+ +
59
+ </button>
60
+ </div>
51
61
  </div>
62
+ <div id="discovered-devices" class="discovered-devices"></div>
52
63
  <div id="skip-devices" class="skip-device-list"></div>
53
64
  </div>
54
65
  <p class="help">
55
- Matching devices will not be added to HomeKit and existing cached
56
- accessories will be removed on restart.
66
+ Click <strong>Load devices</strong> to pick which vacuums to hide, or
67
+ enter device IDs manually. Matching devices will not be added to HomeKit
68
+ and existing cached accessories will be removed on restart.
57
69
  </p>
58
70
  <label class="checkbox">
59
71
  <input id="debug-mode" type="checkbox" />
@@ -65,6 +77,46 @@
65
77
  </div>
66
78
  </section>
67
79
 
80
+ <section class="card">
81
+ <h2>Current Room → MQTT</h2>
82
+ <p class="help">
83
+ Publish the room each vacuum is currently cleaning to a local MQTT broker
84
+ (telemetry only; not exposed to HomeKit). Off by default.
85
+ </p>
86
+ <label class="checkbox">
87
+ <input id="room-mqtt-enabled" type="checkbox" />
88
+ Publish current cleaning room to MQTT
89
+ </label>
90
+ <label>
91
+ Broker URL
92
+ <input
93
+ id="room-mqtt-broker"
94
+ type="text"
95
+ placeholder="mqtt://127.0.0.1:1883"
96
+ />
97
+ </label>
98
+ <label>
99
+ Topic Template
100
+ <input
101
+ id="room-mqtt-topic"
102
+ type="text"
103
+ placeholder="homebridge/roborock/{duid}/current_room"
104
+ />
105
+ </label>
106
+ <p class="help">
107
+ Supports <code>{duid}</code> and <code>{name}</code> tokens; if neither is
108
+ present, <code>/{duid}</code> is appended automatically so multiple
109
+ vacuums never collide.
110
+ </p>
111
+ <label>
112
+ Cleaning Poll Interval (s)
113
+ <input id="room-mqtt-poll" type="number" min="5" placeholder="10" />
114
+ </label>
115
+ <p class="help">
116
+ How often status is polled while the vacuum is actively cleaning.
117
+ </p>
118
+ </section>
119
+
68
120
  <section class="card" id="two-factor-section">
69
121
  <h2>Two-Factor Authentication</h2>
70
122
  <p class="help">
@@ -5,7 +5,13 @@ const elements = {
5
5
  baseUrl: document.getElementById("base-url"),
6
6
  skipDevices: document.getElementById("skip-devices"),
7
7
  addSkipDevice: document.getElementById("add-skip-device"),
8
+ loadDevices: document.getElementById("load-devices"),
9
+ discoveredDevices: document.getElementById("discovered-devices"),
8
10
  debugMode: document.getElementById("debug-mode"),
11
+ roomMqttEnabled: document.getElementById("room-mqtt-enabled"),
12
+ roomMqttBroker: document.getElementById("room-mqtt-broker"),
13
+ roomMqttTopic: document.getElementById("room-mqtt-topic"),
14
+ roomMqttPoll: document.getElementById("room-mqtt-poll"),
9
15
  code: document.getElementById("two-factor-code"),
10
16
  login: document.getElementById("login"),
11
17
  logout: document.getElementById("logout"),
@@ -69,6 +75,13 @@ async function loadConfig() {
69
75
  renderSkipDevices(config.skipDevices);
70
76
  elements.debugMode.checked = Boolean(config.debugMode);
71
77
 
78
+ const roomMqtt = config.currentRoomMqtt || {};
79
+ elements.roomMqttEnabled.checked = Boolean(roomMqtt.enabled);
80
+ elements.roomMqttBroker.value = roomMqtt.brokerUrl || "";
81
+ elements.roomMqttTopic.value = roomMqtt.topic || "";
82
+ elements.roomMqttPoll.value =
83
+ roomMqtt.cleaningPollSeconds != null ? roomMqtt.cleaningPollSeconds : "";
84
+
72
85
  setLoggedInState(Boolean(config.encryptedToken));
73
86
  }
74
87
 
@@ -99,6 +112,32 @@ function getCode() {
99
112
  return elements.code.value.trim();
100
113
  }
101
114
 
115
+ function getRoomMqtt() {
116
+ const config = { enabled: Boolean(elements.roomMqttEnabled.checked) };
117
+
118
+ const brokerUrl = elements.roomMqttBroker.value.trim();
119
+ if (brokerUrl) {
120
+ config.brokerUrl = brokerUrl;
121
+ }
122
+
123
+ const topic = elements.roomMqttTopic.value.trim();
124
+ if (topic) {
125
+ config.topic = topic;
126
+ }
127
+
128
+ const pollRaw = elements.roomMqttPoll.value.trim();
129
+ const cleaningPollSeconds = Number(pollRaw);
130
+ if (pollRaw && Number.isFinite(cleaningPollSeconds)) {
131
+ config.cleaningPollSeconds = cleaningPollSeconds;
132
+ }
133
+
134
+ return config;
135
+ }
136
+
137
+ async function saveRoomMqtt() {
138
+ await updatePluginConfig({ currentRoomMqtt: getRoomMqtt() });
139
+ }
140
+
102
141
  function parseDeviceIds(value) {
103
142
  if (!value) {
104
143
  return [];
@@ -156,6 +195,93 @@ function addSkipDeviceRow(value = "", shouldFocus = true) {
156
195
  }
157
196
  }
158
197
 
198
+ function getSkipDeviceSet() {
199
+ return new Set(
200
+ getSkipDeviceInputs()
201
+ .map((input) => input.value.trim())
202
+ .filter((entry) => entry)
203
+ );
204
+ }
205
+
206
+ function toggleSkipDevice(deviceId, shouldSkip) {
207
+ const skipped = getSkipDeviceSet();
208
+ if (shouldSkip) {
209
+ skipped.add(deviceId);
210
+ } else {
211
+ skipped.delete(deviceId);
212
+ }
213
+ renderSkipDevices([...skipped]);
214
+ saveCredentials();
215
+ }
216
+
217
+ function renderDiscoveredDevices(devices) {
218
+ elements.discoveredDevices.textContent = "";
219
+
220
+ if (!Array.isArray(devices) || devices.length === 0) {
221
+ const empty = document.createElement("p");
222
+ empty.className = "help";
223
+ empty.textContent = "No devices found on this account.";
224
+ elements.discoveredDevices.appendChild(empty);
225
+ return;
226
+ }
227
+
228
+ const skipped = getSkipDeviceSet();
229
+ devices.forEach((device) => {
230
+ const row = document.createElement("label");
231
+ row.className = "checkbox";
232
+
233
+ const input = document.createElement("input");
234
+ input.type = "checkbox";
235
+ input.checked = skipped.has(device.duid);
236
+ input.addEventListener("change", () =>
237
+ toggleSkipDevice(device.duid, input.checked)
238
+ );
239
+
240
+ const label = document.createElement("span");
241
+ const model = device.model ? ` (${device.model})` : "";
242
+ const shared = device.shared ? " — shared" : "";
243
+ label.textContent = `${device.name || device.duid}${model} — ${device.duid}${shared}`;
244
+
245
+ row.append(input, label);
246
+ elements.discoveredDevices.appendChild(row);
247
+ });
248
+ }
249
+
250
+ async function loadDevices() {
251
+ if (
252
+ !window.homebridge ||
253
+ typeof window.homebridge.getPluginConfig !== "function"
254
+ ) {
255
+ return;
256
+ }
257
+
258
+ const configs = await window.homebridge.getPluginConfig();
259
+ const config = configs.find(
260
+ (entry) => entry.platform === "RoborockVacuumPlatform"
261
+ );
262
+ const encryptedToken = config && config.encryptedToken;
263
+ if (!encryptedToken) {
264
+ showToast("warning", "Log in first to load your devices.");
265
+ return;
266
+ }
267
+
268
+ elements.loadDevices.disabled = true;
269
+ try {
270
+ const result = await request("/devices/list", {
271
+ email: getEmail(),
272
+ baseURL: getBaseUrl(),
273
+ encryptedToken,
274
+ });
275
+ if (result.ok) {
276
+ renderDiscoveredDevices(result.devices);
277
+ } else {
278
+ showToast("error", result.message || "Failed to load devices.");
279
+ }
280
+ } finally {
281
+ elements.loadDevices.disabled = false;
282
+ }
283
+ }
284
+
159
285
  async function saveCredentials() {
160
286
  const email = getEmail();
161
287
  const baseURL = getBaseUrl();
@@ -325,8 +451,17 @@ function init() {
325
451
  elements.addSkipDevice.addEventListener("click", () => {
326
452
  addSkipDeviceRow();
327
453
  });
454
+ elements.loadDevices.addEventListener("click", () => {
455
+ loadDevices().catch(() => {
456
+ showToast("error", "Failed to load devices.");
457
+ });
458
+ });
328
459
  elements.debugMode.addEventListener("change", saveCredentials);
329
460
  elements.email.addEventListener("change", saveCredentials);
461
+ elements.roomMqttEnabled.addEventListener("change", saveRoomMqtt);
462
+ elements.roomMqttBroker.addEventListener("change", saveRoomMqtt);
463
+ elements.roomMqttTopic.addEventListener("change", saveRoomMqtt);
464
+ elements.roomMqttPoll.addEventListener("change", saveRoomMqtt);
330
465
  }
331
466
 
332
467
  if (window.homebridge) {
@@ -96,6 +96,28 @@ textarea {
96
96
  resize: vertical;
97
97
  }
98
98
 
99
+ .field-actions {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 8px;
103
+ }
104
+
105
+ .btn-small {
106
+ padding: 8px 14px;
107
+ font-size: 0.85rem;
108
+ }
109
+
110
+ .discovered-devices {
111
+ display: grid;
112
+ gap: 8px;
113
+ margin-top: 12px;
114
+ }
115
+
116
+ .discovered-devices .checkbox {
117
+ margin-top: 0;
118
+ word-break: break-all;
119
+ }
120
+
99
121
  .skip-device-list {
100
122
  display: grid;
101
123
  gap: 10px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-vacuum",
3
- "version": "1.6.2",
3
+ "version": "1.7.2",
4
4
  "description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -111,9 +111,12 @@ class localConnector {
111
111
  while (offset + 4 <= client.chunkBuffer.length) {
112
112
  const segmentLength = client.chunkBuffer.readUInt32BE(offset);
113
113
  const currentBuffer = client.chunkBuffer.subarray(offset + 4, offset + segmentLength + 4);
114
- // length of 17 does not contain any useful data.
115
- // It seems to be protocol handshake metadata.
116
- if (segmentLength != 17) {
114
+ // Short, header-only handshake frames carry no encrypted payload:
115
+ // the L01 HELLO_RESPONSE is 21 bytes (version+seq+random+timestamp+
116
+ // protocol+CRC32); a bare 17-byte header may also appear. Route both
117
+ // to the shortMessage handler below; anything larger is a data
118
+ // message with an encrypted payload.
119
+ if (segmentLength != 17 && segmentLength != 21) {
117
120
  const data = this.adapter.message._decodeMsg(currentBuffer, duid);
118
121
  if (!data) {
119
122
  offset += 4 + segmentLength;
@@ -146,7 +149,9 @@ class localConnector {
146
149
  else {
147
150
  try {
148
151
  const shortMessage = shortMessageParser.parse(currentBuffer);
149
- if (shortMessage.version == "L01" && shortMessage.protocol == 1) {
152
+ // HELLO_RESPONSE (protocol 1) echoing our seq=1 HELLO; its
153
+ // random field is the ack_nonce.
154
+ if (shortMessage.version == "L01" && shortMessage.protocol == 1 && shortMessage.seq == 1) {
150
155
  const currentNonces = this.adapter.localL01Nonces.get(duid) || {};
151
156
  this.adapter.localL01Nonces.set(duid, {
152
157
  connectNonce: currentNonces.connectNonce,
@@ -157,6 +162,7 @@ class localConnector {
157
162
  if (waiter) {
158
163
  this.adapter.clearTimeout(waiter.timeout);
159
164
  this.l01HandshakeWaiters.delete(duid);
165
+ this.adapter.log.debug(`L01 handshake complete for ${duid}: ackNonce=${shortMessage.random}`);
160
166
  waiter.resolve(true);
161
167
  }
162
168
  }
@@ -247,18 +253,29 @@ class localConnector {
247
253
  return;
248
254
  }
249
255
 
256
+ // HELLO request: protocol 0 (HELLO_REQUEST), 21-byte header+CRC frame, seq=1, a
257
+ // real timestamp, and random=connect_nonce. The robot replies with a protocol-1
258
+ // (HELLO_RESPONSE) frame whose random field is the ack_nonce. Pick a stable
259
+ // connect_nonce in [10000, 32767] and store it BEFORE sending, so the follow-up
260
+ // L01 data-message AAD reuses the exact value the robot saw in the HELLO.
250
261
  const timestamp = Math.floor(Date.now() / 1000);
251
- const handshakeMessage = await this.adapter.message.buildRoborockMessage(duid, 1, timestamp, Buffer.alloc(0));
252
- if (!handshakeMessage) {
253
- throw new Error(`Failed to build protocol 1 handshake message for ${duid}`);
254
- }
255
-
256
- const connectNonce = handshakeMessage.readUInt32BE(7);
262
+ const connectNonce = 10000 + Math.floor(Math.random() * 22768);
257
263
  this.adapter.localL01Nonces.set(duid, {
258
264
  connectNonce,
259
265
  ackNonce: undefined,
260
266
  });
261
267
 
268
+ const handshakeMessage = await this.adapter.message.buildRoborockMessage(
269
+ duid,
270
+ 0,
271
+ timestamp,
272
+ Buffer.alloc(0),
273
+ { seq: 1, random: connectNonce }
274
+ );
275
+ if (!handshakeMessage) {
276
+ throw new Error(`Failed to build HELLO handshake message for ${duid}`);
277
+ }
278
+
262
279
  if (this.l01HandshakeWaiters.has(duid)) {
263
280
  const waiter = this.l01HandshakeWaiters.get(duid);
264
281
  this.adapter.clearTimeout(waiter.timeout);
@@ -123,7 +123,7 @@ class message {
123
123
  return payload;
124
124
  }
125
125
 
126
- async buildRoborockMessage(duid, protocol, timestamp, payload) {
126
+ async buildRoborockMessage(duid, protocol, timestamp, payload, options = {}) {
127
127
  const version = await this.adapter.getRobotVersion(duid);
128
128
 
129
129
  let encrypted;
@@ -131,18 +131,29 @@ class message {
131
131
  const currentSeq = seq & 0xffffffff;
132
132
  const currentRandom = random & 0xffffffff;
133
133
 
134
- if (protocol == 1) {
135
- const msg = Buffer.alloc(23);
134
+ // Header-only frames (the L01 HELLO request, protocol 0) carry no encrypted
135
+ // payload, so they have NO payloadLen field — just version(3) + seq(4) + random(4)
136
+ // + timestamp(4) + protocol(2) + CRC32(4) = 21 bytes. (The previous build emitted a
137
+ // 23-byte frame with a spurious payloadLen, which L01 robots silently drop, so the
138
+ // handshake never completed.) Optional explicit seq/random let the HELLO pin seq=1
139
+ // and a stable connect_nonce without disturbing the module-global counters used by
140
+ // data messages.
141
+ if (protocol == 0 || protocol == 1) {
142
+ const headerSeq = (options.seq !== undefined ? options.seq : currentSeq) >>> 0;
143
+ const headerRandom =
144
+ (options.random !== undefined ? options.random : currentRandom) >>> 0;
145
+ const msg = Buffer.alloc(21);
136
146
  msg.write(version);
137
- msg.writeUint32BE(currentSeq, 3);
138
- msg.writeUint32BE(currentRandom, 7);
147
+ msg.writeUint32BE(headerSeq, 3);
148
+ msg.writeUint32BE(headerRandom, 7);
139
149
  msg.writeUint32BE(timestamp, 11);
140
150
  msg.writeUint16BE(protocol, 15);
141
- msg.writeUint16BE(0, 17);
142
- const crc32 = CRC32.buf(msg.subarray(0, msg.length - 4)) >>> 0;
143
- msg.writeUint32BE(crc32, msg.length - 4);
144
- seq++;
145
- random++;
151
+ const crc32 = CRC32.buf(msg.subarray(0, 17)) >>> 0;
152
+ msg.writeUint32BE(crc32, 17);
153
+ if (options.seq === undefined) {
154
+ seq++;
155
+ random++;
156
+ }
146
157
 
147
158
  return msg;
148
159
  }
@@ -21,6 +21,14 @@ class messageQueueHandler {
21
21
  this.adapter.log.info(`Local connection unavailable for ${duid}. Falling back to cloud connection for method ${method}.`);
22
22
  }
23
23
 
24
+ // Some devices (e.g. the Qrevo / roborock.vacuum.a185) keep the local socket
25
+ // connected but never answer, so the disconnect-based fallback above never
26
+ // triggers. After repeated local timeouts, prefer cloud for a cooldown window.
27
+ if (!useCloudConnection && mqttConnectionState && typeof this.adapter.shouldPreferCloudLocally === "function" && this.adapter.shouldPreferCloudLocally(duid)) {
28
+ useCloudConnection = true;
29
+ this.adapter.log.info(`Local connection for ${duid} is unresponsive. Falling back to cloud connection for method ${method}.`);
30
+ }
31
+
24
32
  if (!useCloudConnection && version == "L01") {
25
33
  try {
26
34
  await this.adapter.localConnector.ensureL01Handshake(duid);
@@ -64,6 +72,9 @@ class messageQueueHandler {
64
72
  if (useCloudConnection) {
65
73
  reject(new Error(`Cloud request with id ${messageID} with method ${method} timed out after 10 seconds. MQTT connection state: ${mqttConnectionState}`));
66
74
  } else {
75
+ if (typeof this.adapter.recordLocalTimeout === "function") {
76
+ this.adapter.recordLocalTimeout(duid);
77
+ }
67
78
  reject(new Error(`Local request with id ${messageID} with method ${method} timed out after 10 seconds Local connect state: ${localConnectionState}`));
68
79
  }
69
80
  }, requestTimeout);
@@ -73,6 +84,9 @@ class messageQueueHandler {
73
84
  if (typeof this.adapter.recordMethodSuccess === "function") {
74
85
  this.adapter.recordMethodSuccess(duid, method);
75
86
  }
87
+ if (!useCloudConnection && typeof this.adapter.recordLocalSuccess === "function") {
88
+ this.adapter.recordLocalSuccess(duid);
89
+ }
76
90
  resolve(value);
77
91
  };
78
92
 
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const axios = require("axios");
5
+ const roborockAuth = require("./roborockAuth");
6
+
7
+ function md5hex(str) {
8
+ return crypto.createHash("md5").update(str).digest("hex");
9
+ }
10
+
11
+ // Build an axios instance that signs each request with the rriot Hawk
12
+ // credentials, mirroring the signing the main plugin uses for the Roborock
13
+ // "real" API (see roborockAPI.js initUser).
14
+ function createSignedApi(rriot) {
15
+ const api = axios.create({ baseURL: rriot.r.a });
16
+ api.interceptors.request.use((config) => {
17
+ const timestamp = Math.floor(Date.now() / 1000);
18
+ const nonce = crypto
19
+ .randomBytes(6)
20
+ .toString("base64")
21
+ .substring(0, 6)
22
+ .replace("+", "X")
23
+ .replace("/", "Y");
24
+ const url = new URL(api.getUri(config));
25
+ const prestr = [
26
+ rriot.u,
27
+ rriot.s,
28
+ nonce,
29
+ timestamp,
30
+ md5hex(url.pathname),
31
+ /*queryparams*/ "",
32
+ /*body*/ "",
33
+ ].join(":");
34
+ const mac = crypto
35
+ .createHmac("sha256", rriot.h)
36
+ .update(prestr)
37
+ .digest("base64");
38
+ config.headers["Authorization"] =
39
+ `Hawk id="${rriot.u}", s="${rriot.s}", ts="${timestamp}", nonce="${nonce}", mac="${mac}"`;
40
+ return config;
41
+ });
42
+ return api;
43
+ }
44
+
45
+ // Read-only: fetch the account's devices (owned + shared) so the config UI can
46
+ // let the user pick which to skip instead of typing raw device IDs. The caller
47
+ // is responsible for providing a valid, already-decrypted userData; network or
48
+ // auth failures reject so the UI can surface a message.
49
+ async function fetchDevices({ baseURL, username, clientID, userData }) {
50
+ if (!userData || !userData.token || !userData.rriot) {
51
+ throw new Error("Not logged in.");
52
+ }
53
+
54
+ const loginApi = roborockAuth.createLoginApi({
55
+ baseURL,
56
+ username,
57
+ clientID,
58
+ language: "en",
59
+ });
60
+ loginApi.defaults.headers.common["Authorization"] = userData.token;
61
+
62
+ const homeDetail = await loginApi.get("api/v1/getHomeDetail");
63
+ const homeId =
64
+ homeDetail && homeDetail.data && homeDetail.data.data
65
+ ? homeDetail.data.data.rrHomeId
66
+ : null;
67
+ if (!homeId) {
68
+ throw new Error("Failed to resolve the Roborock home id.");
69
+ }
70
+
71
+ const api = createSignedApi(userData.rriot);
72
+ const homedata = await api.get(`v2/user/homes/${homeId}`);
73
+ const result = (homedata && homedata.data && homedata.data.result) || {};
74
+
75
+ const products = result.products || [];
76
+ const modelOf = (productId) => {
77
+ const product = products.find((entry) => entry.id == productId);
78
+ return product ? product.model : null;
79
+ };
80
+
81
+ const toEntry = (device, shared) => ({
82
+ duid: device.duid,
83
+ name: device.name,
84
+ model: modelOf(device.productId),
85
+ shared,
86
+ });
87
+
88
+ const owned = (result.devices || []).map((device) => toEntry(device, false));
89
+ const received = (result.receivedDevices || []).map((device) =>
90
+ toEntry(device, true)
91
+ );
92
+
93
+ return owned.concat(received);
94
+ }
95
+
96
+ module.exports = { fetchDevices };
@@ -162,6 +162,38 @@ class vacuum {
162
162
  const now = new Date();
163
163
  const seconds = now.getSeconds();
164
164
 
165
+ // current-room -> MQTT (opt-in telemetry): a dedicated lightweight poll,
166
+ // independent of the legacy branch below. That branch is gated on
167
+ // `this.adapter.socket` (a live web-UI client) OR an undefined
168
+ // `config.updateInterval` (=> NaN, never true), so it does not run in
169
+ // normal headless operation and can't be relied on to publish. Polls fast
170
+ // while cleaning, idle otherwise; serialized by `_roomPollInFlight`; never
171
+ // runs when the feature is disabled, so default behaviour is unchanged.
172
+ const roomMqttCfg = this.adapter.config && this.adapter.config.currentRoomMqtt;
173
+ if (roomMqttCfg && roomMqttCfg.enabled) {
174
+ const vac = this.adapter.vacuums[duid];
175
+ const idleInterval = this.adapter.updateInterval || 180;
176
+ const fastInterval = roomMqttCfg.cleaningPollSeconds || 10;
177
+ const interval = vac && vac._lastIsCleaning ? Math.min(fastInterval, idleInterval) : idleInterval;
178
+ if (seconds % interval == 0 && !(vac && vac._roomPollInFlight)) {
179
+ if (vac) vac._roomPollInFlight = true;
180
+ try {
181
+ const ds = await this.adapter.messageQueueHandler.sendRequest(duid, "get_prop", ["get_status"]);
182
+ const s0 = (ds && ds[0]) || {};
183
+ if (vac) vac._lastIsCleaning = this.adapter.isCleaning(s0["state"]);
184
+ this.adapter.publishCurrentRoom(duid, {
185
+ cleaningInfo: s0["cleaning_info"],
186
+ state: s0["state"],
187
+ inCleaning: s0["in_cleaning"],
188
+ });
189
+ } catch (e) {
190
+ this.adapter.log.debug(`current_room poll failed: ${e && e.message}`);
191
+ } finally {
192
+ if (vac) vac._roomPollInFlight = false;
193
+ }
194
+ }
195
+ }
196
+
165
197
  if (this.adapter.socket || seconds % this.adapter.config.updateInterval == 0) {
166
198
  // only send status every minute or if websocket is connected
167
199
 
@@ -257,6 +289,9 @@ class vacuum {
257
289
 
258
290
  if (roomName) {
259
291
  this.adapter.log.debug(`Mapped room matched: ${roomID} with name: ${roomName}`);
292
+ // current-room -> MQTT: cache segment_id -> name for the publisher.
293
+ if (!this.adapter.segmentRoomNames[duid]) this.adapter.segmentRoomNames[duid] = {};
294
+ this.adapter.segmentRoomNames[duid][mappedRoom[0]] = roomName;
260
295
  const objectString = `Devices.${duid}.floors.${roomFloor}.${mappedRoom[0]}`;
261
296
  await this.adapter.createStateObjectHelper(objectString, roomName, "boolean", null, true, "value", true, true);
262
297
  }
@@ -21,6 +21,7 @@ const roborockPackageHelper =
21
21
  const deviceFeatures = require("./lib/deviceFeatures").deviceFeatures;
22
22
  const messageQueueHandler =
23
23
  require("./lib/messageQueueHandler").messageQueueHandler;
24
+ const mqtt = require("mqtt");
24
25
 
25
26
  let socketServer, webserver;
26
27
 
@@ -50,6 +51,12 @@ class Roborock {
50
51
  this.localKeys = null;
51
52
  this.localL01Nonces = new Map();
52
53
  this.roomIDs = {};
54
+ // current-room -> MQTT (opt-in telemetry): per-duid segment_id -> room name
55
+ // cache, a lazily-opened local publisher client, and the last-published
56
+ // dedupe key per duid. All inert unless config.currentRoomMqtt.enabled.
57
+ this.segmentRoomNames = {};
58
+ this.localMqttClient = null;
59
+ this._lastRoomPublishKey = {};
53
60
  this.vacuums = {};
54
61
  this.initializedVacuumDuids = new Set();
55
62
  this.socket = null;
@@ -84,6 +91,15 @@ class Roborock {
84
91
  this.methodBackoffThreshold = 3; // consecutive timeouts before backing off
85
92
  this.methodBackoffInterval = 30 * 60 * 1000; // 30 min before a retry
86
93
 
94
+ // Some newer models (e.g. the Qrevo / roborock.vacuum.a185) keep the local TCP
95
+ // connection open but never answer, so every local request times out. Track
96
+ // consecutive local timeouts per device and, once over the threshold, prefer the
97
+ // cloud path for a cooldown window (retrying local afterwards). Keyed by duid.
98
+ this.localTimeoutStreak = new Map();
99
+ this.localCloudPreference = new Map();
100
+ this.localFailoverThreshold = 3; // consecutive local timeouts before preferring cloud
101
+ this.localFailoverInterval = 5 * 60 * 1000; // 5 min preferring cloud before retrying local
102
+
87
103
  this.localDevices = {};
88
104
  this.remoteDevices = new Set();
89
105
 
@@ -1268,6 +1284,16 @@ class Roborock {
1268
1284
  this.clearInterval(this.vacuums[duid].mainUpdateInterval);
1269
1285
  }
1270
1286
 
1287
+ // current-room -> MQTT: tear down the local publisher client.
1288
+ if (this.localMqttClient) {
1289
+ try {
1290
+ this.localMqttClient.end(true);
1291
+ } catch (e) {
1292
+ /* ignore */
1293
+ }
1294
+ this.localMqttClient = null;
1295
+ }
1296
+
1271
1297
  this.messageQueue.forEach(({ timeout102, timeout301 }) => {
1272
1298
  this.clearTimeout(timeout102);
1273
1299
  if (timeout301) {
@@ -1764,6 +1790,117 @@ class Roborock {
1764
1790
  }
1765
1791
  }
1766
1792
 
1793
+ // current-room -> MQTT: lazily open a LOCAL mqtt publisher (separate from the
1794
+ // Roborock cloud connection). Telemetry only — it never subscribes. Never
1795
+ // throws; a down broker only logs at debug and retries via reconnectPeriod.
1796
+ ensureLocalMqtt() {
1797
+ const cfg = this.config && this.config.currentRoomMqtt;
1798
+ if (!cfg || !cfg.enabled) return null;
1799
+ if (this.localMqttClient) return this.localMqttClient;
1800
+
1801
+ try {
1802
+ const url = cfg.brokerUrl || "mqtt://127.0.0.1:1883";
1803
+ const client = mqtt.connect(url, {
1804
+ reconnectPeriod: 5000,
1805
+ connectTimeout: 10000,
1806
+ });
1807
+ client.on("error", (e) =>
1808
+ this.log.debug(`current_room MQTT error: ${e && e.message}`)
1809
+ );
1810
+ client.on("connect", () =>
1811
+ this.log.debug(`current_room MQTT connected to ${url}`)
1812
+ );
1813
+ // After a reconnect, drop the dedupe cache so the (retained) current room
1814
+ // is re-published — a broker bounce may have lost the retained message.
1815
+ client.on("reconnect", () => {
1816
+ this._lastRoomPublishKey = {};
1817
+ });
1818
+ this.localMqttClient = client;
1819
+ } catch (e) {
1820
+ this.log.debug(`current_room MQTT connect failed: ${e && e.message}`);
1821
+ this.localMqttClient = null;
1822
+ }
1823
+ return this.localMqttClient;
1824
+ }
1825
+
1826
+ // current-room -> MQTT: resolve the publish topic for a device. The configured
1827
+ // topic is a template: {duid} and {name} tokens are substituted; if it contains
1828
+ // no token, /{duid} is appended so multiple vacuums never collide on one topic.
1829
+ resolveRoomTopic(duid) {
1830
+ const cfg = this.config && this.config.currentRoomMqtt;
1831
+ let topic = (cfg && cfg.topic) || "homebridge/roborock/{duid}/current_room";
1832
+ const hasToken = topic.includes("{duid}") || topic.includes("{name}");
1833
+ if (!hasToken) {
1834
+ topic = `${topic.replace(/\/+$/, "")}/${duid}`;
1835
+ }
1836
+ const rawName = (this.vacuums[duid] && this.vacuums[duid].name) || duid;
1837
+ const slug = String(rawName)
1838
+ .toLowerCase()
1839
+ .replace(/[^a-z0-9]+/g, "_")
1840
+ .replace(/^_+|_+$/g, "");
1841
+ // Fall back to duid if the name slugifies to empty (e.g. a symbol-only name),
1842
+ // so a {name}-only template can never collide or produce a malformed topic.
1843
+ const name = slug || duid;
1844
+ return topic.replace(/\{duid\}/g, duid).replace(/\{name\}/g, name);
1845
+ }
1846
+
1847
+ // current-room -> MQTT: publish the room a vacuum is currently cleaning.
1848
+ // segment_id comes from get_status.cleaning_info; the name is resolved from the
1849
+ // segmentRoomNames cache (populated by the get_room_mapping handler). -1 means
1850
+ // docked/idle or, when state indicates motion, relocalizing. target_* expose the
1851
+ // next room (cleaning_info.target_segment_id populates during transitions) so a
1852
+ // consumer can pre-light it; in_cleaning is the device flag (0 once a clean
1853
+ // concludes, even while it returns/empties). Fire-and-forget; never rejects.
1854
+ publishCurrentRoom(duid, raw) {
1855
+ try {
1856
+ const cfg = this.config && this.config.currentRoomMqtt;
1857
+ if (!cfg || !cfg.enabled) return;
1858
+ const client = this.ensureLocalMqtt();
1859
+ if (!client || !client.connected) return;
1860
+
1861
+ const ci = raw && raw.cleaningInfo;
1862
+ const segId =
1863
+ ci && typeof ci.segment_id === "number" ? ci.segment_id : -1;
1864
+ const room =
1865
+ segId >= 0 ? this.segmentRoomNames?.[duid]?.[segId] ?? null : null;
1866
+ const tSegId =
1867
+ ci && typeof ci.target_segment_id === "number"
1868
+ ? ci.target_segment_id
1869
+ : -1;
1870
+ const targetRoom =
1871
+ tSegId >= 0 ? this.segmentRoomNames?.[duid]?.[tSegId] ?? null : null;
1872
+ const state = typeof raw.state === "number" ? raw.state : null;
1873
+ const inCleaning =
1874
+ typeof raw.inCleaning === "number" ? raw.inCleaning : null;
1875
+
1876
+ // target_segment_id and in_cleaning MUST be in the dedupe key: they change
1877
+ // while segment_id/state may not, so without them those transitions (e.g. a
1878
+ // target flip -1 -> N -> -1) would be deduped away.
1879
+ const key = `${segId}|${room}|${state}|${tSegId}|${targetRoom}|${inCleaning}`;
1880
+ if (key === this._lastRoomPublishKey[duid]) return; // dedupe (ts excluded)
1881
+ this._lastRoomPublishKey[duid] = key;
1882
+
1883
+ const topic = this.resolveRoomTopic(duid);
1884
+ const payload = JSON.stringify({
1885
+ segment_id: segId,
1886
+ room,
1887
+ state,
1888
+ target_segment_id: tSegId,
1889
+ target_room: targetRoom,
1890
+ in_cleaning: inCleaning,
1891
+ ts: Date.now(),
1892
+ });
1893
+ client.publish(topic, payload, { retain: true, qos: 0 }, (err) => {
1894
+ if (err) {
1895
+ this.log.debug(`current_room MQTT publish failed: ${err.message}`);
1896
+ this._lastRoomPublishKey[duid] = undefined; // allow retry on next change
1897
+ }
1898
+ });
1899
+ } catch (e) {
1900
+ this.log.debug(`publishCurrentRoom error: ${e && e.message}`);
1901
+ }
1902
+ }
1903
+
1767
1904
  async getRobotVersion(duid) {
1768
1905
  const homedata = await this.getStateAsync("HomeData");
1769
1906
  if (homedata && homedata.val) {
@@ -1915,6 +2052,45 @@ class Roborock {
1915
2052
  return !until || Date.now() >= until;
1916
2053
  }
1917
2054
 
2055
+ // Called by the message queue when a LOCAL request times out. After a few
2056
+ // consecutive local timeouts we treat the device's local connection as
2057
+ // unresponsive (connected but mute) and prefer the cloud path for a cooldown.
2058
+ recordLocalTimeout(duid) {
2059
+ if (!duid) {
2060
+ return;
2061
+ }
2062
+
2063
+ const streak = (this.localTimeoutStreak.get(duid) || 0) + 1;
2064
+
2065
+ if (streak >= this.localFailoverThreshold) {
2066
+ this.localCloudPreference.set(duid, Date.now() + this.localFailoverInterval);
2067
+ this.localTimeoutStreak.set(duid, 0);
2068
+ this.log.info(
2069
+ `Local connection for ${duid} is unresponsive after ${streak} consecutive timeouts; preferring cloud for ${Math.round(this.localFailoverInterval / 60000)} min.`
2070
+ );
2071
+ } else {
2072
+ this.localTimeoutStreak.set(duid, streak);
2073
+ }
2074
+ }
2075
+
2076
+ // Called by the message queue when a LOCAL request succeeds. Clears any local
2077
+ // failover state so a recovered local connection is used again.
2078
+ recordLocalSuccess(duid) {
2079
+ if (!duid) {
2080
+ return;
2081
+ }
2082
+
2083
+ this.localTimeoutStreak.delete(duid);
2084
+ this.localCloudPreference.delete(duid);
2085
+ }
2086
+
2087
+ // Whether requests for a device should prefer the cloud path because its local
2088
+ // connection is connected but unresponsive (true during the failover cooldown).
2089
+ shouldPreferCloudLocally(duid) {
2090
+ const until = this.localCloudPreference.get(duid);
2091
+ return !!until && Date.now() < until;
2092
+ }
2093
+
1918
2094
  // Poll a method that a device may not support. A device that never answers
1919
2095
  // (e.g. a newer-protocol model lacking the V1 method) backs off instead of
1920
2096
  // timing out every cycle. See recordMethodTimeout/backoff.