homebridge-roborock-vacuum 1.4.6 → 1.4.7

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/CLAUDE.md ADDED
@@ -0,0 +1,50 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ - Install: `npm ci`
8
+ - Build: `npm run build` (runs `rimraf ./dist && tsc`; only `src/` is compiled to `dist/`)
9
+ - Lint: `npm run lint` (Prettier check) / `npm run lint:fix`
10
+ - Test: `npm test` (Jest)
11
+ - Run a single test file: `npm test -- path/to/file.test.ts`
12
+ - Run by name: `npm test -- -t "should do something"`
13
+ - File + name: `npm test -- path/to/file.test.ts -t "should do something"`
14
+ - Coverage: `npm test -- --coverage`
15
+ - Debug flaky/async: add `--runInBand`
16
+
17
+ CI parity sequence: `npm ci` → `npm run lint` → `npm run build` → `npm test -- --coverage`.
18
+
19
+ Note: there are currently no committed Jest specs under `src/`. `roborockLib/test.js` is integration-style, not a Jest spec. New tests should be `*.test.ts` colocated in `src/`.
20
+
21
+ ## Architecture
22
+
23
+ This is a Homebridge dynamic platform plugin that bridges Roborock vacuums to HomeKit. It has three source roots with very different concerns:
24
+
25
+ - **`src/`** (TypeScript, strict mode, `module: commonjs`, `target: ES2018`) — the Homebridge plugin surface. Compiled to `dist/`; `dist/index.js` is the published entry point.
26
+ - `src/index.ts` registers the platform with Homebridge via `PLATFORM_NAME` from `settings.ts`.
27
+ - `src/platform.ts` is the `DynamicPlatformPlugin`. It restores cached accessories, then on `APIEvent.DID_FINISH_LAUNCHING` discovers devices via the Roborock API and registers/unregisters `PlatformAccessory`s. It also installs a process-wide filter for Node's DEP0040 deprecation warning emitted by an upstream dep — keep that filter in place when editing.
28
+ - `src/vacuum_accessory.ts` wires HomeKit Services/Characteristics to vacuum state and scene-switch behavior. Characteristic handlers must stay lightweight and resilient to null state.
29
+ - `src/crypto.ts` handles Roborock token encryption/decryption and key files. Returns safe defaults on decryption failure rather than throwing — preserve that behavior.
30
+ - `src/types.ts`, `src/settings.ts`, `src/logger.ts` — config shape, plugin/platform name constants, and the platform logger wrapper.
31
+ - `src/ui/index.ts` — TypeScript shared by the config UI; distinct from the `homebridge-ui/` runtime assets below.
32
+
33
+ - **`roborockLib/`** (legacy JavaScript, **not** part of `tsc` compilation) — Roborock cloud/local protocol implementation, loaded from `src/platform.ts` via `require("../roborockLib/roborockAPI")`. Subdirectories cover MQTT transport, local connector, message queueing, auth, map parsing (`RRMapParser.js`), per-model feature flags (`deviceFeatures.js`), and packaging helpers. Treat changes here as risky — they impact both cloud and local device communication paths.
34
+
35
+ - **`homebridge-ui/`** — Homebridge config UI: `server.js` (Express server using `@homebridge/plugin-ui-utils`) plus `public/` static assets. This is independent of `src/` compilation.
36
+
37
+ The `src/` ↔ `roborockLib/` boundary is the main complexity: TypeScript is strict but `roborockAPI` is consumed via `require` and typed as `any` on the platform. When threading new fields through, either widen the surface in `src/types.ts` or keep narrow `unknown` + narrowing at the boundary.
38
+
39
+ ## OpenSpec workflow
40
+
41
+ This repo uses OpenSpec (`openspec/`) for change proposals — see `openspec/AGENTS.md` and `openspec/project.md`. For non-trivial behavior changes, new capabilities, breaking changes, or architecture shifts, create a change proposal under `openspec/changes/<change-id>/` before coding rather than going straight to implementation.
42
+
43
+ ## Conventions worth knowing
44
+
45
+ - Prettier: `tabWidth: 2`, `semi: true`, `trailingComma: "es5"`. Ignored: `coverage`, `dist`, `node_modules`, `__snapshots__`.
46
+ - TS strict is on, but `noImplicitAny: false` for legacy interop. Don't introduce broad new `any` in TS code; prefer `unknown` + narrowing.
47
+ - Existing snake_case filenames (e.g. `vacuum_accessory.ts`) are intentional — preserve them.
48
+ - Constants like `PLUGIN_NAME` / `PLATFORM_NAME` are `UPPER_SNAKE_CASE` and load-bearing for Homebridge accessory caching — UUID generation must stay deterministic.
49
+ - `config.schema.json` is the user-visible config contract; keep it aligned with `src/types.ts` and README when adding options.
50
+ - `AGENTS.md` carries overlapping command/architecture guidance for other agentic tools; keep it in sync when changing the equivalent sections here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-vacuum",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -455,6 +455,9 @@ class Roborock {
455
455
  this.devices = this.devices.filter(
456
456
  (device) => !ignoredSet.has(device.sn)
457
457
  );
458
+ this.receivedDevices = (homedataResult.receivedDevices || []).filter(
459
+ (device) => !ignoredSet.has(device.sn)
460
+ );
458
461
 
459
462
  const allManagedDevices = (homedataResult.devices || []).concat(
460
463
  homedataResult.receivedDevices || []
@@ -1008,11 +1011,21 @@ class Roborock {
1008
1011
  }
1009
1012
 
1010
1013
  getProductAttribute(duid, attribute) {
1011
- const products = this.products;
1012
- const productID = this.devices.find(
1013
- (device) => device.duid == duid
1014
- ).productId;
1015
- const product = products.find((product) => product.id == productID);
1014
+ // Owned devices live in this.devices; shared devices in this.receivedDevices.
1015
+ const device =
1016
+ this.devices.find((device) => device.duid == duid) ||
1017
+ (this.receivedDevices || []).find((device) => device.duid == duid);
1018
+
1019
+ if (!device) {
1020
+ this.log.warn(
1021
+ `getProductAttribute: no device found for duid ${duid}, returning null for "${attribute}".`
1022
+ );
1023
+ return null;
1024
+ }
1025
+
1026
+ const product = (this.products || []).find(
1027
+ (product) => product.id == device.productId
1028
+ );
1016
1029
 
1017
1030
  return product ? product[attribute] : null;
1018
1031
  }