iobroker.proscenic 0.1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/admin/i18n/de.json +9 -0
- package/admin/i18n/en.json +9 -0
- package/admin/i18n/es.json +9 -0
- package/admin/i18n/fr.json +9 -0
- package/admin/i18n/it.json +9 -0
- package/admin/i18n/nl.json +9 -0
- package/admin/i18n/pl.json +9 -0
- package/admin/i18n/pt.json +9 -0
- package/admin/i18n/ru.json +9 -0
- package/admin/i18n/uk.json +9 -0
- package/admin/i18n/zh-cn.json +9 -0
- package/admin/jsonConfig.json +85 -0
- package/admin/proscenic-m7-pro.png +0 -0
- package/admin/proscenic.png +0 -0
- package/build/domain/commands.js +129 -0
- package/build/domain/commands.js.map +7 -0
- package/build/domain/consumables.js +63 -0
- package/build/domain/consumables.js.map +7 -0
- package/build/domain/live-map.js +829 -0
- package/build/domain/live-map.js.map +7 -0
- package/build/domain/maintenance-message.js +128 -0
- package/build/domain/maintenance-message.js.map +7 -0
- package/build/domain/map.js +58 -0
- package/build/domain/map.js.map +7 -0
- package/build/domain/reconnect-policy.js +45 -0
- package/build/domain/reconnect-policy.js.map +7 -0
- package/build/domain/status.js +108 -0
- package/build/domain/status.js.map +7 -0
- package/build/main.js +529 -0
- package/build/main.js.map +7 -0
- package/build/objects/object-definitions.js +214 -0
- package/build/objects/object-definitions.js.map +7 -0
- package/build/objects/projector.js +231 -0
- package/build/objects/projector.js.map +7 -0
- package/build/protocol/crypto.js +81 -0
- package/build/protocol/crypto.js.map +7 -0
- package/build/protocol/gateway-client.js +136 -0
- package/build/protocol/gateway-client.js.map +7 -0
- package/build/protocol/rest-client.js +184 -0
- package/build/protocol/rest-client.js.map +7 -0
- package/build/protocol/types.js +17 -0
- package/build/protocol/types.js.map +7 -0
- package/io-package.json +125 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C@ptain Ch@os
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# ioBroker.proscenic
|
|
2
|
+
|
|
3
|
+
Development-stage ioBroker adapter for supported Proscenic vacuum robots,
|
|
4
|
+
initially the Proscenic M7 Pro (`M7_PRO`, model `811_LDS`) through the legacy
|
|
5
|
+
Proscenic cloud/gateway path.
|
|
6
|
+
|
|
7
|
+
Manufacturer/product information: [Proscenic robot vacuums](https://www.proscenic.com/collections/robot-vacuums).
|
|
8
|
+
|
|
9
|
+
## Project status
|
|
10
|
+
|
|
11
|
+
This repository contains a TypeScript ioBroker adapter that can authenticate
|
|
12
|
+
against the verified legacy European Proscenic cloud, enumerate one real M7 Pro,
|
|
13
|
+
connect to the cloud gateway, project normalized runtime states, and expose the
|
|
14
|
+
first real-device-validated control and maintenance objects.
|
|
15
|
+
|
|
16
|
+
Verified on one owner-local Proscenic M7 Pro so far:
|
|
17
|
+
|
|
18
|
+
- cloud login, token acquisition, device discovery, gateway discovery, framed
|
|
19
|
+
socket reception, decryption, bounded reconnect, and adapter unload cleanup;
|
|
20
|
+
- status projection for battery, cleaning mode, fan, mop/water candidates,
|
|
21
|
+
features, safe map metadata, and redacted maintenance diagnostics;
|
|
22
|
+
- writable command buttons for start, pause, continue, return to station, fan
|
|
23
|
+
modes, deep cleaning, and dust collection;
|
|
24
|
+
- consumable counters for filter, side brush, main brush, and sensors;
|
|
25
|
+
- recent maintenance/message history, including dust-bag messages;
|
|
26
|
+
- experimental local-development live-map rendering for VIS debugging, including
|
|
27
|
+
app-oriented map colors, no-go areas, room-zone candidates, charger marker,
|
|
28
|
+
and an interpolated pose trail.
|
|
29
|
+
|
|
30
|
+
The adapter is not a public release yet. The supported hardware claim is limited
|
|
31
|
+
to the tested M7 Pro (`M7_PRO` / `811_LDS`) and the legacy cloud backend. Other
|
|
32
|
+
models, accounts, regions, firmware versions, Tuya-local control, room-cleaning
|
|
33
|
+
commands, final map storage/retention policy, and production release readiness
|
|
34
|
+
still require separate evidence.
|
|
35
|
+
|
|
36
|
+
Raw payloads, serial numbers, gateway endpoints, device addresses, credentials,
|
|
37
|
+
packet captures, private maps, and home-layout data are not committed or exposed
|
|
38
|
+
as public fixtures. The current live-map object is explicitly experimental and
|
|
39
|
+
intended for local VIS/debug use.
|
|
40
|
+
|
|
41
|
+
## Project identity
|
|
42
|
+
|
|
43
|
+
- GitHub repository: `ioBroker.proscenic`
|
|
44
|
+
- npm package: `iobroker.proscenic`
|
|
45
|
+
- adapter ID: `proscenic`
|
|
46
|
+
- namespace: `proscenic.<instance>`
|
|
47
|
+
- license: MIT
|
|
48
|
+
|
|
49
|
+
## Security and privacy
|
|
50
|
+
|
|
51
|
+
Never publish Proscenic credentials, session tokens, serial numbers, device or
|
|
52
|
+
gateway addresses, private network data, maps, packet captures, or logs.
|
|
53
|
+
Security policy and contribution requirements are documented in
|
|
54
|
+
[SECURITY.md](SECURITY.md) and [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
55
|
+
|
|
56
|
+
## Documentation
|
|
57
|
+
|
|
58
|
+
- [Project definition](docs/PROJECT_DEFINITION.md)
|
|
59
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
60
|
+
- [Upstream research and provenance](docs/UPSTREAM_RESEARCH.md)
|
|
61
|
+
- [Publication checklist](docs/PUBLICATION_CHECKLIST.md)
|
|
62
|
+
- [Architecture decisions](docs/decisions/README.md)
|
|
63
|
+
|
|
64
|
+
## Changelog
|
|
65
|
+
|
|
66
|
+
### 0.1.0 (2026-09-14)
|
|
67
|
+
|
|
68
|
+
- Added the first real-device-validated command object tree for the M7 Pro:
|
|
69
|
+
start, pause, continue, return, fan modes, deep cleaning, and dust collection.
|
|
70
|
+
- Added status projection for the verified legacy Proscenic cloud/gateway path.
|
|
71
|
+
- Added consumable runtime objects with derived remaining percentage and overdue
|
|
72
|
+
hours.
|
|
73
|
+
- Added bounded maintenance/message history objects for recent device messages.
|
|
74
|
+
- Added experimental local live-map rendering for VIS debugging with app-oriented
|
|
75
|
+
colors, no-go overlays, room-zone candidates, charger marker, and pose trail.
|
|
76
|
+
- Added the M7 Pro product image and a display-prefixed device code.
|
|
77
|
+
- Improved command value normalization for visualization tools.
|
|
78
|
+
|
|
79
|
+
### 0.0.1 (2026-09-10)
|
|
80
|
+
|
|
81
|
+
- Initial adapter skeleton and project foundation.
|
|
82
|
+
|
|
83
|
+
## Independence
|
|
84
|
+
|
|
85
|
+
This is an independent interoperability project and is not affiliated with,
|
|
86
|
+
endorsed by, or sponsored by Proscenic. Product and company names belong to
|
|
87
|
+
their respective owners.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT License. See [LICENSE](LICENSE).
|
|
92
|
+
|
|
93
|
+
Copyright (c) 2026 C@ptain Ch@os
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic Adaptereinstellungen",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App-Anbieter",
|
|
5
|
+
"cloudEmail": "Cloud-E-Mail-Adresse",
|
|
6
|
+
"cloudPassword": "Cloud-Passwort",
|
|
7
|
+
"preferredDeviceCode": "Bevorzugter Gerätecode",
|
|
8
|
+
"preferredDeviceCodeHelp": "Optionaler Filter für das erste unterstützte Zielgerät. Für den verifizierten Proscenic M7 Pro M7_PRO beibehalten."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Optional filter for the first supported target. Keep M7_PRO for the verified Proscenic M7 Pro."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Filtro opcional para el primer dispositivo compatible. Mantén M7_PRO para el Proscenic M7 Pro verificado."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Filtre optionnel pour le premier appareil pris en charge. Conservez M7_PRO pour le Proscenic M7 Pro vérifié."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Filtro opzionale per il primo dispositivo supportato. Mantieni M7_PRO per il Proscenic M7 Pro verificato."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Optioneel filter voor het eerste ondersteunde apparaat. Laat M7_PRO staan voor de geverifieerde Proscenic M7 Pro."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Opcjonalny filtr dla pierwszego obsługiwanego urządzenia. Pozostaw M7_PRO dla zweryfikowanego Proscenic M7 Pro."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Filtro opcional para o primeiro dispositivo compatível. Mantenha M7_PRO para o Proscenic M7 Pro verificado."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Необязательный фильтр для первого поддерживаемого устройства. Оставьте M7_PRO для проверенного Proscenic M7 Pro."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "Необов’язковий фільтр для першого підтримуваного пристрою. Залиште M7_PRO для перевіреного Proscenic M7 Pro."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"proscenic adapter settings": "Proscenic adapter settings",
|
|
3
|
+
"region": "Region",
|
|
4
|
+
"appVendor": "App vendor",
|
|
5
|
+
"cloudEmail": "Cloud email address",
|
|
6
|
+
"cloudPassword": "Cloud password",
|
|
7
|
+
"preferredDeviceCode": "Preferred device code",
|
|
8
|
+
"preferredDeviceCodeHelp": "第一个受支持设备的可选筛选条件。已验证的 Proscenic M7 Pro 请保留 M7_PRO。"
|
|
9
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"i18n": true,
|
|
3
|
+
"type": "panel",
|
|
4
|
+
"items": {
|
|
5
|
+
"adapterLogo": {
|
|
6
|
+
"type": "staticImage",
|
|
7
|
+
"src": "proscenic.png",
|
|
8
|
+
"newLine": true,
|
|
9
|
+
"xs": 12,
|
|
10
|
+
"sm": 12,
|
|
11
|
+
"md": 12,
|
|
12
|
+
"lg": 12,
|
|
13
|
+
"xl": 12,
|
|
14
|
+
"style": {
|
|
15
|
+
"width": 140,
|
|
16
|
+
"height": 140,
|
|
17
|
+
"objectFit": "contain",
|
|
18
|
+
"margin": "8px 0 16px 0"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"region": {
|
|
22
|
+
"type": "select",
|
|
23
|
+
"label": "region",
|
|
24
|
+
"options": [
|
|
25
|
+
{
|
|
26
|
+
"label": "Europe",
|
|
27
|
+
"value": "eu"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"newLine": true,
|
|
31
|
+
"xs": 12,
|
|
32
|
+
"sm": 12,
|
|
33
|
+
"md": 6,
|
|
34
|
+
"lg": 4,
|
|
35
|
+
"xl": 4
|
|
36
|
+
},
|
|
37
|
+
"vendor": {
|
|
38
|
+
"type": "select",
|
|
39
|
+
"label": "appVendor",
|
|
40
|
+
"options": [
|
|
41
|
+
{
|
|
42
|
+
"label": "Proscenic",
|
|
43
|
+
"value": "proscenic"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"newLine": false,
|
|
47
|
+
"xs": 12,
|
|
48
|
+
"sm": 12,
|
|
49
|
+
"md": 6,
|
|
50
|
+
"lg": 4,
|
|
51
|
+
"xl": 4
|
|
52
|
+
},
|
|
53
|
+
"username": {
|
|
54
|
+
"type": "text",
|
|
55
|
+
"label": "cloudEmail",
|
|
56
|
+
"newLine": true,
|
|
57
|
+
"xs": 12,
|
|
58
|
+
"sm": 12,
|
|
59
|
+
"md": 12,
|
|
60
|
+
"lg": 6,
|
|
61
|
+
"xl": 6
|
|
62
|
+
},
|
|
63
|
+
"password": {
|
|
64
|
+
"type": "password",
|
|
65
|
+
"label": "cloudPassword",
|
|
66
|
+
"newLine": false,
|
|
67
|
+
"xs": 12,
|
|
68
|
+
"sm": 12,
|
|
69
|
+
"md": 12,
|
|
70
|
+
"lg": 6,
|
|
71
|
+
"xl": 6
|
|
72
|
+
},
|
|
73
|
+
"deviceCode": {
|
|
74
|
+
"type": "text",
|
|
75
|
+
"label": "preferredDeviceCode",
|
|
76
|
+
"help": "preferredDeviceCodeHelp",
|
|
77
|
+
"newLine": true,
|
|
78
|
+
"xs": 12,
|
|
79
|
+
"sm": 12,
|
|
80
|
+
"md": 12,
|
|
81
|
+
"lg": 6,
|
|
82
|
+
"xl": 6
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var commands_exports = {};
|
|
20
|
+
__export(commands_exports, {
|
|
21
|
+
COMMAND_DEFINITIONS: () => COMMAND_DEFINITIONS,
|
|
22
|
+
buildCommandRequest: () => buildCommandRequest,
|
|
23
|
+
commandForStateId: () => commandForStateId,
|
|
24
|
+
normalizeCommandButtonValue: () => normalizeCommandButtonValue
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(commands_exports);
|
|
27
|
+
const COMMAND_DEFINITIONS = [
|
|
28
|
+
{ id: "commands.start", command: "start" },
|
|
29
|
+
{ id: "commands.pause", command: "pause" },
|
|
30
|
+
{ id: "commands.continue", command: "continue" },
|
|
31
|
+
{ id: "commands.return", command: "return" },
|
|
32
|
+
{ id: "commands.fan.quiet", command: "fanQuiet" },
|
|
33
|
+
{ id: "commands.fan.standard", command: "fanStandard" },
|
|
34
|
+
{ id: "commands.fan.strong", command: "fanStrong" },
|
|
35
|
+
{ id: "commands.deepCleaning", command: "deepCleaning" },
|
|
36
|
+
{ id: "commands.collectDust", command: "collectDust" }
|
|
37
|
+
];
|
|
38
|
+
function commandForStateId(id) {
|
|
39
|
+
var _a;
|
|
40
|
+
return (_a = COMMAND_DEFINITIONS.find((definition) => definition.id === id)) == null ? void 0 : _a.command;
|
|
41
|
+
}
|
|
42
|
+
function normalizeCommandButtonValue(value) {
|
|
43
|
+
if (value === true || value === 1) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (value === false || value === 0 || value === null) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value === "string") {
|
|
50
|
+
const normalized = value.trim().toLowerCase();
|
|
51
|
+
if (normalized === "true" || normalized === "1" || normalized === "on") {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
function buildCommandRequest(command, serial, username, now = /* @__PURE__ */ new Date()) {
|
|
61
|
+
const encodedSerial = encodeURIComponent(serial);
|
|
62
|
+
const encodedUsername = encodeURIComponent(username);
|
|
63
|
+
switch (command) {
|
|
64
|
+
case "start":
|
|
65
|
+
return {
|
|
66
|
+
path: `/instructions/cmd21005/${encodedSerial}?username=${encodedUsername}`,
|
|
67
|
+
body: "cleanMode=sweepOnly"
|
|
68
|
+
};
|
|
69
|
+
case "pause":
|
|
70
|
+
return {
|
|
71
|
+
path: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,
|
|
72
|
+
body: "mode=pause"
|
|
73
|
+
};
|
|
74
|
+
case "continue":
|
|
75
|
+
return {
|
|
76
|
+
path: `/instructions/${encodedSerial}/21017?username=${encodedUsername}`,
|
|
77
|
+
body: "pauseOrContinue=continue"
|
|
78
|
+
};
|
|
79
|
+
case "return":
|
|
80
|
+
return {
|
|
81
|
+
path: `/instructions/${encodedSerial}/21012?username=${encodedUsername}`,
|
|
82
|
+
body: "charge=start"
|
|
83
|
+
};
|
|
84
|
+
case "fanQuiet":
|
|
85
|
+
return {
|
|
86
|
+
path: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,
|
|
87
|
+
body: "setMode=quiet"
|
|
88
|
+
};
|
|
89
|
+
case "fanStandard":
|
|
90
|
+
return {
|
|
91
|
+
path: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,
|
|
92
|
+
body: "setMode=auto"
|
|
93
|
+
};
|
|
94
|
+
case "fanStrong":
|
|
95
|
+
return {
|
|
96
|
+
path: `/instructions/${encodedSerial}/21022?username=${encodedUsername}`,
|
|
97
|
+
body: "setMode=strong"
|
|
98
|
+
};
|
|
99
|
+
case "deepCleaning":
|
|
100
|
+
return {
|
|
101
|
+
path: `/instructions/cmd21005_2/${encodedSerial}?username=${encodedUsername}`,
|
|
102
|
+
body: "mode=depthTotalClean"
|
|
103
|
+
};
|
|
104
|
+
case "collectDust":
|
|
105
|
+
return {
|
|
106
|
+
path: `/instructions/cmd/${encodedSerial}?username=${encodedUsername}`,
|
|
107
|
+
contentType: "application/json;charset=UTF-8",
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
dInfo: {
|
|
110
|
+
ts: now.getTime().toString(),
|
|
111
|
+
userId: username
|
|
112
|
+
},
|
|
113
|
+
data: {
|
|
114
|
+
cmd: "startDustCenter",
|
|
115
|
+
value: 0
|
|
116
|
+
},
|
|
117
|
+
infoType: 21024
|
|
118
|
+
})
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
COMMAND_DEFINITIONS,
|
|
125
|
+
buildCommandRequest,
|
|
126
|
+
commandForStateId,
|
|
127
|
+
normalizeCommandButtonValue
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var consumables_exports = {};
|
|
20
|
+
__export(consumables_exports, {
|
|
21
|
+
normalizeConsumables21015: () => normalizeConsumables21015
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(consumables_exports);
|
|
24
|
+
const CONSUMABLE_INTERVAL_HOURS = {
|
|
25
|
+
filter: 150,
|
|
26
|
+
sideBrush: 200,
|
|
27
|
+
mainBrush: 300,
|
|
28
|
+
sensors: 30
|
|
29
|
+
};
|
|
30
|
+
function normalizeConsumables21015(data) {
|
|
31
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
const record = data;
|
|
35
|
+
const consumables = Object.fromEntries(
|
|
36
|
+
Object.keys(CONSUMABLE_INTERVAL_HOURS).map((component) => [component, normalizeConsumable(component, record[component])]).filter((entry) => entry[1] !== void 0)
|
|
37
|
+
);
|
|
38
|
+
return Object.keys(consumables).length > 0 ? consumables : void 0;
|
|
39
|
+
}
|
|
40
|
+
function normalizeConsumable(component, value) {
|
|
41
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
const usedSeconds = Math.trunc(value);
|
|
45
|
+
const intervalHours = CONSUMABLE_INTERVAL_HOURS[component];
|
|
46
|
+
const intervalSeconds = intervalHours * 3600;
|
|
47
|
+
const remainingPercent = roundToTwoDecimals((1 - usedSeconds / intervalSeconds) * 100);
|
|
48
|
+
const overdueHours = roundToTwoDecimals(Math.max(0, (usedSeconds - intervalSeconds) / 3600));
|
|
49
|
+
return {
|
|
50
|
+
usedSeconds,
|
|
51
|
+
intervalHours,
|
|
52
|
+
remainingPercent,
|
|
53
|
+
overdueHours
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function roundToTwoDecimals(value) {
|
|
57
|
+
return Math.round(value * 100) / 100;
|
|
58
|
+
}
|
|
59
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
60
|
+
0 && (module.exports = {
|
|
61
|
+
normalizeConsumables21015
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=consumables.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/consumables.ts"],
|
|
4
|
+
"sourcesContent": ["export type ConsumableComponent = \"filter\" | \"sideBrush\" | \"mainBrush\" | \"sensors\";\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\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": ";;;;;;;;;;;;;;;;;;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;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|