iobroker.proscenic 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -11
- package/build/domain/commands.js +30 -2
- package/build/domain/commands.js.map +2 -2
- package/build/domain/consumables.js +83 -2
- package/build/domain/consumables.js.map +2 -2
- package/build/domain/live-map.js +254 -57
- package/build/domain/live-map.js.map +2 -2
- package/build/domain/reconnect-policy.js +1 -1
- package/build/domain/reconnect-policy.js.map +1 -1
- package/build/domain/status.js +39 -0
- package/build/domain/status.js.map +2 -2
- package/build/domain/zone-cleaning.js +115 -0
- package/build/domain/zone-cleaning.js.map +7 -0
- package/build/main.js +833 -76
- package/build/main.js.map +3 -3
- package/build/objects/object-definitions.js +91 -7
- package/build/objects/object-definitions.js.map +2 -2
- package/build/objects/projector.js +99 -2
- package/build/objects/projector.js.map +2 -2
- package/build/protocol/rest-client.js +13 -2
- package/build/protocol/rest-client.js.map +2 -2
- package/io-package.json +27 -1
- package/package.json +1 -1
- package/www/map-viewer/index.html +72 -0
- package/www/map-viewer/viewer.css +183 -0
- package/www/map-viewer/viewer.js +542 -0
package/README.md
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
<img width="147" height="139" alt="image" src="https://github.com/user-attachments/assets/c5d6a8c4-e6e4-42d6-be61-2c79e37f5a60" />
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
5
|
Development-stage ioBroker adapter for supported Proscenic vacuum robots,
|
|
9
6
|
initially the Proscenic M7 Pro (`M7_PRO`, model `811_LDS`) through the legacy
|
|
10
7
|
Proscenic cloud/gateway path.
|
|
@@ -22,30 +19,53 @@ Verified on Proscenic M7 Pro so far:
|
|
|
22
19
|
|
|
23
20
|
<img width="1136" height="378" alt="image" src="https://github.com/user-attachments/assets/8a53c100-3ec4-41a0-8254-29cdf28c7000" />
|
|
24
21
|
|
|
25
|
-
|
|
26
22
|
- cloud login, token acquisition, device discovery, gateway discovery, framed
|
|
27
23
|
socket reception, decryption, bounded reconnect, and adapter unload cleanup;
|
|
28
24
|
- status projection for battery, cleaning mode, fan, mop/water candidates,
|
|
29
25
|
features, safe map metadata, and redacted maintenance diagnostics;
|
|
30
26
|
- writable command buttons for start, pause, continue, return to station, fan
|
|
31
|
-
modes, deep cleaning, and
|
|
27
|
+
modes, deep cleaning, dust collection, and guarded saved-zone cleaning;
|
|
32
28
|
- consumable counters for filter, side brush, main brush, and sensors;
|
|
33
29
|
- recent maintenance/message history, including dust-bag messages;
|
|
34
30
|
- local-development live-map rendering for VIS, including
|
|
35
|
-
app-oriented map colors, no-go areas,
|
|
36
|
-
and
|
|
31
|
+
app-oriented map colors, saved zones, no-go areas, charger marker,
|
|
32
|
+
a task-scoped pose trail, and SVG animation between gateway positions. A
|
|
33
|
+
static PNG fallback and a same-origin iframe viewer URL remain available for
|
|
34
|
+
other visualization modes. In the iframe viewer, visible saved zones can be
|
|
35
|
+
selected directly on the map and started with an in-map action button.
|
|
37
36
|
|
|
38
37
|
The adapter is an early public beta. The supported hardware claim is limited to
|
|
39
38
|
the tested M7 Pro (`M7_PRO` / `811_LDS`) and the legacy cloud backend. Other
|
|
40
|
-
models, accounts, regions, firmware versions, Tuya-local control,
|
|
41
|
-
|
|
42
|
-
evidence.
|
|
39
|
+
models, accounts, regions, firmware versions, Tuya-local control, final
|
|
40
|
+
real-device validation of zone cleaning, and map storage/retention policy still
|
|
41
|
+
require separate evidence.
|
|
43
42
|
|
|
44
43
|
Raw payloads, serial numbers, gateway endpoints, device addresses, credentials,
|
|
45
44
|
packet captures, private maps, and home-layout data are not committed or exposed
|
|
46
45
|
as public fixtures. The current live-map object is explicitly experimental and
|
|
47
46
|
intended for local VIS/debug use.
|
|
48
47
|
|
|
48
|
+
## Saved-zone cleaning
|
|
49
|
+
|
|
50
|
+
`commands.zones.available` lists the currently selectable saved zones from the
|
|
51
|
+
authoritative 21004 catalog. Write a JSON array of one or more listed numeric
|
|
52
|
+
IDs to `commands.zones.selectedIds`, then trigger `commands.zones.start`.
|
|
53
|
+
|
|
54
|
+
The adapter refreshes and validates the catalog and active map before it sends
|
|
55
|
+
one partial-cleaning request. Empty, stale, unknown, duplicate, forbidden, or
|
|
56
|
+
malformed selections are rejected. REST acceptance is reported through the
|
|
57
|
+
normal `commands.last*` diagnostics and is followed by a bounded status
|
|
58
|
+
confirmation wait.
|
|
59
|
+
|
|
60
|
+
`map.live.showZoneOverlays` controls both visibility and interactive selection
|
|
61
|
+
in the `map.live.viewerUrl` iframe. When enabled, unselected zones are shown as
|
|
62
|
+
transparent white overlays with contrasting blue-gray boundaries and selected
|
|
63
|
+
zones as transparent light-green overlays. Clicking a valid zone toggles its ID in
|
|
64
|
+
`commands.zones.selectedIds`. As soon as at least one valid zone is selected,
|
|
65
|
+
the viewer shows a compact start action which writes to `commands.zones.start`.
|
|
66
|
+
Turning the switch off hides and disables the interactive overlays without
|
|
67
|
+
modifying upstream saved zones.
|
|
68
|
+
|
|
49
69
|
## Project identity
|
|
50
70
|
|
|
51
71
|
- GitHub repository: `ioBroker.proscenic`
|
|
@@ -71,6 +91,40 @@ Security policy and contribution requirements are documented in
|
|
|
71
91
|
|
|
72
92
|
## Changelog
|
|
73
93
|
|
|
94
|
+
### Unreleased
|
|
95
|
+
|
|
96
|
+
### 0.3.0 (2026-09-20)
|
|
97
|
+
|
|
98
|
+
- Added a derived robot activity state for dashboards and strengthened command
|
|
99
|
+
delivery with bounded cloud-session recovery and explicit confirmation
|
|
100
|
+
diagnostics.
|
|
101
|
+
- Changed the primary experimental live-map image to an animated SVG while
|
|
102
|
+
retaining an explicit static PNG fallback.
|
|
103
|
+
- Prevented transient charging status reports during active cleaning from
|
|
104
|
+
erasing the current task's accumulated pose trail.
|
|
105
|
+
- Added `map.live.viewerUrl` with an interactive, same-origin live-map viewer
|
|
106
|
+
that updates without reloading its iframe, uses the configured canvas color,
|
|
107
|
+
and presents the centered map without an internal title bar.
|
|
108
|
+
- Added guarded consumable-reset button candidates with a fresh read before the
|
|
109
|
+
write, `21016` gateway confirmation, exact non-target counter validation, and
|
|
110
|
+
a persistent `21015` readback. The sensor reset has been validated on the
|
|
111
|
+
real device; the remaining component resets still need separate validation.
|
|
112
|
+
- Added guarded saved-zone cleaning through `commands.zones.selectedIds` and
|
|
113
|
+
`commands.zones.start`, with a fresh 21004 validation before the 30000
|
|
114
|
+
request. Added `map.live.showZoneOverlays` for VIS and direct click selection
|
|
115
|
+
plus a compact contextual start button in the same-origin map viewer. The
|
|
116
|
+
authoritative 21004 catalog now distinguishes selectable zones from
|
|
117
|
+
forbidden areas and keeps stale map selections from being executed.
|
|
118
|
+
|
|
119
|
+
### 0.2.0 (2026-09-16)
|
|
120
|
+
|
|
121
|
+
- Added writable live-map color controls for the white background and blue map
|
|
122
|
+
area used by the rendered PNG.
|
|
123
|
+
- Preserved the live-map cleaning trail across room-to-room path changes during
|
|
124
|
+
the same cleaning task.
|
|
125
|
+
- Clarified device availability diagnostics and the distinction between cloud,
|
|
126
|
+
gateway, and last-known robot online states.
|
|
127
|
+
|
|
74
128
|
### 0.1.4 (2026-09-15)
|
|
75
129
|
|
|
76
130
|
- Refresh existing ioBroker object metadata during adapter startup so role,
|
|
@@ -100,7 +154,7 @@ Security policy and contribution requirements are documented in
|
|
|
100
154
|
hours.
|
|
101
155
|
- Added bounded maintenance/message history objects for recent device messages.
|
|
102
156
|
- Added experimental local live-map rendering for VIS debugging with app-oriented
|
|
103
|
-
colors, no-go overlays,
|
|
157
|
+
colors, no-go overlays, saved-zone overlays, charger marker, and pose trail.
|
|
104
158
|
- Added the M7 Pro product image and a display-prefixed device code.
|
|
105
159
|
- Improved command value normalization for visualization tools.
|
|
106
160
|
|
package/build/domain/commands.js
CHANGED
|
@@ -21,7 +21,9 @@ __export(commands_exports, {
|
|
|
21
21
|
COMMAND_DEFINITIONS: () => COMMAND_DEFINITIONS,
|
|
22
22
|
buildCommandRequest: () => buildCommandRequest,
|
|
23
23
|
commandForStateId: () => commandForStateId,
|
|
24
|
-
|
|
24
|
+
isStatusConfirmationForCommand: () => isStatusConfirmationForCommand,
|
|
25
|
+
normalizeCommandButtonValue: () => normalizeCommandButtonValue,
|
|
26
|
+
supportsStatusConfirmation: () => supportsStatusConfirmation
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(commands_exports);
|
|
27
29
|
const COMMAND_DEFINITIONS = [
|
|
@@ -39,6 +41,30 @@ function commandForStateId(id) {
|
|
|
39
41
|
var _a;
|
|
40
42
|
return (_a = COMMAND_DEFINITIONS.find((definition) => definition.id === id)) == null ? void 0 : _a.command;
|
|
41
43
|
}
|
|
44
|
+
function supportsStatusConfirmation(command) {
|
|
45
|
+
return command !== "collectDust";
|
|
46
|
+
}
|
|
47
|
+
function isStatusConfirmationForCommand(command, status) {
|
|
48
|
+
switch (command) {
|
|
49
|
+
case "start":
|
|
50
|
+
case "continue":
|
|
51
|
+
case "deepCleaning":
|
|
52
|
+
case "zoneCleaning":
|
|
53
|
+
return status.mode === "sweep";
|
|
54
|
+
case "pause":
|
|
55
|
+
return status.mode === "pause";
|
|
56
|
+
case "return":
|
|
57
|
+
return status.mode === "backcharge" || status.mode === "charge" || status.mode === "fullcharge";
|
|
58
|
+
case "fanQuiet":
|
|
59
|
+
return status.fanMode === "quiet";
|
|
60
|
+
case "fanStandard":
|
|
61
|
+
return status.fanMode === "auto";
|
|
62
|
+
case "fanStrong":
|
|
63
|
+
return status.fanMode === "strong";
|
|
64
|
+
case "collectDust":
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
42
68
|
function normalizeCommandButtonValue(value) {
|
|
43
69
|
if (value === true || value === 1) {
|
|
44
70
|
return true;
|
|
@@ -124,6 +150,8 @@ function buildCommandRequest(command, serial, username, now = /* @__PURE__ */ ne
|
|
|
124
150
|
COMMAND_DEFINITIONS,
|
|
125
151
|
buildCommandRequest,
|
|
126
152
|
commandForStateId,
|
|
127
|
-
|
|
153
|
+
isStatusConfirmationForCommand,
|
|
154
|
+
normalizeCommandButtonValue,
|
|
155
|
+
supportsStatusConfirmation
|
|
128
156
|
});
|
|
129
157
|
//# sourceMappingURL=commands.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/domain/commands.ts"],
|
|
4
|
-
"sourcesContent": ["export type RobotCommand =\n\t| \"start\"\n\t| \"pause\"\n\t| \"continue\"\n\t| \"return\"\n\t| \"fanQuiet\"\n\t| \"fanStandard\"\n\t| \"fanStrong\"\n\t| \"deepCleaning\"\n\t| \"collectDust\";\n\nexport interface CommandRequest {\n\tpath: string;\n\tbody: string;\n\tcontentType?: string;\n}\n\nexport interface CommandDefinition {\n\tid: string;\n\tcommand: RobotCommand;\n}\n\nexport const COMMAND_DEFINITIONS: readonly CommandDefinition[] = [\n\t{ id: \"commands.start\", command: \"start\" },\n\t{ id: \"commands.pause\", command: \"pause\" },\n\t{ id: \"commands.continue\", command: \"continue\" },\n\t{ id: \"commands.return\", command: \"return\" },\n\t{ id: \"commands.fan.quiet\", command: \"fanQuiet\" },\n\t{ id: \"commands.fan.standard\", command: \"fanStandard\" },\n\t{ id: \"commands.fan.strong\", command: \"fanStrong\" },\n\t{ id: \"commands.deepCleaning\", command: \"deepCleaning\" },\n\t{ id: \"commands.collectDust\", command: \"collectDust\" },\n];\n\nexport function commandForStateId(id: string): RobotCommand | undefined {\n\treturn COMMAND_DEFINITIONS.find(definition => definition.id === id)?.command;\n}\n\nexport function normalizeCommandButtonValue(value: ioBroker.StateValue | undefined): boolean | undefined {\n\tif (value === true || value === 1) {\n\t\treturn true;\n\t}\n\n\tif (value === false || value === 0 || value === null) {\n\t\treturn false;\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst normalized = value.trim().toLowerCase();\n\t\tif (normalized === \"true\" || normalized === \"1\" || normalized === \"on\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (normalized === \"false\" || normalized === \"0\" || normalized === \"off\" || normalized === \"\") {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nexport function buildCommandRequest(\n\tcommand: RobotCommand,\n\tserial: string,\n\tusername: string,\n\tnow = new Date(),\n): CommandRequest {\n\tconst encodedSerial = encodeURIComponent(serial);\n\tconst encodedUsername = encodeURIComponent(username);\n\n\tswitch (command) {\n\t\tcase \"start\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd21005/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tbody: \"cleanMode=sweepOnly\",\n\t\t\t};\n\t\tcase \"pause\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,\n\t\t\t\tbody: \"mode=pause\",\n\t\t\t};\n\t\tcase \"continue\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,\n\t\t\t\tbody: \"pauseOrContinue=continue\",\n\t\t\t};\n\t\tcase \"return\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21012?username=${encodedUsername}`,\n\t\t\t\tbody: \"charge=start\",\n\t\t\t};\n\t\tcase \"fanQuiet\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=quiet\",\n\t\t\t};\n\t\tcase \"fanStandard\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=auto\",\n\t\t\t};\n\t\tcase \"fanStrong\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=strong\",\n\t\t\t};\n\t\tcase \"deepCleaning\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd21005_2/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tbody: \"mode=depthTotalClean\",\n\t\t\t};\n\t\tcase \"collectDust\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tcontentType: \"application/json;charset=UTF-8\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdInfo: {\n\t\t\t\t\t\tts: now.getTime().toString(),\n\t\t\t\t\t\tuserId: username,\n\t\t\t\t\t},\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tcmd: \"startDustCenter\",\n\t\t\t\t\t\tvalue: 0,\n\t\t\t\t\t},\n\t\t\t\t\tinfoType: 21024,\n\t\t\t\t}),\n\t\t\t};\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["export type RobotCommand =\n\t| \"start\"\n\t| \"pause\"\n\t| \"continue\"\n\t| \"return\"\n\t| \"fanQuiet\"\n\t| \"fanStandard\"\n\t| \"fanStrong\"\n\t| \"deepCleaning\"\n\t| \"collectDust\";\n\nexport type ConfirmableRobotCommand = RobotCommand | \"zoneCleaning\";\n\nexport interface CommandRequest {\n\tpath: string;\n\tbody: string;\n\tcontentType?: string;\n}\n\nexport interface CommandDefinition {\n\tid: string;\n\tcommand: RobotCommand;\n}\n\nexport interface CommandStatusSnapshot {\n\tmode?: string;\n\tfanMode?: string;\n}\n\nexport const COMMAND_DEFINITIONS: readonly CommandDefinition[] = [\n\t{ id: \"commands.start\", command: \"start\" },\n\t{ id: \"commands.pause\", command: \"pause\" },\n\t{ id: \"commands.continue\", command: \"continue\" },\n\t{ id: \"commands.return\", command: \"return\" },\n\t{ id: \"commands.fan.quiet\", command: \"fanQuiet\" },\n\t{ id: \"commands.fan.standard\", command: \"fanStandard\" },\n\t{ id: \"commands.fan.strong\", command: \"fanStrong\" },\n\t{ id: \"commands.deepCleaning\", command: \"deepCleaning\" },\n\t{ id: \"commands.collectDust\", command: \"collectDust\" },\n];\n\nexport function commandForStateId(id: string): RobotCommand | undefined {\n\treturn COMMAND_DEFINITIONS.find(definition => definition.id === id)?.command;\n}\n\nexport function supportsStatusConfirmation(command: ConfirmableRobotCommand): boolean {\n\treturn command !== \"collectDust\";\n}\n\nexport function isStatusConfirmationForCommand(\n\tcommand: ConfirmableRobotCommand,\n\tstatus: CommandStatusSnapshot,\n): boolean {\n\tswitch (command) {\n\t\tcase \"start\":\n\t\tcase \"continue\":\n\t\tcase \"deepCleaning\":\n\t\tcase \"zoneCleaning\":\n\t\t\treturn status.mode === \"sweep\";\n\t\tcase \"pause\":\n\t\t\treturn status.mode === \"pause\";\n\t\tcase \"return\":\n\t\t\treturn status.mode === \"backcharge\" || status.mode === \"charge\" || status.mode === \"fullcharge\";\n\t\tcase \"fanQuiet\":\n\t\t\treturn status.fanMode === \"quiet\";\n\t\tcase \"fanStandard\":\n\t\t\treturn status.fanMode === \"auto\";\n\t\tcase \"fanStrong\":\n\t\t\treturn status.fanMode === \"strong\";\n\t\tcase \"collectDust\":\n\t\t\treturn false;\n\t}\n}\n\nexport function normalizeCommandButtonValue(value: ioBroker.StateValue | undefined): boolean | undefined {\n\tif (value === true || value === 1) {\n\t\treturn true;\n\t}\n\n\tif (value === false || value === 0 || value === null) {\n\t\treturn false;\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst normalized = value.trim().toLowerCase();\n\t\tif (normalized === \"true\" || normalized === \"1\" || normalized === \"on\") {\n\t\t\treturn true;\n\t\t}\n\t\tif (normalized === \"false\" || normalized === \"0\" || normalized === \"off\" || normalized === \"\") {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nexport function buildCommandRequest(\n\tcommand: RobotCommand,\n\tserial: string,\n\tusername: string,\n\tnow = new Date(),\n): CommandRequest {\n\tconst encodedSerial = encodeURIComponent(serial);\n\tconst encodedUsername = encodeURIComponent(username);\n\n\tswitch (command) {\n\t\tcase \"start\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd21005/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tbody: \"cleanMode=sweepOnly\",\n\t\t\t};\n\t\tcase \"pause\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,\n\t\t\t\tbody: \"mode=pause\",\n\t\t\t};\n\t\tcase \"continue\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,\n\t\t\t\tbody: \"pauseOrContinue=continue\",\n\t\t\t};\n\t\tcase \"return\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21012?username=${encodedUsername}`,\n\t\t\t\tbody: \"charge=start\",\n\t\t\t};\n\t\tcase \"fanQuiet\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=quiet\",\n\t\t\t};\n\t\tcase \"fanStandard\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=auto\",\n\t\t\t};\n\t\tcase \"fanStrong\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,\n\t\t\t\tbody: \"setMode=strong\",\n\t\t\t};\n\t\tcase \"deepCleaning\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd21005_2/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tbody: \"mode=depthTotalClean\",\n\t\t\t};\n\t\tcase \"collectDust\":\n\t\t\treturn {\n\t\t\t\tpath: `/instructions/cmd/${encodedSerial}?username=${encodedUsername}`,\n\t\t\t\tcontentType: \"application/json;charset=UTF-8\",\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdInfo: {\n\t\t\t\t\t\tts: now.getTime().toString(),\n\t\t\t\t\t\tuserId: username,\n\t\t\t\t\t},\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tcmd: \"startDustCenter\",\n\t\t\t\t\t\tvalue: 0,\n\t\t\t\t\t},\n\t\t\t\t\tinfoType: 21024,\n\t\t\t\t}),\n\t\t\t};\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,15 +18,89 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var consumables_exports = {};
|
|
20
20
|
__export(consumables_exports, {
|
|
21
|
-
|
|
21
|
+
CONSUMABLE_RESET_DEFINITIONS: () => CONSUMABLE_RESET_DEFINITIONS,
|
|
22
|
+
buildConsumableResetRequest: () => buildConsumableResetRequest,
|
|
23
|
+
consumableCounterSnapshotsEqual: () => consumableCounterSnapshotsEqual,
|
|
24
|
+
consumableResetForStateId: () => consumableResetForStateId,
|
|
25
|
+
isConsumableResetConfirmation: () => isConsumableResetConfirmation,
|
|
26
|
+
normalizeConsumableCounterSnapshot: () => normalizeConsumableCounterSnapshot,
|
|
27
|
+
normalizeConsumables21015: () => normalizeConsumables21015,
|
|
28
|
+
runSafeConsumableReset: () => runSafeConsumableReset
|
|
22
29
|
});
|
|
23
30
|
module.exports = __toCommonJS(consumables_exports);
|
|
31
|
+
const CONSUMABLE_RESET_DEFINITIONS = [
|
|
32
|
+
{ id: "consumables.filter.reset", component: "filter", name: "Reset filter runtime" },
|
|
33
|
+
{ id: "consumables.sideBrush.reset", component: "sideBrush", name: "Reset side brush runtime" },
|
|
34
|
+
{ id: "consumables.mainBrush.reset", component: "mainBrush", name: "Reset main brush runtime" },
|
|
35
|
+
{ id: "consumables.sensors.reset", component: "sensors", name: "Reset sensor runtime" }
|
|
36
|
+
];
|
|
24
37
|
const CONSUMABLE_INTERVAL_HOURS = {
|
|
25
38
|
filter: 150,
|
|
26
39
|
sideBrush: 200,
|
|
27
40
|
mainBrush: 300,
|
|
28
41
|
sensors: 30
|
|
29
42
|
};
|
|
43
|
+
const CONSUMABLE_COUNTER_FIELDS = [
|
|
44
|
+
"filter",
|
|
45
|
+
"mainBrush",
|
|
46
|
+
"sideBrush",
|
|
47
|
+
"sensors",
|
|
48
|
+
"battery"
|
|
49
|
+
];
|
|
50
|
+
function consumableResetForStateId(id) {
|
|
51
|
+
var _a;
|
|
52
|
+
return (_a = CONSUMABLE_RESET_DEFINITIONS.find((definition) => definition.id === id)) == null ? void 0 : _a.component;
|
|
53
|
+
}
|
|
54
|
+
function normalizeConsumableCounterSnapshot(data) {
|
|
55
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const record = data;
|
|
59
|
+
const keys = Object.keys(record);
|
|
60
|
+
if (keys.length !== CONSUMABLE_COUNTER_FIELDS.length || keys.some((key) => !CONSUMABLE_COUNTER_FIELDS.includes(key))) {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const entries = CONSUMABLE_COUNTER_FIELDS.map((field) => [field, record[field]]);
|
|
64
|
+
if (entries.some(([, value]) => typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
return Object.fromEntries(entries);
|
|
68
|
+
}
|
|
69
|
+
function buildConsumableResetRequest(component, snapshot, serial, username) {
|
|
70
|
+
return {
|
|
71
|
+
path: `/instructions/cmd21016/${encodeURIComponent(serial)}?username=${encodeURIComponent(username)}`,
|
|
72
|
+
contentType: "application/json;charset=UTF-8",
|
|
73
|
+
body: JSON.stringify({ ...snapshot, [component]: 0 })
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function isConsumableResetConfirmation(before, after, component) {
|
|
77
|
+
return CONSUMABLE_COUNTER_FIELDS.every((field) => after[field] === (field === component ? 0 : before[field]));
|
|
78
|
+
}
|
|
79
|
+
function consumableCounterSnapshotsEqual(expected, actual) {
|
|
80
|
+
return CONSUMABLE_COUNTER_FIELDS.every((field) => actual[field] === expected[field]);
|
|
81
|
+
}
|
|
82
|
+
async function runSafeConsumableReset(component, operations) {
|
|
83
|
+
await operations.onProgress("reading-before-reset");
|
|
84
|
+
const before = await operations.readSnapshot();
|
|
85
|
+
if (before[component] === 0) {
|
|
86
|
+
await operations.onProgress("already-zero");
|
|
87
|
+
return "already-zero";
|
|
88
|
+
}
|
|
89
|
+
await operations.onProgress("sending");
|
|
90
|
+
const echoed = await operations.sendReset(before);
|
|
91
|
+
if (!isConsumableResetConfirmation(before, echoed, component)) {
|
|
92
|
+
throw new Error("Consumable reset echo changed unexpected counters or did not reset the target");
|
|
93
|
+
}
|
|
94
|
+
await operations.onProgress("echo-confirmed");
|
|
95
|
+
await operations.onProgress("verifying-readback");
|
|
96
|
+
const persisted = await operations.readSnapshot();
|
|
97
|
+
const expected = { ...before, [component]: 0 };
|
|
98
|
+
if (!consumableCounterSnapshotsEqual(expected, persisted)) {
|
|
99
|
+
throw new Error("Consumable reset did not persist exactly across the verification read");
|
|
100
|
+
}
|
|
101
|
+
await operations.onProgress("verified");
|
|
102
|
+
return "verified";
|
|
103
|
+
}
|
|
30
104
|
function normalizeConsumables21015(data) {
|
|
31
105
|
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
32
106
|
return void 0;
|
|
@@ -58,6 +132,13 @@ function roundToTwoDecimals(value) {
|
|
|
58
132
|
}
|
|
59
133
|
// Annotate the CommonJS export names for ESM import in node:
|
|
60
134
|
0 && (module.exports = {
|
|
61
|
-
|
|
135
|
+
CONSUMABLE_RESET_DEFINITIONS,
|
|
136
|
+
buildConsumableResetRequest,
|
|
137
|
+
consumableCounterSnapshotsEqual,
|
|
138
|
+
consumableResetForStateId,
|
|
139
|
+
isConsumableResetConfirmation,
|
|
140
|
+
normalizeConsumableCounterSnapshot,
|
|
141
|
+
normalizeConsumables21015,
|
|
142
|
+
runSafeConsumableReset
|
|
62
143
|
});
|
|
63
144
|
//# sourceMappingURL=consumables.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/domain/consumables.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,MAAM,4BAAiE;AAAA,EACtE,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AACV;AAEO,SAAS,0BAA0B,MAA6C;AACtF,MAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AACrE,WAAO;AAAA,EACR;AAEA,QAAM,SAAS;AACf,QAAM,cAAc,OAAO;AAAA,IACzB,OAAO,KAAK,yBAAyB,EACpC,IAAI,eAAa,CAAC,WAAW,oBAAoB,WAAW,OAAO,SAAS,CAAC,CAAC,CAAU,EACxF,OAAO,CAAC,UAAoE,MAAM,CAAC,MAAM,MAAS;AAAA,EACrG;AAEA,SAAO,OAAO,KAAK,WAAW,EAAE,SAAS,IAAI,cAAc;AAC5D;AAEA,SAAS,oBAAoB,WAAgC,OAA6C;AACzG,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG;AACtE,WAAO;AAAA,EACR;AAEA,QAAM,cAAc,KAAK,MAAM,KAAK;AACpC,QAAM,gBAAgB,0BAA0B,SAAS;AACzD,QAAM,kBAAkB,gBAAgB;AACxC,QAAM,mBAAmB,oBAAoB,IAAI,cAAc,mBAAmB,GAAG;AACrF,QAAM,eAAe,mBAAmB,KAAK,IAAI,IAAI,cAAc,mBAAmB,IAAK,CAAC;AAE5F,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAEA,SAAS,mBAAmB,OAAuB;AAClD,SAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AAClC;",
|
|
4
|
+
"sourcesContent": ["import type { CommandRequest } from \"./commands\";\n\nexport type ConsumableComponent = \"filter\" | \"sideBrush\" | \"mainBrush\" | \"sensors\";\n\nexport type ConsumableCounterField = ConsumableComponent | \"battery\";\n\nexport type ConsumableCounterSnapshot = Record<ConsumableCounterField, number>;\n\nexport interface ConsumableResetDefinition {\n\tid: string;\n\tcomponent: ConsumableComponent;\n\tname: string;\n}\n\nexport type ConsumableResetResult =\n\t\"reading-before-reset\" | \"sending\" | \"echo-confirmed\" | \"verifying-readback\" | \"verified\" | \"already-zero\";\n\nexport interface SafeConsumableResetOperations {\n\treadSnapshot: () => Promise<ConsumableCounterSnapshot>;\n\tsendReset: (before: ConsumableCounterSnapshot) => Promise<ConsumableCounterSnapshot>;\n\tonProgress: (result: ConsumableResetResult) => Promise<void>;\n}\n\nexport const CONSUMABLE_RESET_DEFINITIONS: readonly ConsumableResetDefinition[] = [\n\t{ id: \"consumables.filter.reset\", component: \"filter\", name: \"Reset filter runtime\" },\n\t{ id: \"consumables.sideBrush.reset\", component: \"sideBrush\", name: \"Reset side brush runtime\" },\n\t{ id: \"consumables.mainBrush.reset\", component: \"mainBrush\", name: \"Reset main brush runtime\" },\n\t{ id: \"consumables.sensors.reset\", component: \"sensors\", name: \"Reset sensor runtime\" },\n];\n\nexport interface ConsumableState {\n\tusedSeconds: number;\n\tintervalHours: number;\n\tremainingPercent: number;\n\toverdueHours: number;\n}\n\nexport type ConsumableStates = Partial<Record<ConsumableComponent, ConsumableState>>;\n\nconst CONSUMABLE_INTERVAL_HOURS: Record<ConsumableComponent, number> = {\n\tfilter: 150,\n\tsideBrush: 200,\n\tmainBrush: 300,\n\tsensors: 30,\n};\n\nconst CONSUMABLE_COUNTER_FIELDS: readonly ConsumableCounterField[] = [\n\t\"filter\",\n\t\"mainBrush\",\n\t\"sideBrush\",\n\t\"sensors\",\n\t\"battery\",\n];\n\nexport function consumableResetForStateId(id: string): ConsumableComponent | undefined {\n\treturn CONSUMABLE_RESET_DEFINITIONS.find(definition => definition.id === id)?.component;\n}\n\nexport function normalizeConsumableCounterSnapshot(data: unknown): ConsumableCounterSnapshot | undefined {\n\tif (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n\t\treturn undefined;\n\t}\n\n\tconst record = data as Record<string, unknown>;\n\tconst keys = Object.keys(record);\n\tif (\n\t\tkeys.length !== CONSUMABLE_COUNTER_FIELDS.length ||\n\t\tkeys.some(key => !CONSUMABLE_COUNTER_FIELDS.includes(key as ConsumableCounterField))\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst entries = CONSUMABLE_COUNTER_FIELDS.map(field => [field, record[field]] as const);\n\tif (entries.some(([, value]) => typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 0)) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(entries) as ConsumableCounterSnapshot;\n}\n\nexport function buildConsumableResetRequest(\n\tcomponent: ConsumableComponent,\n\tsnapshot: ConsumableCounterSnapshot,\n\tserial: string,\n\tusername: string,\n): CommandRequest {\n\treturn {\n\t\tpath: `/instructions/cmd21016/${encodeURIComponent(serial)}?username=${encodeURIComponent(username)}`,\n\t\tcontentType: \"application/json;charset=UTF-8\",\n\t\tbody: JSON.stringify({ ...snapshot, [component]: 0 }),\n\t};\n}\n\nexport function isConsumableResetConfirmation(\n\tbefore: ConsumableCounterSnapshot,\n\tafter: ConsumableCounterSnapshot,\n\tcomponent: ConsumableComponent,\n): boolean {\n\treturn CONSUMABLE_COUNTER_FIELDS.every(field => after[field] === (field === component ? 0 : before[field]));\n}\n\nexport function consumableCounterSnapshotsEqual(\n\texpected: ConsumableCounterSnapshot,\n\tactual: ConsumableCounterSnapshot,\n): boolean {\n\treturn CONSUMABLE_COUNTER_FIELDS.every(field => actual[field] === expected[field]);\n}\n\nexport async function runSafeConsumableReset(\n\tcomponent: ConsumableComponent,\n\toperations: SafeConsumableResetOperations,\n): Promise<\"verified\" | \"already-zero\"> {\n\tawait operations.onProgress(\"reading-before-reset\");\n\tconst before = await operations.readSnapshot();\n\tif (before[component] === 0) {\n\t\tawait operations.onProgress(\"already-zero\");\n\t\treturn \"already-zero\";\n\t}\n\n\tawait operations.onProgress(\"sending\");\n\tconst echoed = await operations.sendReset(before);\n\tif (!isConsumableResetConfirmation(before, echoed, component)) {\n\t\tthrow new Error(\"Consumable reset echo changed unexpected counters or did not reset the target\");\n\t}\n\n\tawait operations.onProgress(\"echo-confirmed\");\n\tawait operations.onProgress(\"verifying-readback\");\n\tconst persisted = await operations.readSnapshot();\n\tconst expected = { ...before, [component]: 0 };\n\tif (!consumableCounterSnapshotsEqual(expected, persisted)) {\n\t\tthrow new Error(\"Consumable reset did not persist exactly across the verification read\");\n\t}\n\n\tawait operations.onProgress(\"verified\");\n\treturn \"verified\";\n}\n\nexport function normalizeConsumables21015(data: unknown): ConsumableStates | undefined {\n\tif (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n\t\treturn undefined;\n\t}\n\n\tconst record = data as Record<string, unknown>;\n\tconst consumables = Object.fromEntries(\n\t\t(Object.keys(CONSUMABLE_INTERVAL_HOURS) as ConsumableComponent[])\n\t\t\t.map(component => [component, normalizeConsumable(component, record[component])] as const)\n\t\t\t.filter((entry): entry is readonly [ConsumableComponent, ConsumableState] => entry[1] !== undefined),\n\t) as ConsumableStates;\n\n\treturn Object.keys(consumables).length > 0 ? consumables : undefined;\n}\n\nfunction normalizeConsumable(component: ConsumableComponent, value: unknown): ConsumableState | undefined {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n\t\treturn undefined;\n\t}\n\n\tconst usedSeconds = Math.trunc(value);\n\tconst intervalHours = CONSUMABLE_INTERVAL_HOURS[component];\n\tconst intervalSeconds = intervalHours * 3_600;\n\tconst remainingPercent = roundToTwoDecimals((1 - usedSeconds / intervalSeconds) * 100);\n\tconst overdueHours = roundToTwoDecimals(Math.max(0, (usedSeconds - intervalSeconds) / 3_600));\n\n\treturn {\n\t\tusedSeconds,\n\t\tintervalHours,\n\t\tremainingPercent,\n\t\toverdueHours,\n\t};\n}\n\nfunction roundToTwoDecimals(value: number): number {\n\treturn Math.round(value * 100) / 100;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|