homebridge-roborock-matter 3.4.5 → 3.4.6
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 +9 -0
- package/homebridge-ui/public/index.js +48 -5
- package/package.json +1 -1
- package/roborockLib/roborockAPI.js +80 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.6
|
|
4
|
+
|
|
5
|
+
**Trying cloud-only mode once marked a robot "Cloud only" forever.** jawnlydon reported in [#7](https://github.com/mathiashornbek/homebridge-roborock-matter/issues/7) that a "Cloud Only" instance of his robot "seems to have stuck around through several re-pairs" — surviving a bridge restart, repeated Apple Home re-pairings, and a complete uninstall and reinstall of the plugin. It was not a leftover accessory. It was one stale field.
|
|
6
|
+
|
|
7
|
+
- **The markers cloud-only mode writes are now retracted when it is switched off.** Enabling the mode stamps a robot's transport diagnostics to say local transport is disabled, and those diagnostics are persisted. `tcpConnectionState` is only ever rewritten when a LAN connection is actually attempted — and none is attempted for a robot no local IP was discovered for — so for such a robot the marker stayed on disk permanently, outliving the setting that wrote it. Startup now reconciles the markers in both directions, clearing only the fields that still hold the marker value so a live LAN connection is never stomped.
|
|
8
|
+
- **The report stopped contradicting itself.** Reading that stale marker back, the device card said `connectionStatus: Cloud only` with the hint "Cloud-only mode is enabled, so local LAN discovery and local TCP control are disabled" — two lines under the same report's `cloudOnlyMode: disabled`. The report was pointing at a setting that was off, and it cost its reader an evening.
|
|
9
|
+
- **Setting and clearing derive from one table**, so a marker added later is retracted later. A hand-written list of fields to put back is the same mistake as a hand-written list of files or log lines, one level down — the lesson 3.4.3 and 3.4.5 each learned in their own layer.
|
|
10
|
+
- **`cloudOnlyMode` in the diagnostic report now quotes the saved config, not the checkbox.** The `matterFeatures` line was fixed for exactly this reason and the fix stopped at that line, leaving the line directly above it still reading its form control — so a report could state a setting the running plugin did not have. A test now enumerates the rule over the source: nothing the report builder reaches may read the settings form, except the helpers whose whole job is to warn that the form and the saved config disagree. Unsaved edits to cloud-only mode now raise that warning too.
|
|
11
|
+
|
|
3
12
|
## 3.4.5
|
|
4
13
|
|
|
5
14
|
**The one log line that exists to diagnose Apple Home display problems was hiding the transitions.** `Matter publish for <robot>: battery=…, operationalState=…, runMode=…, cleanMode=…` was added so an Apple Home display issue could be settled from a single log excerpt — and it was only ever emitted when the **battery** value changed. skmzwanke reported in [#8](https://github.com/mathiashornbek/homebridge-roborock-matter/issues/8) that his Apple Home tile sat on "Traveling to Room"/"Preparing" for an entire cleaning run, and sent a log covering that whole run: every operational-state transition in it was invisible, because the line only appeared on the four polls where the battery happened to tick down. The log contained the answer and could not show it.
|
|
@@ -252,21 +252,64 @@ function getCloudOnlyMode() {
|
|
|
252
252
|
*
|
|
253
253
|
* @returns {Promise<string>}
|
|
254
254
|
*/
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The plugin config as SAVED, or null when it cannot be read.
|
|
257
|
+
*
|
|
258
|
+
* Every settings-derived line in the diagnostic report goes through here. The
|
|
259
|
+
* form is only ever consulted afterwards, to warn that it disagrees.
|
|
260
|
+
*
|
|
261
|
+
* @returns {Promise<Record<string, any> | null>}
|
|
262
|
+
*/
|
|
263
|
+
async function readSavedPluginConfig() {
|
|
257
264
|
try {
|
|
258
265
|
const configs =
|
|
259
266
|
window.homebridge &&
|
|
260
267
|
typeof window.homebridge.getPluginConfig === "function"
|
|
261
268
|
? await window.homebridge.getPluginConfig()
|
|
262
269
|
: null;
|
|
263
|
-
|
|
264
|
-
? configs.find((entry) => entry.platform === "RoborockVacuumPlatform")
|
|
270
|
+
return configs
|
|
271
|
+
? configs.find((entry) => entry.platform === "RoborockVacuumPlatform") ||
|
|
272
|
+
null
|
|
265
273
|
: null;
|
|
266
274
|
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Cloud-only mode as the RUNNING plugin has it.
|
|
281
|
+
*
|
|
282
|
+
* This read the checkbox until 3.4.6, which is the same trap the matterFeatures
|
|
283
|
+
* line was fixed for one line below — fixing the line in front of me instead of
|
|
284
|
+
* the rule. A user who had tried cloud-only mode and switched it off again got
|
|
285
|
+
* a report whose settings line said `disabled` while the device below it said
|
|
286
|
+
* "Cloud only", and spent an evening hunting a ghost setting.
|
|
287
|
+
*
|
|
288
|
+
* @returns {Promise<string>}
|
|
289
|
+
*/
|
|
290
|
+
async function describeSavedCloudOnlyMode() {
|
|
291
|
+
const config = await readSavedPluginConfig();
|
|
292
|
+
if (!config) {
|
|
267
293
|
return "unavailable";
|
|
268
294
|
}
|
|
269
295
|
|
|
296
|
+
const label = config.cloudOnlyMode === true ? "enabled" : "disabled";
|
|
297
|
+
return hasUnsavedCloudOnlyEdit(config)
|
|
298
|
+
? `${label} (WARNING: the settings form has unsaved changes; this is the value the plugin is running)`
|
|
299
|
+
: label;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** True when the cloud-only checkbox differs from what is saved in the config. */
|
|
303
|
+
function hasUnsavedCloudOnlyEdit(config) {
|
|
304
|
+
return (
|
|
305
|
+
Boolean(elements.cloudOnlyMode && elements.cloudOnlyMode.checked) !==
|
|
306
|
+
(config.cloudOnlyMode === true)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function describeEnabledMatterFeatures() {
|
|
311
|
+
const config = await readSavedPluginConfig();
|
|
312
|
+
|
|
270
313
|
if (!config) {
|
|
271
314
|
return "unavailable";
|
|
272
315
|
}
|
|
@@ -1114,7 +1157,7 @@ async function buildDiagnosticsReport(result) {
|
|
|
1114
1157
|
`nodeVersion: ${result.nodeVersion || "unknown"}`,
|
|
1115
1158
|
`token: ${hasToken ? "present" : "missing"}`,
|
|
1116
1159
|
`homeData: ${result.hasHomeData ? "present" : "missing"}`,
|
|
1117
|
-
`cloudOnlyMode: ${
|
|
1160
|
+
`cloudOnlyMode: ${await describeSavedCloudOnlyMode()}`,
|
|
1118
1161
|
// Which Apple Home features are switched on decides what the plugin is
|
|
1119
1162
|
// even allowed to publish, so a report that omits them cannot answer
|
|
1120
1163
|
// "why doesn't Apple Home show X?" — the first question every one of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.6",
|
|
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": {
|
|
@@ -38,6 +38,27 @@ const B01_LIVE_ROOM_FETCH_V1_STATES = new Set([5, 11, 16, 17, 18]);
|
|
|
38
38
|
// cached live room is cleared so a later run starts fresh.
|
|
39
39
|
const B01_LIVE_ROOM_CLEAR_V1_STATES = new Set([3, 8]);
|
|
40
40
|
|
|
41
|
+
// The transport-diagnostics fields cloud-only mode owns, and the marker value
|
|
42
|
+
// it writes into each.
|
|
43
|
+
//
|
|
44
|
+
// These diagnostics are persisted, so the markers outlive the setting unless
|
|
45
|
+
// something puts them back. `tcpConnectionState` is the one that stuck: it is
|
|
46
|
+
// only rewritten when a LAN connection is actually attempted, and none is
|
|
47
|
+
// attempted for a robot no local IP was ever discovered for. A user who tried
|
|
48
|
+
// cloud-only mode once and switched it off again kept "Cloud only" on the
|
|
49
|
+
// device card through restarts, re-pairs and a full plugin reinstall, and the
|
|
50
|
+
// diagnostic report told them cloud-only mode was enabled two lines under its
|
|
51
|
+
// own `cloudOnlyMode: disabled`.
|
|
52
|
+
//
|
|
53
|
+
// Setting and clearing both derive from this table on purpose. A hand-written
|
|
54
|
+
// list of fields to clear is the same mistake as a hand-written list of files
|
|
55
|
+
// or log lines one level up: it is correct until someone adds a fourth marker.
|
|
56
|
+
const CLOUD_ONLY_TRANSPORT_MARKERS = Object.freeze({
|
|
57
|
+
lastTransportReason: "cloud-only-mode",
|
|
58
|
+
localDiscoveryState: "disabled",
|
|
59
|
+
tcpConnectionState: "disabled",
|
|
60
|
+
});
|
|
61
|
+
|
|
41
62
|
// Minimum gap between live-room map fetch attempts while cleaning. The map
|
|
42
63
|
// payload is an order of magnitude heavier than get_status, so it rides a
|
|
43
64
|
// slower cadence than the active status polls — but 20s meant a robot could
|
|
@@ -896,6 +917,49 @@ class Roborock {
|
|
|
896
917
|
});
|
|
897
918
|
}
|
|
898
919
|
|
|
920
|
+
/**
|
|
921
|
+
* Reconcile one robot's cloud-only transport markers with the mode as it is
|
|
922
|
+
* configured right now.
|
|
923
|
+
*
|
|
924
|
+
* Enabling stamps every marker in CLOUD_ONLY_TRANSPORT_MARKERS. Disabling
|
|
925
|
+
* clears exactly those fields that still hold the marker value, so a LAN
|
|
926
|
+
* connection that came up in the meantime is never stomped, and a robot that
|
|
927
|
+
* has no diagnostics yet is not given any.
|
|
928
|
+
*
|
|
929
|
+
* @param {string} duid
|
|
930
|
+
* @param {boolean} cloudOnly
|
|
931
|
+
*/
|
|
932
|
+
async syncCloudOnlyTransportMarkers(duid, cloudOnly) {
|
|
933
|
+
if (!duid) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (cloudOnly) {
|
|
938
|
+
await this.updateTransportDiagnostics(duid, {
|
|
939
|
+
...CLOUD_ONLY_TRANSPORT_MARKERS,
|
|
940
|
+
});
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const entry = this.getTransportDiagnostics()[duid];
|
|
945
|
+
if (!entry || typeof entry != "object") {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const cleared = {};
|
|
950
|
+
for (const [field, marker] of Object.entries(
|
|
951
|
+
CLOUD_ONLY_TRANSPORT_MARKERS
|
|
952
|
+
)) {
|
|
953
|
+
if (entry[field] === marker) {
|
|
954
|
+
cleared[field] = null;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (Object.keys(cleared).length) {
|
|
959
|
+
await this.updateTransportDiagnostics(duid, cleared);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
899
963
|
getTransportDiagnostics() {
|
|
900
964
|
const diagnostics = this.getStateAsync("TransportDiagnostics");
|
|
901
965
|
if (diagnostics && typeof diagnostics.val == "string") {
|
|
@@ -1518,29 +1582,30 @@ class Roborock {
|
|
|
1518
1582
|
ignoredSet
|
|
1519
1583
|
);
|
|
1520
1584
|
|
|
1521
|
-
|
|
1585
|
+
const cloudOnly = this.isCloudOnlyModeEnabled();
|
|
1586
|
+
if (cloudOnly) {
|
|
1522
1587
|
this.log.info(
|
|
1523
1588
|
"Roborock cloud-only mode is enabled; local LAN discovery and TCP connections will be skipped."
|
|
1524
1589
|
);
|
|
1590
|
+
}
|
|
1525
1591
|
|
|
1526
|
-
|
|
1592
|
+
for (const device of managedDevicesForDiagnostics) {
|
|
1593
|
+
if (cloudOnly) {
|
|
1527
1594
|
await this.updateTransportDiagnostics(device.duid, {
|
|
1528
1595
|
lastTransport: "cloud",
|
|
1529
|
-
lastTransportReason: "cloud-only-mode",
|
|
1530
1596
|
localIp: null,
|
|
1531
|
-
|
|
1532
|
-
|
|
1597
|
+
});
|
|
1598
|
+
} else if (!device.localKey) {
|
|
1599
|
+
await this.updateTransportDiagnostics(device.duid, {
|
|
1600
|
+
lastTransport: "cloud",
|
|
1601
|
+
lastTransportReason: "missing-local-key",
|
|
1533
1602
|
});
|
|
1534
1603
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
lastTransportReason: "missing-local-key",
|
|
1541
|
-
});
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1604
|
+
|
|
1605
|
+
// Runs in BOTH directions: switching the mode off has to retract
|
|
1606
|
+
// the markers it wrote, or they stay on disk for good and the
|
|
1607
|
+
// device keeps reporting itself as cloud-only.
|
|
1608
|
+
await this.syncCloudOnlyTransportMarkers(device.duid, cloudOnly);
|
|
1544
1609
|
}
|
|
1545
1610
|
|
|
1546
1611
|
// this.adapter.log.debug(`initUser test: ${JSON.stringify(Array.from(this.adapter.localKeys.entries()))}`);
|
|
@@ -4505,6 +4570,6 @@ class Roborock {
|
|
|
4505
4570
|
}
|
|
4506
4571
|
}
|
|
4507
4572
|
|
|
4508
|
-
module.exports = { Roborock };
|
|
4573
|
+
module.exports = { Roborock, CLOUD_ONLY_TRANSPORT_MARKERS };
|
|
4509
4574
|
|
|
4510
4575
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|