homebridge-roborock-matter 3.0.1 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.2
4
+
5
+ A full read of the codebase turned up defects that no test covered and that the logs had been reporting for weeks without anyone reading them properly. Two of these could end the Homebridge process; one meant a headline feature had never worked in any released version. Nothing here requires re-pairing.
6
+
7
+ - **A malformed UDP packet can no longer take Homebridge down.** The discovery socket is bound to `0.0.0.0:58866` and receives whatever the network broadcasts at it — the Roborock phone app doing its own discovery, a port scanner, a truncated retransmit. Neither the binary parser nor `JSON.parse` was guarded, and a synchronous throw inside a dgram handler is an uncaught exception. One stray datagram from any host on the LAN was enough to stop the bridge, and because such senders are usually periodic, to keep stopping it.
8
+ - **The MQTT startup watchdog no longer crashes the process it was written to rescue.** After 30 seconds without a broker connection it called `restart()` on the adapter — a method this class has never had. The resulting TypeError inside an async timer is an unhandled rejection, which Node terminates on. The one situation it was meant to handle, a Pi that boots before the network is up, was therefore the one situation guaranteed to kill the bridge. It now logs a clear, throttled warning and lets the client keep reconnecting, and the timer is tracked and cancelled on shutdown.
9
+ - **LAN discovery works for the first time.** `decryptECB` called the PKCS#7 helper without `this.`, so every broadcast raised a ReferenceError that the surrounding `catch` swallowed and turned into `null`. Robots were only ever reached locally when the cloud happened to report their IP; otherwise they stayed cloud-only, with slower commands and no resilience to a Roborock outage. Startup also spent its full five-second discovery window on a result that could not contain anything.
10
+ - **Q7-series clean mode and suction now reach the robot.** Both commands were dispatched through an allow-list that contained neither of them, so they fell through to `Command set_clean_type not found.` and nothing was sent. The log line has been there since 13 July. They now take the same path the classic models already used, which also reports genuinely unsupported commands instead of discarding them silently.
11
+ - **Cloud commands no longer time out after doing exactly what was asked.** The protocol-102 handler tried to detect a secure request by comparing the result to the string `"ok"`, but the wire format is the array `["ok"]`, and `["ok"] != "ok"` is false. Every ordinary cloud command that acknowledges this way — start, stop, pause, dock, suction, room clean — was left pending until the ten-second timeout and then reported as failed in Apple Home, while the robot had already carried it out. Secure requests are now tracked by an explicit flag, and a secure request that fails resolves instead of hanging.
12
+ - **A cloud hiccup can no longer unregister every robot.** When the home-data fetch fails, startup logs the error and continues, so the device list reads as empty — and stale-accessory cleanup then removed every Matter accessory. Matter locks the mode list at commissioning, so that meant re-pairing the entire fleet, and a restart did not undo it. Cleanup is now skipped unless the API is initialised and actually returned robots, and a `result`-less cloud response no longer overwrites the cached device list on disk.
13
+ - **Mop modes are no longer offered on robots that cannot mop.** Two capability entries were written as bare arrays where every neighbouring entry ends in `.includes(robotModel)` — and an array, empty or not, is truthy. Both were therefore true for every robot, which is why dry-only models (S4, S4 Max, S5, S6 Pure) showed Mop and Vacuum + Mop plus a water-level control, and had `set_water_box_custom_mode` fired at hardware with no tank.
14
+ - **Fixed a corrupt header on robots reporting protocol version `\x81S\x19`.** The version is three raw bytes, but it was written as UTF-8, which encodes `0x81` as two bytes; the sequence number then overwrote the overflow. Both the version and the sequence number went out wrong, so those robots ignored every message. The decode side already used latin1.
15
+ - A local reconnect failure can no longer surface as an unhandled rejection, and the byte-identical copy of the MQTT connector at the repository root — dead code that was being hand-synced with the real one, and had already drifted — is gone.
16
+ - Full suite: 315 tests, 36 new. Each new test was checked to fail against the previous code and pass against this one.
17
+
3
18
  ## 3.0.1
4
19
 
5
20
  Follow-up to 3.0.0 after measuring a real restart instead of trusting the reasoning. Two things needed fixing, one of them mine.
package/dist/platform.js CHANGED
@@ -402,8 +402,29 @@ class RoborockPlatform {
402
402
  if (!matter || typeof matter.unregisterPlatformAccessories !== "function") {
403
403
  return;
404
404
  }
405
- const currentMatterUuids = new Set(this.roborockAPI
406
- .getVacuumList()
405
+ // A failed startup surfaces here as "the account has no robots": when
406
+ // getHomeDetail() throws (Roborock maintenance, a rate-limited response,
407
+ // or plain DNS failure — EAI_AGAIN hit this log twice in three weeks) the
408
+ // error is caught and logged, but the discovery callback still runs, so
409
+ // getVacuumList() returns an empty array. Treating that as "everything is
410
+ // stale" unregisters every Matter accessory, and because Matter locks the
411
+ // mode list at commissioning, the user then has to re-pair every single
412
+ // robot — a destructive, non-recoverable outcome triggered by one bad
413
+ // cloud response. Leaving a genuinely removed robot in place until the
414
+ // next successful discovery is by far the cheaper mistake.
415
+ if (!this.roborockAPI.isInited()) {
416
+ this.log.debug("Skipping stale-accessory cleanup: the Roborock API is not initialised yet, so the device list cannot be trusted.");
417
+ return;
418
+ }
419
+ const knownDevices = this.roborockAPI.getVacuumList();
420
+ if (!Array.isArray(knownDevices) || knownDevices.length === 0) {
421
+ this.log.warn("Skipping stale-accessory cleanup: the Roborock account reported no robots. " +
422
+ "This is almost always a temporary cloud or network failure, and unregistering " +
423
+ "the Matter accessories here would force you to re-pair every robot. If you have " +
424
+ "genuinely removed all robots from your account, remove the accessories manually.");
425
+ return;
426
+ }
427
+ const currentMatterUuids = new Set(knownDevices
407
428
  .filter((device) => this.isSupportedDevice(this.roborockAPI.getProductAttribute(device.duid, "model")))
408
429
  .map((device) => this.generateMatterUuid(device.duid)));
409
430
  const staleAccessories = this.matterAccessories.filter((accessory) => !currentMatterUuids.has(accessory.UUID));
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;;;AAWA,wFAAsE;AAEtE,sDAA8C;AAE9C,yCAAwD;AACxD,qCAA0C;AAE1C,MAAM,YAAY,GAAG,SAAS,CAAC;AAC/B,IAAI,sBAAsB,GAAG,KAAK,CAAC;AACnC,MAAM,wCAAwC,GAAG,CAAC,CAAC;AAEnD,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;IAiBnC;;;;;;;OAOG;IACH,YACE,gBAAwB,EACxB,MAAsB,EACL,GAAQ;QAAR,QAAG,GAAH,GAAG,CAAK;QA3BX,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,GAAwB,EAAE,CAAC;QACtC,sBAAiB,GAAU,EAAE,CAAC;QAC9B,kBAAa,GAC5B,IAAI,GAAG,EAAE,CAAC;QACJ,4BAAuB,GAAG,KAAK,CAAC;QAoBtC,IAAI,CAAC,cAAc,GAAG,MAAgC,CAAC;QAEvD,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;QAChD,MAAM,6BAA6B,GACjC,IAAI,CAAC,sCAAsC,CACzC,IAAI,CAAC,cAAc,CAAC,6BAA6B,CAClD,CAAC;QAEJ,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,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,WAAW;YAC5C,uBAAuB,EACrB,IAAI,CAAC,cAAc,CAAC,uBAAuB,KAAK,KAAK;YACvD,sBAAsB,EACpB,IAAI,CAAC,cAAc,CAAC,sBAAsB,KAAK,KAAK;YACtD,aAAa,EAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC;YACzD,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,WAAW;YACxB,kBAAkB,EAAE,6BAA6B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;SACnE,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,IAAI,CAAC,cAAc,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;YAC/C,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,4GAA4G,CAC7G,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,qCAAoB,GAAG,EAAE;YAClC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YAEnC,wEAAwE;YACxE,4CAA4C;YAC5C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;gBACjD,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,CAAC;YAED,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,MAAM,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACvC,CAAC;IAEO,sCAAsC,CAAC,KAAc;QAC3D,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,QAAQ;YACvB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;gBAChD,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBACf,CAAC,CAAC,wCAAwC,CAAC;QAEjD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,wCAAwC,CAAC;QAClD,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,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,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,kEAAkE;QAClE,kEAAkE;QAClE,iCAAiC;QACjC,OAAO,CAAC,OAAO,CACb,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,CACH,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACzB,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,qCAAqC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC9F,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,oBAAoB,CAAC,EAAU,EAAE,QAAiB;;QACxD,sEAAsE;QACtE,sEAAsE;QACtE,IAAI,MAAA,IAAI,CAAC,cAAc,0CAAE,SAAS,EAAE,CAAC;YACnC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,wBAAwB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,IACE,OAAO,IAAI,CAAC,WAAW,CAAC,+BAA+B,KAAK,UAAU,EACtE,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,+BAA+B,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,wBAAwB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC/D,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,kBAAkB,CAAC,UAAU,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,IACE,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC5B,CAAC,IAAI,CAAC,+BAA+B,EAAE,EACvC,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,qBAAqB,EAAE,2DAA2D,CACnF,CAAC;YACF,OAAO;QACT,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;YACjD,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAEO,kBAAkB,CACxB,IAAY,EACZ,EAAU,EACV,QAAiB;QAEjB,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEO,YAAY,CAClB,MAAqC,EACrC,EAAU,EACV,QAAiB;QAEjB,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACvD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,sCAAsC,GAAG,KAAK,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,wBAAwB,CAAC,EAAU,EAAE,IAAa;QACxD,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,IAA+B,CAAC;QAChD,IACE,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;YACrD,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC;YACxD,OAAO,CAAC,IAAI,EACZ,CAAC;YACD,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,mBAAmB,CAAC,EAAU;QACpC,OAAO,EAAE,KAAK,cAAc,IAAI,EAAE,KAAK,cAAc,CAAC;IACxD,CAAC;IAEM,+BAA+B;QACpC,OAAO,IAAI,CAAC,4BAA4B,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAEO,4BAA4B;QAClC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,MAAM,OAAO,GACX,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,KAAK,UAAU;YAClD,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE;YAClC,CAAC,CAAC,EAAE,CAAC;QAET,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,EAAE,CAAC;oBACjB,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC;IACpB,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAAC,SAA4B;QAC7C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,SAAS,CAAC,WAAW,eAAe,CAAC,CAAC;QAE1E,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,iCAAiC,iBAAiB,CAAC,WAAW,eAAe,CAC9E,CAAC;gBACF,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,sBAAW,EAAE,wBAAa,EAAE;oBACjE,iBAAiB;iBAClB,CAAC,CAAC;YACL,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;;;OAGG;IACH,wBAAwB,CAAC,SAAc;;QACrC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,6BAA6B,SAAS,CAAC,WAAW,eAAe,CAClE,CAAC;QACF,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEvC,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACnC,IAAI,CAAC,CAAA,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,WAAW,0CAAE,oBAAoB,CAAA,EAAE,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC;QAC/D,IAAI,CAAC,4BAA4B,CAAC,SAAS,EAAE;YAC3C,IAAI;YACJ,IAAI,EAAE,SAAS,CAAC,WAAW;SAC5B,CAAC,CAAC;QACH,IAAI,CAAC,0BAA0B,CAC7B,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,WAAW,EAAE,EACrC,SAAS,EACT,IAAI,CACL,CAAC;IACJ,CAAC;IAED,iBAAiB,CAAC,KAAa;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACxD,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,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC;gBAEjD,oEAAoE;gBACpE,8DAA8D;gBAC9D,cAAc;gBACd,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC7B,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;gBAC1C,CAAC;YACH,CAAC;YAED,oFAAoF;YACpF,wEAAwE;YACxE,mEAAmE;YACnE,qEAAqE;YACrE,qCAAqC;YACrC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,iCAAiC,IAAI,CAAC,WAAW,CAAC,MAAM,2BAA2B,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,0CAA0C,CACzK,CAAC;gBACF,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,sBAAW,EAAE,wBAAa,EAAE;oBACjE,GAAG,IAAI,CAAC,WAAW;iBACpB,CAAC,CAAC;gBACH,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9B,CAAC;YAED,MAAM,IAAI,CAAC,gCAAgC,EAAE,CAAC;QAChD,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,YAAY;QACV,yEAAyE;QACzE,6DAA6D;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAU,CAAC;QAC5B,MAAM,aAAa,GACjB,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACvC,CAAC,CAAC,GAAG,CAAC,eAAe,EAAE;YACvB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAE1B,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;gBAClC,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAC;gBACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,mKAAmK,CACpK,CAAC;YACJ,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,GAAG,CAAC,MAAM,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,MAAW;;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QAED,IAAI,CAAC,CAAA,MAAA,MAAM,CAAC,WAAW,0CAAE,oBAAoB,CAAA,EAAE,CAAC;YAC9C,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,oGAAoG,CACrG,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CACnD,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CACvC,CAAC;QACF,MAAM,SAAS,GACb,iBAAiB;YACjB,IAAI,CAAC,qBAAqB,CACxB,MAAM,EACN,MAAM,CAAC,WAAW,CAAC,oBAAoB,CACxC,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,0BAA0B,CAC5C,MAAM,EACN,SAAS,EACT,OAAO,CAAC,iBAAiB,CAAC,CAC3B,CAAC;QAEF,IAAI,iBAAiB,EAAE,CAAC;YACtB,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;YACpD,MAAM,CAAC,0BAA0B,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC;YACnE,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,mCAAmC,SAAS,CAAC,WAAW,MAAM,IAAI,IAAI,CACvE,CAAC;QACF,MAAM,MAAM,CAAC,2BAA2B,CAAC,sBAAW,EAAE,wBAAa,EAAE;YACnE,SAAS;SACV,CAAC,CAAC;QACH,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,CAAC,0BAA0B,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IACpE,CAAC;IAEO,qBAAqB,CAAC,MAAW,EAAE,UAAmB;QAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG;YAChB,IAAI,EAAE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;YACnC,UAAU;YACV,OAAO,EAAE,EAAE,IAAI,EAAE;SAClB,CAAC;QAEF,IAAI,CAAC,4BAA4B,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACrD,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,4BAA4B,CAAC,SAAc,EAAE,MAAW;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,WAAW,GACf,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC;YAClD,MAAM,CAAC,IAAI;YACX,iBAAiB,CAAC;QACpB,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAE1E,SAAS,CAAC,WAAW,GAAG,WAAW,CAAC;QACpC,4EAA4E;QAC5E,4EAA4E;QAC5E,SAAS,CAAC,IAAI,GAAG,WAAW,CAAC;QAC7B,SAAS,CAAC,YAAY;YACpB,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;QAC3D,SAAS,CAAC,YAAY,GAAG,UAAU,CAAC;QACpC,SAAS,CAAC,KAAK;YACb,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,iBAAiB,CAAC;QAC3E,SAAS,CAAC,OAAO,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;QAE3D,IAAI,gBAAgB,EAAE,CAAC;YACrB,SAAS,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,OAAO,SAAS,CAAC,gBAAgB,CAAC;QACpC,CAAC;IACH,CAAC;IAEO,0BAA0B,CAChC,MAAW,EACX,SAAc,EACd,YAAqB;QAErB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE9C,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAChC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,iCAA6B,CAC9C,IAAI,EACJ,SAAS,EACT,MAAM,EACN,YAAY,CACb,CAAC;QACF,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,KAAK,CAAC,gCAAgC;;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAU,CAAC;QAC5B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,6BAA6B,KAAK,UAAU,EAAE,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,IAAI,CAAC,WAAW;aACb,aAAa,EAAE;aACf,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CACjB,IAAI,CAAC,iBAAiB,CACpB,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAC3D,CACF;aACA,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CACzD,CAAC;QAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CACpD,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CACvD,CAAC;QAEF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;YACzC,IAAI,CAAC,GAAG,CAAC,IAAI,CACX,yCAAyC,SAAS,CAAC,WAAW,MAAM,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,IAAI,cAAc,sDAAsD,CACnL,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,CAAC,6BAA6B,CACxC,sBAAW,EACX,wBAAa,EACb,gBAAgB,CACjB,CAAC;QAEF,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;YACpD,IAAI,IAAI,EAAE,CAAC;gBACT,MAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,0CAAE,OAAO,EAAE,CAAC;gBACxC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAC5C,CAAC,eAAe,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CAC7D,CAAC;YACF,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACf,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,IAAY;;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAU,CAAC;QAC5B,MAAM,aAAa,GAAG,CAAA,MAAA,GAAG,CAAC,MAAM,0CAAE,IAAI,KAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5D,OAAO,aAAa,CAAC,QAAQ,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IAEO,sBAAsB,CAAC,SAAc;QAC3C,IAAI,CAAC,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,OAAO,CAAA,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC1C,OAAO,SAAS,CAAC,OAAO,CAAC;QAC3B,CAAC;QAED,IAAI,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC/C,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QAChC,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAvkBD,mCAukBC"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-matter",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "The most complete Roborock plugin for Apple Home. Supports the entire Roborock lineup — from the classic S-series to the new 2025 Q7 series that no other plugin can control. Sign in with your Roborock account and get native start/stop, room cleaning, suction levels, battery, and live 'cleaning in the kitchen' room tracking. Verified by Homebridge.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -811,10 +811,22 @@ class deviceFeatures {
811
811
  ].includes(robotModel),
812
812
  // this isn't the correct way to use this. This code must be from a different robot
813
813
  // isVoiceControlSupported: !!(parseInt(`0x${this.featuresStr || "0"}`.slice(-10, -9)) & 2),
814
+ // These two were written as bare arrays while every other model list in
815
+ // this object ends in `.includes(robotModel)`. An array is truthy in JS
816
+ // — an EMPTY array included — and the gate below is
817
+ // `if (featureList[feature])`, so both were unconditionally true for
818
+ // every robot. isElectronicWaterBoxSupported feeds isSupportedWaterMode,
819
+ // which is what makes canMop and canControlWater true, so Apple Home
820
+ // offered Mop and Vacuum + Mop plus a water-level control on dry-only
821
+ // robots (S4, S4 Max, S5, S6 Pure) and fired set_water_box_custom_mode
822
+ // at hardware with no tank. The three model allowlists above exist
823
+ // precisely to prevent that.
814
824
  isVoiceControlSupported: [
815
825
  "roborock.vacuum.a27", // S7 MaxV (Ultra)
816
- ],
817
- isElectronicWaterBoxSupported: [], // nothing for now. If this is needed, add the models here
826
+ ].includes(robotModel),
827
+ // No model is known to need this yet. Add models to the array and keep
828
+ // the .includes(robotModel) call if that changes.
829
+ isElectronicWaterBoxSupported: [].includes(robotModel),
818
830
  isCleanRouteFastModeSupported:
819
831
  this.featuresStr &&
820
832
  !!(256 & parseInt("0x" + this.featuresStr.slice(-8))),
@@ -103,9 +103,22 @@ class localConnector {
103
103
  scheduleReconnect(duid, ip, delayMs = LOCAL_RECONNECT_DELAY_MS) {
104
104
  this.clearReconnectTimer(duid);
105
105
  const { setTimer } = getTimerFns(this.adapter);
106
- const timer = setTimer(async () => {
106
+ // createClient can reject before its own try/catch takes over (the
107
+ // awaited diagnostics write happens first, and the failure handler awaits
108
+ // more of them). A bare timer callback that rejects is an unhandled
109
+ // rejection, which Node terminates the process for by default — so a full
110
+ // SD card at the moment a reconnect fires would take Homebridge down.
111
+ // Reconnect failures are expected and already logged inside createClient;
112
+ // swallowing them here only prevents the crash.
113
+ const timer = setTimer(() => {
107
114
  this.reconnectTimers.delete(duid);
108
- await this.createClient(duid, ip);
115
+ Promise.resolve()
116
+ .then(() => this.createClient(duid, ip))
117
+ .catch((error) => {
118
+ this.adapter.log.debug(
119
+ `Local reconnect attempt for ${duid} failed: ${error?.message || error}`
120
+ );
121
+ });
109
122
  }, delayMs);
110
123
  this.reconnectTimers.set(duid, timer);
111
124
  }
@@ -508,19 +521,37 @@ class localConnector {
508
521
  return new Promise((resolve, reject) => {
509
522
  const devices = {};
510
523
 
524
+ // The discovery socket is bound to 0.0.0.0 and receives whatever any
525
+ // host on the LAN broadcasts to this port — the Roborock phone app doing
526
+ // its own discovery, a port scanner, a malformed retransmit. Neither
527
+ // `localMessageParser.parse` (binary-parser throws RangeError on a short
528
+ // or over-declared buffer) nor `JSON.parse` is total, and a synchronous
529
+ // throw inside a dgram handler is an uncaught exception that takes
530
+ // Homebridge down with it. One stray datagram must never do that, so
531
+ // every unparseable packet is skipped instead.
511
532
  server.on("message", (msg) => {
512
- const parsedMessage = localMessageParser.parse(msg);
513
- const decodedMessage = this.decryptECB(
514
- parsedMessage.payload,
515
- BROADCAST_TOKEN
516
- ); // this might be decryptCBC for A01. Haven't checked this yet
517
-
518
- if (decodedMessage == null) {
519
- this.adapter.log.debug(`getLocalDevices: decodedMessage is null`);
533
+ let parsedDecodedMessage;
534
+
535
+ try {
536
+ const parsedMessage = localMessageParser.parse(msg);
537
+ const decodedMessage = this.decryptECB(
538
+ parsedMessage.payload,
539
+ BROADCAST_TOKEN
540
+ ); // this might be decryptCBC for A01. Haven't checked this yet
541
+
542
+ if (decodedMessage == null) {
543
+ this.adapter.log.debug(`getLocalDevices: decodedMessage is null`);
544
+ return;
545
+ }
546
+
547
+ parsedDecodedMessage = JSON.parse(decodedMessage);
548
+ } catch (error) {
549
+ this.adapter.log.debug(
550
+ `getLocalDevices: ignoring a malformed discovery datagram (${msg?.length ?? 0} bytes): ${error?.message || error}`
551
+ );
520
552
  return;
521
553
  }
522
554
 
523
- const parsedDecodedMessage = JSON.parse(decodedMessage);
524
555
  this.adapter.log.debug(
525
556
  `getLocalDevices parsedDecodedMessage: ${JSON.stringify(parsedDecodedMessage)}`
526
557
  );
@@ -604,7 +635,7 @@ class localConnector {
604
635
  decipher.update(input),
605
636
  decipher.final(),
606
637
  ]);
607
- const unpadded = safeRemovePkcs7(decryptedBuf);
638
+ const unpadded = this.safeRemovePkcs7(decryptedBuf);
608
639
 
609
640
  // 若原協定內容是 UTF-8,這裡再轉字串;否則直接回傳 Buffer 讓上層處理
610
641
  return unpadded.toString("utf8");
@@ -119,7 +119,13 @@ class message {
119
119
 
120
120
  if (protocol == 1) {
121
121
  const msg = Buffer.alloc(23);
122
- msg.write(version);
122
+ // latin1, not the UTF-8 default: the protocol version is three raw
123
+ // bytes, and one of the versions this file handles explicitly is
124
+ // "\x81S\x19". UTF-8 encodes 0x81 as two bytes (c2 81), so write()
125
+ // returned 4 and produced `c2 81 53 19`; the writeUint32BE below then
126
+ // overwrote the fourth byte, leaving a corrupt version AND a wrong
127
+ // sequence number. _decodeMsg already reads it back with latin1.
128
+ msg.write(version, "latin1");
123
129
  msg.writeUint32BE(currentSeq, 3);
124
130
  msg.writeUint32BE(currentRandom, 7);
125
131
  msg.writeUint32BE(timestamp, 11);
@@ -185,7 +191,13 @@ class message {
185
191
 
186
192
  if (encrypted) {
187
193
  const msg = Buffer.alloc(23 + encrypted.length);
188
- msg.write(version);
194
+ // latin1, not the UTF-8 default: the protocol version is three raw
195
+ // bytes, and one of the versions this file handles explicitly is
196
+ // "\x81S\x19". UTF-8 encodes 0x81 as two bytes (c2 81), so write()
197
+ // returned 4 and produced `c2 81 53 19`; the writeUint32BE below then
198
+ // overwrote the fourth byte, leaving a corrupt version AND a wrong
199
+ // sequence number. _decodeMsg already reads it back with latin1.
200
+ msg.write(version, "latin1");
189
201
  msg.writeUint32BE(currentSeq, 3);
190
202
  msg.writeUint32BE(currentRandom, 7);
191
203
  msg.writeUint32BE(timestamp, 11);
@@ -31,6 +31,9 @@ function getRequestTimeout(method, requestTimeoutMs) {
31
31
  * @property {(value: unknown) => void} resolve
32
32
  * @property {(reason?: unknown) => void} reject
33
33
  * @property {ReturnType<typeof setTimeout>} timeout
34
+ * @property {boolean} [secure] True for requests whose protocol-102 reply is
35
+ * only an acknowledgement, with the real payload arriving on protocol 301.
36
+ * @property {string} [method] The Roborock method, kept for diagnostics.
34
37
  */
35
38
 
36
39
  /**
@@ -322,11 +325,18 @@ class messageQueueHandler {
322
325
  }
323
326
  }, requestTimeout);
324
327
 
325
- // Store request with resolve and reject functions
328
+ // Store request with resolve and reject functions.
329
+ // `secure` travels with the entry so the MQTT receiver can tell a
330
+ // secure request (whose protocol-102 reply is only an ack — the real
331
+ // payload arrives on protocol 301) from an ordinary one (whose 102
332
+ // reply IS the result). It used to guess by comparing the result to
333
+ // the string "ok", which silently never matched.
326
334
  this.adapter.pendingRequests.set(messageID, {
327
335
  resolve,
328
336
  reject,
329
337
  timeout,
338
+ secure,
339
+ method,
330
340
  });
331
341
 
332
342
  if (useCloudConnection) {
@@ -39,6 +39,39 @@ let rriot;
39
39
  let photoGzipChunks = [];
40
40
  let photoChunkID = 0;
41
41
 
42
+ /**
43
+ * True when a protocol-102 result is a bare "command accepted" acknowledgement
44
+ * rather than real data. Roborock sends this as the single-element array
45
+ * ["ok"]; some firmware answers with the bare string.
46
+ * @param {unknown} result
47
+ * @returns {boolean}
48
+ */
49
+ function isOkAcknowledgement(result) {
50
+ if (result === "ok") {
51
+ return true;
52
+ }
53
+ return Array.isArray(result) && result.length === 1 && result[0] === "ok";
54
+ }
55
+
56
+ /**
57
+ * Decide whether a protocol-102 reply completes a pending request.
58
+ *
59
+ * Only a secure request answered with a bare acknowledgement keeps waiting —
60
+ * its real payload arrives on protocol 301. Everything else resolves here,
61
+ * including a secure request that came back with an error, which would
62
+ * otherwise hang until the request timeout.
63
+ *
64
+ * @param {{secure?: boolean}|undefined} pending
65
+ * @param {unknown} result
66
+ * @returns {boolean}
67
+ */
68
+ function shouldResolveOn102(pending, result) {
69
+ if (!pending) {
70
+ return false;
71
+ }
72
+ return !(pending.secure === true && isOkAcknowledgement(result));
73
+ }
74
+
42
75
  function payloadStartsWith(payload, value) {
43
76
  return (
44
77
  Buffer.isBuffer(payload) &&
@@ -76,6 +109,7 @@ class roborock_mqtt_connector {
76
109
  this.adapter = adapter;
77
110
 
78
111
  this.connected = false;
112
+ this.initialConnectTimeout = null;
79
113
 
80
114
  // NOTE: this class previously generated its own RSA-2048 keypair here,
81
115
  // but nothing ever read it — the protocol keypair lives in message.js
@@ -101,9 +135,27 @@ class roborock_mqtt_connector {
101
135
  }
102
136
 
103
137
  async initMQTT_Subscribe() {
104
- const timeout = setTimeout(async () => {
105
- this.adapter.restart();
138
+ // This used to invoke a `restart` method on the adapter, which the
139
+ // Roborock class has never had — so on the one path it was written for
140
+ // (the broker
141
+ // neither connecting nor emitting `reconnect` within 30 s, e.g. a Pi that
142
+ // boots before the network is up, or a SYN black-holed by a firewall) it
143
+ // threw a TypeError inside an async timer. That is an unhandled rejection,
144
+ // which terminates the Homebridge process under Node's default policy —
145
+ // the exact opposite of the intended recovery. mqtt.js already retries on
146
+ // its own reconnectPeriod, so the correct behaviour here is to say so
147
+ // clearly and let it keep trying. The handle is stored and unref'd so
148
+ // shutdown can cancel it instead of firing into a torn-down adapter.
149
+ this.clearInitialConnectTimeout();
150
+ this.initialConnectTimeout = setTimeout(() => {
151
+ this.initialConnectTimeout = null;
152
+ this.logConnectionIssue(
153
+ `The Roborock MQTT broker has not answered within 30 seconds of startup. The client keeps reconnecting in the background; robots stay on cloud fallback until the session is up.`
154
+ );
106
155
  }, 30000);
156
+ if (typeof this.initialConnectTimeout?.unref === "function") {
157
+ this.initialConnectTimeout.unref();
158
+ }
107
159
 
108
160
  await client.on("connect", (result) => {
109
161
  if (typeof result != "undefined") {
@@ -114,7 +166,7 @@ class roborock_mqtt_connector {
114
166
  );
115
167
  }
116
168
  });
117
- clearTimeout(timeout);
169
+ this.clearInitialConnectTimeout();
118
170
 
119
171
  this.connected = true;
120
172
  if (this._connectionIssueActive) {
@@ -157,7 +209,7 @@ class roborock_mqtt_connector {
157
209
  );
158
210
  }
159
211
  });
160
- clearTimeout(timeout);
212
+ this.clearInitialConnectTimeout();
161
213
  this.adapter.log.debug(`MQTT connection reconnect attempt.`);
162
214
  });
163
215
 
@@ -169,6 +221,17 @@ class roborock_mqtt_connector {
169
221
  });
170
222
  }
171
223
 
224
+ /**
225
+ * Cancel the startup watchdog. Safe to call when it is not armed, and
226
+ * called from shutdown so the timer cannot outlive the adapter.
227
+ */
228
+ clearInitialConnectTimeout() {
229
+ if (this.initialConnectTimeout) {
230
+ clearTimeout(this.initialConnectTimeout);
231
+ this.initialConnectTimeout = null;
232
+ }
233
+ }
234
+
172
235
  /**
173
236
  * Warn about a connection problem at most once per 5 minutes per message,
174
237
  * and remember that an outage is in progress so the next successful
@@ -306,16 +369,23 @@ class roborock_mqtt_connector {
306
369
  }
307
370
  }
308
371
 
309
- // special check for secure request like get_map_v1 etc. Don't process if result is OK. Instead wait for the actual response for protocol 301
310
- if (dps.result != "ok") {
311
- if (this.adapter.pendingRequests.has(dps.id)) {
312
- const { resolve, timeout } = this.adapter.pendingRequests.get(
313
- dps.id
314
- );
315
- this.adapter.clearTimeout(timeout);
316
- this.adapter.pendingRequests.delete(dps.id);
317
- resolve(dps.result);
318
- }
372
+ // Secure requests (get_map_v1 and friends) answer protocol 102 with
373
+ // a bare acknowledgement and deliver the real payload on protocol
374
+ // 301, so those must stay pending. Everything else is resolved here.
375
+ //
376
+ // This used to be `if (dps.result != "ok")`, which is always true:
377
+ // the wire format is the ARRAY ["ok"], and `["ok"] != "ok"` is false
378
+ // only after ToPrimitive — so the guard never fired for the case it
379
+ // was written for, and instead swallowed the completion of every
380
+ // ordinary cloud command that answers ["ok"] (app_start, app_stop,
381
+ // app_pause, app_charge, set_custom_mode, app_segment_clean, ...).
382
+ // Those requests then sat until the 10 s timeout and failed in Apple
383
+ // Home even though the robot had already carried them out.
384
+ const pending = this.adapter.pendingRequests.get(dps.id);
385
+ if (shouldResolveOn102(pending, dps.result)) {
386
+ this.adapter.clearTimeout(pending.timeout);
387
+ this.adapter.pendingRequests.delete(dps.id);
388
+ pending.resolve(dps.result);
319
389
  }
320
390
  // protocol 300 seems to be for get_photo 0 only. get_photo 0 is for large images. 1 is for small images.
321
391
  } else if (data.protocol == 300) {
@@ -606,4 +676,6 @@ module.exports = {
606
676
  parseProtocol301Header,
607
677
  parsePhotoPayload,
608
678
  payloadStartsWith,
679
+ isOkAcknowledgement,
680
+ shouldResolveOn102,
609
681
  };
@@ -1410,6 +1410,18 @@ class Roborock {
1410
1410
  const homedata = await this.api.get(`v2/user/homes/${homeId}`);
1411
1411
  const homedataResult = homedata.data.result;
1412
1412
 
1413
+ // Guard the persisted copy the same way updateHomeData already does.
1414
+ // Roborock occasionally answers 200 with an envelope that carries no
1415
+ // `result` (maintenance windows, rate limiting). Writing that through
1416
+ // stores `{"ack":true}` over the cached device list on disk, so the
1417
+ // next restart starts from a destroyed HomeData as well — turning a
1418
+ // transient cloud hiccup into a persistent failure.
1419
+ if (!homedataResult) {
1420
+ throw new Error(
1421
+ `The Roborock cloud returned no home data for home ${homeId}. Keeping the previously cached device list.`
1422
+ );
1423
+ }
1424
+
1413
1425
  await this.setStateAsync("HomeData", {
1414
1426
  val: JSON.stringify(homedataResult),
1415
1427
  ack: true,
@@ -2108,15 +2120,32 @@ class Roborock {
2108
2120
  if (
2109
2121
  this.getVacuumDeviceInfo(duid, "pv") === b01Q7Adapter.B01_PROTOCOL_VERSION
2110
2122
  ) {
2123
+ // These used to go through startCommand(), whose SIMPLE_VACUUM_COMMANDS
2124
+ // allow-list contains neither `set_clean_type` nor `set_custom_mode` —
2125
+ // so both fell through to `Command ... not found.` and nothing was ever
2126
+ // sent to the robot. Mathias' own Homebridge log has carried that line
2127
+ // since 2026-07-13. runMatterSettingCommand is the same path the classic
2128
+ // (non-B01) branch below already uses: it goes straight to
2129
+ // vacuum.command and reports unsupported results as errors.
2130
+ //
2131
+ // The value stays wrapped in an array on purpose. Matter clean mode 0 is
2132
+ // a valid selection (vacuum), and vacuum.command's default branch tests
2133
+ // `if (value && ...)` — a bare 0 is falsy and would silently be sent as
2134
+ // an empty parameter list, which b01Q7Adapter then drops entirely.
2111
2135
  if (Number.isInteger(settings?.cleanMode)) {
2112
2136
  try {
2113
- await this.startCommand(
2137
+ await this.runMatterSettingCommand(
2114
2138
  duid,
2115
2139
  "set_clean_type",
2116
2140
  [settings.cleanMode],
2117
2141
  commandOptions
2118
2142
  );
2119
2143
  } catch (error) {
2144
+ this.rememberUnsupportedMatterSettingCommand(
2145
+ duid,
2146
+ "set_clean_type",
2147
+ error
2148
+ );
2120
2149
  this.log.debug(
2121
2150
  `B01 clean-type command failed for ${duid}; continuing with the start command. ${error.message || error}`
2122
2151
  );
@@ -2126,13 +2155,18 @@ class Roborock {
2126
2155
  const fanPower = settings?.fanPower;
2127
2156
  if (Number.isInteger(fanPower) && fanPower !== 105) {
2128
2157
  try {
2129
- await this.startCommand(
2158
+ await this.runMatterSettingCommand(
2130
2159
  duid,
2131
2160
  "set_custom_mode",
2132
2161
  [fanPower],
2133
2162
  commandOptions
2134
2163
  );
2135
2164
  } catch (error) {
2165
+ this.rememberUnsupportedMatterSettingCommand(
2166
+ duid,
2167
+ "set_custom_mode",
2168
+ error
2169
+ );
2136
2170
  this.log.debug(
2137
2171
  `B01 suction command failed for ${duid}; continuing with the start command. ${error.message || error}`
2138
2172
  );
@@ -2770,6 +2804,10 @@ class Roborock {
2770
2804
 
2771
2805
  this.localConnector.clearLocalDevicedTimeout();
2772
2806
 
2807
+ // The MQTT startup watchdog is armed in initMQTT_Subscribe and would
2808
+ // otherwise survive shutdown and fire into a torn-down adapter.
2809
+ this.rr_mqtt_connector?.clearInitialConnectTimeout?.();
2810
+
2773
2811
  for (const duid in this.vacuums) {
2774
2812
  this.clearInterval(this.vacuums[duid].getStatusIntervalHandle);
2775
2813
  this.clearInterval(this.vacuums[duid].mainUpdateIntervalHandle);
@@ -1,609 +0,0 @@
1
- "use strict";
2
-
3
- const mqtt = require("mqtt");
4
- const crypto = require("crypto");
5
- const Parser = require("binary-parser").Parser;
6
- const zlib = require("zlib");
7
- const roborockCrypto = require("./roborockCrypto");
8
-
9
- const PHOTO_MAGIC = "ROBOROCK";
10
- const PHOTO_HEADER_MIN_LENGTH = 9;
11
- const PROTOCOL_301_HEADER_LENGTH = 24;
12
-
13
- const protocol301Parser = new Parser()
14
- .endianess("little")
15
- .string("endpoint", {
16
- length: 15,
17
- stripNull: true,
18
- })
19
- .uint8("unknown1")
20
- .uint16("id")
21
- .buffer("unknown2", {
22
- length: 6,
23
- });
24
-
25
- const photoParser = new Parser()
26
- .endianess("little")
27
- .string("roborock", {
28
- length: 8,
29
- stripNull: true,
30
- })
31
- .uint8("id");
32
-
33
- let mqttUser;
34
- let mqttPassword;
35
- let client;
36
- let endpoint;
37
- let rriot;
38
-
39
- let photoGzipChunks = [];
40
- let photoChunkID = 0;
41
-
42
- function payloadStartsWith(payload, value) {
43
- return (
44
- Buffer.isBuffer(payload) &&
45
- payload.length >= value.length &&
46
- payload.subarray(0, value.length).toString("utf8") === value
47
- );
48
- }
49
-
50
- function parsePhotoPayload(payload) {
51
- if (
52
- !payloadStartsWith(payload, PHOTO_MAGIC) ||
53
- payload.length < PHOTO_HEADER_MIN_LENGTH
54
- ) {
55
- return null;
56
- }
57
-
58
- return photoParser.parse(payload);
59
- }
60
-
61
- function parseProtocol301Header(payload) {
62
- if (
63
- !Buffer.isBuffer(payload) ||
64
- payload.length < PROTOCOL_301_HEADER_LENGTH
65
- ) {
66
- return null;
67
- }
68
-
69
- return protocol301Parser.parse(
70
- payload.subarray(0, PROTOCOL_301_HEADER_LENGTH)
71
- );
72
- }
73
-
74
- class roborock_mqtt_connector {
75
- constructor(adapter) {
76
- this.adapter = adapter;
77
-
78
- this.connected = false;
79
-
80
- // NOTE: this class previously generated its own RSA-2048 keypair here,
81
- // but nothing ever read it — the protocol keypair lives in message.js
82
- // (lazily created for the rare photo path). Removed: one full RSA
83
- // keygen less at every startup.
84
- }
85
-
86
- async initUser(userdata) {
87
- rriot = userdata.rriot;
88
-
89
- endpoint = roborockCrypto
90
- .md5bin(rriot.k)
91
- .subarray(8, 14)
92
- .toString("base64"); // Could be a random but rather static string. The app generates it on first run.
93
- mqttUser = roborockCrypto.md5hex(rriot.u + ":" + rriot.k).substring(2, 10);
94
- mqttPassword = roborockCrypto.md5hex(rriot.s + ":" + rriot.k).substring(16);
95
- client = mqtt.connect(rriot.r.m, {
96
- clientId: mqttUser,
97
- username: mqttUser,
98
- password: mqttPassword,
99
- keepalive: 30,
100
- });
101
- }
102
-
103
- async initMQTT_Subscribe() {
104
- const timeout = setTimeout(async () => {
105
- this.adapter.restart();
106
- }, 30000);
107
-
108
- await client.on("connect", (result) => {
109
- if (typeof result != "undefined") {
110
- client.subscribe(`rr/m/o/${rriot.u}/${mqttUser}/#`, (err, granted) => {
111
- if (err) {
112
- this.logConnectionIssue(
113
- `Failed to subscribe to the Roborock MQTT server: ${err} (granted: ${JSON.stringify(granted)}).`
114
- );
115
- }
116
- });
117
- clearTimeout(timeout);
118
-
119
- this.connected = true;
120
- if (this._connectionIssueActive) {
121
- this._connectionIssueActive = false;
122
- this._connectionIssueLog?.clear();
123
- this.adapter.log.info(
124
- `Roborock MQTT connection recovered after the reported outage.`
125
- );
126
- }
127
- }
128
- this.adapter.log.debug(
129
- `MQTT connection connected ${JSON.stringify(result)}.`
130
- );
131
- });
132
-
133
- // Connection-state events are account-level transport telemetry, not
134
- // per-robot command failures: log them as clear, throttled warnings
135
- // instead of routing them through catchError (which used to produce the
136
- // misleading `Failed to execute client.on("error") on robot undefined`
137
- // spam twice per reconnect attempt during network outages).
138
- await client.on("error", (error) => {
139
- this.connected = false;
140
- this.logConnectionIssue(
141
- `Roborock MQTT connection error: ${error?.message || error}. The client keeps reconnecting automatically.`
142
- );
143
- });
144
-
145
- await client.on("close", () => {
146
- if (this.connected) {
147
- this.adapter.log.info(`MQTT connection closed; reconnecting.`);
148
- }
149
- this.connected = false;
150
- });
151
-
152
- await client.on("reconnect", () => {
153
- client.subscribe(`rr/m/o/${rriot.u}/${mqttUser}/#`, (err, granted) => {
154
- if (err) {
155
- this.logConnectionIssue(
156
- `Failed to subscribe to the Roborock MQTT server after reconnect: ${err} (granted: ${JSON.stringify(granted)}).`
157
- );
158
- }
159
- });
160
- clearTimeout(timeout);
161
- this.adapter.log.debug(`MQTT connection reconnect attempt.`);
162
- });
163
-
164
- await client.on("offline", () => {
165
- this.connected = false;
166
- this.logConnectionIssue(
167
- `Roborock MQTT connection is offline. The client keeps reconnecting automatically.`
168
- );
169
- });
170
- }
171
-
172
- /**
173
- * Warn about a connection problem at most once per 5 minutes per message,
174
- * and remember that an outage is in progress so the next successful
175
- * connect logs a single recovery line instead of silence.
176
- * @param {string} message
177
- */
178
- logConnectionIssue(message) {
179
- if (!this._connectionIssueLog) {
180
- this._connectionIssueLog = new Map();
181
- }
182
- this._connectionIssueActive = true;
183
- const now = Date.now();
184
- const lastAt = this._connectionIssueLog.get(message) || 0;
185
- if (now - lastAt >= 5 * 60 * 1000) {
186
- this._connectionIssueLog.set(message, now);
187
- this.adapter.log.warn(message);
188
- } else {
189
- this.adapter.log.debug(message);
190
- }
191
- }
192
-
193
- getKnownDeviceDuids() {
194
- const knownDuids = new Set();
195
-
196
- if (this.adapter.localKeys instanceof Map) {
197
- for (const duid of this.adapter.localKeys.keys()) {
198
- knownDuids.add(duid);
199
- }
200
- }
201
-
202
- if (this.adapter.devices && Array.isArray(this.adapter.devices)) {
203
- for (const device of this.adapter.devices) {
204
- if (device && device.duid) {
205
- knownDuids.add(device.duid);
206
- }
207
- }
208
- }
209
-
210
- return knownDuids;
211
- }
212
-
213
- resolveDuidFromTopic(topic) {
214
- const topicSegments = topic
215
- .split("/")
216
- .filter((segment) => segment && segment.length > 0);
217
- if (topicSegments.length === 0) {
218
- return null;
219
- }
220
-
221
- const knownDuids = this.getKnownDeviceDuids();
222
- const topicTail = topicSegments[topicSegments.length - 1];
223
-
224
- if (knownDuids.has(topicTail)) {
225
- return topicTail;
226
- }
227
-
228
- for (let index = topicSegments.length - 2; index >= 0; index--) {
229
- if (knownDuids.has(topicSegments[index])) {
230
- return topicSegments[index];
231
- }
232
- }
233
-
234
- if (knownDuids.size === 0) {
235
- return topicTail;
236
- }
237
-
238
- return null;
239
- }
240
-
241
- async initMQTT_Message() {
242
- this.adapter.log.info(`MQTT initialized`);
243
-
244
- client.on("message", (topic, message) => {
245
- try {
246
- const duid = this.resolveDuidFromTopic(topic);
247
- if (!duid) {
248
- this.adapter.log.debug(
249
- `Skipping MQTT message with unmatched topic '${topic}'.`
250
- );
251
- return;
252
- }
253
-
254
- const data = this.adapter.message._decodeMsg(message, duid);
255
- if (!data) {
256
- return;
257
- }
258
- // this.adapter.log.debug(`MESSAGE RECEIVED for duid ${duid} with key: ${this.adapter.localKeys.get(duid)} data: ${JSON.stringify(data)} raw: ${JSON.stringify(mqttMessageParser.parse(message))} message: ${message}`);
259
- // this.adapter.log.debug(`MESSAGE RECEIVED for duid ${duid} with key: ${this.adapter.localKeys.get(duid)} data: ${JSON.stringify(data.toString("hex"))} message: ${message}`);
260
- // this.adapter.log.debug(`MESSAGE RECEIVED for duid ${duid} with key: ${this.adapter.localKeys.get(duid)} data: ${JSON.stringify(data)}`);
261
-
262
- // this.adapter.log.debug("Protocol: " + data.protocol);
263
- if (data.protocol == 102) {
264
- const parsedPayload = JSON.parse(data.payload);
265
- let dps;
266
- if (typeof parsedPayload.dps["102"] != "undefined") {
267
- dps = JSON.parse(parsedPayload.dps["102"]);
268
- } else if (typeof parsedPayload.dps["10001"] != "undefined") {
269
- if (typeof parsedPayload.dps["10001"] == "string") {
270
- dps = JSON.parse(parsedPayload.dps["10001"]);
271
- } else {
272
- dps = parsedPayload.dps["10001"];
273
- }
274
- } else {
275
- dps = parsedPayload.dps;
276
- }
277
-
278
- if (resolveB01PendingResponse(this.adapter, duid, dps)) {
279
- return;
280
- }
281
-
282
- if (dps.id !== undefined) {
283
- // Runs for every cloud message; only pay the stringify cost
284
- // when debug logging is actually enabled.
285
- if (this.adapter.config.debug) {
286
- this.adapter.log.debug(
287
- `Cloud message with protocol 102 and id ${dps.id} received. Result: ${JSON.stringify(dps.result)}`
288
- );
289
- }
290
- if (typeof dps.result !== "undefined") {
291
- this.adapter.setStateAsync("CloudMessage", {
292
- duid,
293
- payload: dps.result,
294
- });
295
- }
296
- } else {
297
- this.adapter.log.debug(
298
- `Cloud message with protocol 102 received. Result: ${data.payload}`
299
- );
300
-
301
- if (this.adapter.deviceNotify !== undefined) {
302
- this.adapter.deviceNotify("CloudMessage", {
303
- duid,
304
- payload: JSON.parse(data.payload),
305
- });
306
- }
307
- }
308
-
309
- // special check for secure request like get_map_v1 etc. Don't process if result is OK. Instead wait for the actual response for protocol 301
310
- if (dps.result != "ok") {
311
- if (this.adapter.pendingRequests.has(dps.id)) {
312
- const { resolve, timeout } = this.adapter.pendingRequests.get(
313
- dps.id
314
- );
315
- this.adapter.clearTimeout(timeout);
316
- this.adapter.pendingRequests.delete(dps.id);
317
- resolve(dps.result);
318
- }
319
- }
320
- // protocol 300 seems to be for get_photo 0 only. get_photo 0 is for large images. 1 is for small images.
321
- } else if (data.protocol == 300) {
322
- const photoData = parsePhotoPayload(data.payload);
323
- if (photoData) {
324
- if (this.adapter.pendingRequests.has(photoData.id)) {
325
- this.adapter.log.debug(`First photo gzip chunk detected!`);
326
-
327
- photoGzipChunks.push(data.payload.slice(56));
328
- photoChunkID = photoData.id;
329
- }
330
- } else {
331
- this.adapter.log.debug(
332
- `Skipping protocol 300 MQTT message for ${duid} because the payload is not a complete Roborock photo header.`
333
- );
334
- }
335
- } else if (data.protocol == 301) {
336
- // B01/Q7 map upload responses arrive on protocol 301 as an opaque
337
- // base64 blob. Resolve the per-device pending map request first;
338
- // classic v1 photo/map chunk handling continues below otherwise.
339
- const pendingMap = this.adapter.pendingB01MapRequests?.get(duid);
340
- if (pendingMap) {
341
- this.adapter.clearTimeout(pendingMap.timeout);
342
- this.adapter.pendingB01MapRequests.delete(duid);
343
- pendingMap.resolve(data.payload);
344
- return;
345
- }
346
-
347
- if (data.seq == 2 && photoGzipChunks != [] && photoChunkID != 0) {
348
- this.adapter.log.debug(`Second photo gzip chunk detected!`);
349
- photoGzipChunks.push(data.payload);
350
-
351
- if (this.adapter.pendingRequests.has(photoChunkID)) {
352
- const { resolve, timeout } =
353
- this.adapter.pendingRequests.get(photoChunkID);
354
- this.adapter.clearTimeout(timeout);
355
- this.adapter.pendingRequests.delete(photoChunkID);
356
-
357
- const finalPhotoGzip = Buffer.concat(photoGzipChunks);
358
-
359
- photoGzipChunks = [];
360
- photoChunkID = 0;
361
-
362
- resolve(finalPhotoGzip);
363
- }
364
- } else {
365
- const photoData = parsePhotoPayload(data.payload);
366
- if (photoData) {
367
- this.adapter.log.debug(
368
- `Cloud message with protocol 301 and photo id ${photoData.id} received.`
369
- );
370
-
371
- if (this.adapter.pendingRequests.has(photoData.id)) {
372
- const { resolve, timeout } = this.adapter.pendingRequests.get(
373
- photoData.id
374
- );
375
- this.adapter.clearTimeout(timeout);
376
- this.adapter.pendingRequests.delete(photoData.id);
377
- this.adapter.log.debug(
378
- `Cloud message with protocol 301 and photo id ${photoData.id} received.`
379
- );
380
- resolve(data.payload.slice(56));
381
- }
382
- } else {
383
- const data2 = parseProtocol301Header(data.payload);
384
- if (!data2) {
385
- this.adapter.log.debug(
386
- `Skipping protocol 301 MQTT message for ${duid} because the payload is shorter than ${PROTOCOL_301_HEADER_LENGTH} bytes.`
387
- );
388
- return;
389
- }
390
-
391
- if (!endpoint.startsWith(data2.endpoint)) {
392
- return;
393
- }
394
-
395
- const iv = Buffer.alloc(16, 0);
396
- const decipher = crypto.createDecipheriv(
397
- "aes-128-cbc",
398
- this.adapter.nonce,
399
- iv
400
- );
401
- let decrypted = Buffer.concat([
402
- decipher.update(data.payload.subarray(24)),
403
- decipher.final(),
404
- ]);
405
- decrypted = zlib.gunzipSync(decrypted);
406
- // this.adapter.log.debug("raw 301: " + decrypted);
407
-
408
- if (this.adapter.pendingRequests.has(data2.id)) {
409
- const { resolve, timeout } = this.adapter.pendingRequests.get(
410
- data2.id
411
- );
412
- this.adapter.clearTimeout(timeout);
413
- this.adapter.pendingRequests.delete(data2.id);
414
- // this.adapter.log.debug("protocol 301 OK check: " + JSON.stringify(decrypted));
415
- this.adapter.log.debug(
416
- `Cloud message with protocol 301 and id ${data2.id} received.`
417
- );
418
- resolve(decrypted);
419
- }
420
- }
421
- }
422
- } else if (data.protocol == 500) {
423
- // 500 is for general information
424
- const dataString = data.payload.toString("utf8");
425
- let parsedData;
426
-
427
- try {
428
- parsedData = JSON.parse(dataString);
429
- } catch (error) {
430
- // If parsing fails, the data might be corrupted or in an unexpected format
431
- this.adapter.log.warn(
432
- `Unable to parse message for ${duid}. Error: ${error.message}. Data: ${dataString}`
433
- );
434
- return;
435
- }
436
-
437
- // Check if the device is online
438
- if (parsedData.online == false) {
439
- this.adapter.log.info(
440
- `Couldn't process message. The device ${duid} is offline.`
441
- );
442
- } else if (parsedData.online == true) {
443
- // this.adapter.log.info(`Device ${duid} is online.`);
444
- } else if (
445
- // Check for firmware update information
446
- parsedData.mqttOtaData
447
- ) {
448
- const otaStatus = parsedData.mqttOtaData.mqttOtaStatus?.status;
449
- const otaProgress =
450
- parsedData.mqttOtaData.mqttOtaProgress?.progress;
451
-
452
- if (otaStatus) {
453
- this.adapter.log.info(
454
- `Device ${duid} firmware update status: ${otaStatus}`
455
- );
456
- }
457
-
458
- if (otaProgress !== undefined) {
459
- this.adapter.log.info(
460
- `Device ${duid} firmware update progress: ${otaProgress}%`
461
- );
462
- }
463
- } else {
464
- // Received an unrecognized message
465
- this.adapter.log.warn(
466
- `Received an unrecognized message for ${duid}. Data: ${dataString}`
467
- );
468
- }
469
- } else {
470
- this.adapter.log.debug(
471
- `Received message with unknown protocol ${data.protocol} data: ${JSON.stringify(data)}.`
472
- );
473
- }
474
- } catch (error) {
475
- this.adapter.log.error(
476
- `client.on message failed for topic '${topic}': ${error.stack || error}`
477
- );
478
- }
479
- });
480
- }
481
-
482
- getEndpoint() {
483
- return endpoint;
484
- }
485
-
486
- sendMessage(duid, roborockMessage) {
487
- client.publish(`rr/m/i/${rriot.u}/${mqttUser}/${duid}`, roborockMessage, {
488
- qos: 1,
489
- });
490
- }
491
-
492
- isConnected() {
493
- return this.connected;
494
- }
495
-
496
- /**
497
- * Resolve once the MQTT session is usable, or after `timeoutMs` regardless.
498
- *
499
- * The broker handshake takes a few seconds, and cloud requests issued
500
- * before it completes fail outright with "Cloud connection not available".
501
- * Startup used to get away with this because an unrelated fixed delay
502
- * happened to sit in front of the first requests; waiting on the real
503
- * signal makes that timing explicit rather than accidental. Resolving on
504
- * timeout (instead of rejecting) keeps a slow or offline broker from
505
- * blocking startup — callers fall back to their own error handling.
506
- *
507
- * @param {number} [timeoutMs=10000]
508
- * @returns {Promise<boolean>} whether the connection came up in time
509
- */
510
- async waitUntilConnected(timeoutMs = 10000) {
511
- if (this.connected) {
512
- return true;
513
- }
514
-
515
- const deadline = Date.now() + timeoutMs;
516
- while (!this.connected && Date.now() < deadline) {
517
- await new Promise((resolve) => {
518
- const timer = setTimeout(resolve, 100);
519
- if (typeof timer?.unref === "function") {
520
- timer.unref();
521
- }
522
- });
523
- }
524
-
525
- return this.connected;
526
- }
527
-
528
- async ensureConnected() {
529
- if (client && this.connected) {
530
- this.adapter.log.debug("MQTT health check passed. Reconnect skipped.");
531
- return false;
532
- }
533
-
534
- await this.reconnectClient(true);
535
- return true;
536
- }
537
-
538
- async reconnectClient(force = false) {
539
- if (client) {
540
- try {
541
- if (!force && this.connected) {
542
- this.adapter.log.debug(
543
- "MQTT reconnect skipped because client is already connected."
544
- );
545
- return false;
546
- }
547
-
548
- this.adapter.log.info("Reconnecting mqtt client!");
549
- await client.end();
550
- client.reconnect();
551
- return true;
552
- } catch (error) {
553
- this.adapter.catchError(
554
- `Failed to reconnect with error: ${error}`,
555
- `reconnectClient`
556
- );
557
- }
558
- }
559
-
560
- return false;
561
- }
562
- }
563
-
564
- /**
565
- * Correlate a Q7/B01 RPC response (dps 10001 payload) to its pending request
566
- * by msgId. Returns true when the dps object was a B01 message and has been
567
- * fully handled; false when the caller should continue v1 processing.
568
- * Robot-initiated B01 pushes (no matching request) trigger a status refresh
569
- * instead of guessing at undocumented event payload formats.
570
- */
571
- function resolveB01PendingResponse(adapter, duid, dps) {
572
- if (!dps || dps.msgId === undefined || dps.id !== undefined) {
573
- return false;
574
- }
575
-
576
- const b01Key = String(dps.msgId);
577
- const pendingB01 = adapter.pendingRequests.get(b01Key);
578
-
579
- if (pendingB01) {
580
- adapter.clearTimeout(pendingB01.timeout);
581
- adapter.pendingRequests.delete(b01Key);
582
- if (dps.code !== undefined && dps.code !== 0) {
583
- pendingB01.reject(
584
- new Error(
585
- `B01 command ${dps.method || "(unknown method)"} failed with code ${dps.code} for ${duid}.`
586
- )
587
- );
588
- } else {
589
- pendingB01.resolve(dps.data !== undefined ? dps.data : null);
590
- }
591
- } else {
592
- adapter.log.debug(
593
- `Unsolicited B01 message for ${duid} (${dps.method || "no method"}); scheduling a status refresh.`
594
- );
595
- if (typeof adapter.getStatus === "function") {
596
- void adapter.getStatus(duid, { force: true }).catch(() => undefined);
597
- }
598
- }
599
-
600
- return true;
601
- }
602
-
603
- module.exports = {
604
- resolveB01PendingResponse,
605
- roborock_mqtt_connector,
606
- parseProtocol301Header,
607
- parsePhotoPayload,
608
- payloadStartsWith,
609
- };