homebridge-roborock-matter 2.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.
Files changed (59) hide show
  1. package/.gitattributes +13 -0
  2. package/CHANGELOG.md +469 -0
  3. package/LICENSE +23 -0
  4. package/README.md +45 -0
  5. package/__tests__/testdata/b01_q7_get_prop_response.json +1 -0
  6. package/__tests__/testdata/b01_q7_map_fixture.json +16 -0
  7. package/config.schema.json +118 -0
  8. package/dist/crypto.js +65 -0
  9. package/dist/crypto.js.map +1 -0
  10. package/dist/index.js +10 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/live_message.js +25 -0
  13. package/dist/live_message.js.map +1 -0
  14. package/dist/logger.js +37 -0
  15. package/dist/logger.js.map +1 -0
  16. package/dist/matter_vacuum_accessory.js +1877 -0
  17. package/dist/matter_vacuum_accessory.js.map +1 -0
  18. package/dist/platform.js +441 -0
  19. package/dist/platform.js.map +1 -0
  20. package/dist/settings.js +8 -0
  21. package/dist/settings.js.map +1 -0
  22. package/dist/types.js +3 -0
  23. package/dist/types.js.map +1 -0
  24. package/dist/ui/index.js +741 -0
  25. package/dist/ui/index.js.map +1 -0
  26. package/docs/matter-rvc-updating-homebridge-report.md +120 -0
  27. package/homebridge-roborock-vacuum.code-workspace +7 -0
  28. package/homebridge-ui/public/index.html +257 -0
  29. package/homebridge-ui/public/index.js +1276 -0
  30. package/homebridge-ui/public/styles.css +611 -0
  31. package/homebridge-ui/server.js +3 -0
  32. package/package.json +91 -0
  33. package/roborockLib/i18n/de/translations.json +188 -0
  34. package/roborockLib/i18n/en/translations.json +208 -0
  35. package/roborockLib/i18n/es/translations.json +188 -0
  36. package/roborockLib/i18n/fr/translations.json +188 -0
  37. package/roborockLib/i18n/it/translations.json +188 -0
  38. package/roborockLib/i18n/nl/translations.json +188 -0
  39. package/roborockLib/i18n/pl/translations.json +188 -0
  40. package/roborockLib/i18n/pt/translations.json +188 -0
  41. package/roborockLib/i18n/ru/translations.json +188 -0
  42. package/roborockLib/i18n/uk/translations.json +188 -0
  43. package/roborockLib/i18n/zh-cn/translations.json +188 -0
  44. package/roborockLib/lib/RRMapParser.js +459 -0
  45. package/roborockLib/lib/b01Q7Adapter.js +468 -0
  46. package/roborockLib/lib/deviceFeatures.js +1349 -0
  47. package/roborockLib/lib/localConnector.js +629 -0
  48. package/roborockLib/lib/map/map.html +137 -0
  49. package/roborockLib/lib/map/zones.js +851 -0
  50. package/roborockLib/lib/message.js +402 -0
  51. package/roborockLib/lib/messageQueueHandler.js +411 -0
  52. package/roborockLib/lib/roborockAuth.js +178 -0
  53. package/roborockLib/lib/roborockCrypto.js +55 -0
  54. package/roborockLib/lib/roborockPackageHelper.js +142 -0
  55. package/roborockLib/lib/roborock_mqtt_connector.js +567 -0
  56. package/roborockLib/lib/sniffing/mitmproxy_roborock.py +300 -0
  57. package/roborockLib/lib/vacuum.js +992 -0
  58. package/roborockLib/roborockAPI.js +3635 -0
  59. package/tsconfig.roborockLib.json +17 -0
@@ -0,0 +1,851 @@
1
+ var mapBase64, selectedElement;
2
+ var left,
3
+ topMap,
4
+ mapMinX,
5
+ mapMinY,
6
+ mapSizeX,
7
+ mapSizeY,
8
+ goToTarget = false;
9
+ var zoomLevel = 0.55;
10
+ var scaleFactor = 2;
11
+
12
+ var zoomStep = 0.1;
13
+ var minZoom = 0.5;
14
+ var maxZoom = 3;
15
+ var wheelZoom = 1;
16
+
17
+ var panOffsetX = 0;
18
+ var panOffsetY = 0;
19
+ var isPanning = false;
20
+ var startX = 0;
21
+ var startY = 0;
22
+
23
+ var popupX, popupY;
24
+
25
+ var go_to_pin_image =
26
+ "data:image/png;base64,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AAAOVWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDplODk0ZjlkMy1iNjZlLTQ1ODQtODY0MS0zMGI5MTk2ZDM2NTQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6YjM1MWM4ODEtZmQwYi00MjlkLWExZDMtNjkwOTFiNWE1YzYxIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6YTZlMmNkZTctOGZhZC00ZDVhLWI5N2UtY2VhZTc0NWNmYjVmIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzczNjQ5MzQzNDYyMjgiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMiIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjM6MDI6MjVUMjM6NDI6MTIrMDE6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIzOjAyOjI1VDIzOjQyOjEyKzAxOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjcxYWIyOGQtODExMy00NWRiLWJkMjQtNGJkOTJjZjk5NThmIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIzLTAyLTI1VDIzOjI1OjIzIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA4MjZiNWE2LTgwZjctNDU2My1hY2UwLThjMTMzYjQyZmZhNCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChXaW5kb3dzKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wMi0yNVQyMzo0MjoxNCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz6W+nyxAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5wIZFioOADz0AgAAAyVJREFUaN7tmr9uE0EQhz8ncmKSwglQkMSCIg0URMgFFUUkZARyj1DeIhQuIsQj0LqnJHkBK6FJ5QK5IIpBsuhiWVEiOY7y3/INxaybBOE9+27vbPknjWSfd2fmu/Xt7c4djDXWWGMNkRLhupcJ4CnwHHgNrADLwLxp0AT+AD+B78Ae8BsS3pCdR5kCeQPyA+QExAORHuaZtj9M36lhgX0FsmMB2Mt21Fd8QVMgGyA3AcB27cb4TMUNdgbkG0gnQNiudYzvmbjA3gPZDAH0tm1qrGhhJ0C+Wk5Kg5pnYk1ECfwB5NoBbNeuNWY0sPdBjhzCdu1IY7uFTYAUI4DtWlFzcAf8CKQeIXBdc/CvfieAt8Cinw6ZDBQKUC5Do6FWLuuxTMZ3/EWTg7MRrvgZkXxepFIR8Ty5I8/T3/J536NccQW7CHJpm9jKikitJj1Vq2lbH8CXmkv4wDk/y8dSSaxVKvleduZcXMNPgEmbhrkcZLP2jrNZ7WOpSZNL6MBztvvopSVIp+0dp9Pax8defs4F8LQtcCoFyaS942RS+/gAnnYBfAaITcOrK2i37R2329rHdjIxuYQOfAJYlWDqdWi17B23WtrHUp7JJfRZejVGs/SqC+CHIBcxuA9faC5uFh97MVhp7blcWq77XfBnMiKFgki5LNJoqJXLeiyT6WsDse4SeBnkPMLd0rnm4G63dAhUIyy1VE0OroATZ8BWhMBbJgenVY95kNMI/s6nGrs/DVABTDSBYgSjWzSxIynkPQNpOhzdpsbsXwPWeBO/gJLDM1wyMSMtxi+AtByMbktjDaYgqviH5lqWMM+qiXFIPCQLIMchju5xEKMbNPRaiMBrxE8yC7IbAuyu+o6lJAvSDhC2rT5jLfkU0IPxjvqKvWQOZD8A4H31NRSSFwNuH8/Vx9BIEuZlFK/PJ/0bjh+HBgI91eesvTtE72jdgX4McuAD9kD7DLXkvWVZ90bbjoTkc49bVUfbjIxkFmT7P8DbMV5N9Q39AKT6D9iq/jaSkpe39s4tPTbSknfmdYVL/exWEdzcZQL4aL58GcKXwccaa6yx3Okve0d25MUPGTcAAAAASUVORK5CYII=";
27
+ var goToPin = new Image();
28
+
29
+ var canvasOffsetX, canvasOffsetY;
30
+
31
+ var data;
32
+ var map;
33
+ var popupTimeout;
34
+ var timeoutStart;
35
+
36
+ var selectedObstacleID;
37
+
38
+ window.onload = function () {
39
+ var popup = document.getElementById("popup");
40
+ var popupImage = document.getElementById("popup-image");
41
+ var triangle = document.getElementById("triangle");
42
+ var largePhoto = document.getElementById("largePhoto");
43
+ var largePhotoImage = document.getElementById("largePhoto-image");
44
+ var dataImagePattern =
45
+ /^data:(image\/[a-z0-9.+-]+);base64,([a-z0-9+/=\s]+)$/i;
46
+
47
+ function revokeObjectURL(imageElement) {
48
+ if (imageElement.dataset.objectUrl) {
49
+ URL.revokeObjectURL(imageElement.dataset.objectUrl);
50
+ delete imageElement.dataset.objectUrl;
51
+ }
52
+ }
53
+
54
+ function decodeImageData(rawValue) {
55
+ if (typeof rawValue !== "string") {
56
+ return null;
57
+ }
58
+
59
+ const value = rawValue.trim();
60
+ if (!value) {
61
+ return null;
62
+ }
63
+
64
+ const match = value.match(dataImagePattern);
65
+ if (!match) {
66
+ return null;
67
+ }
68
+
69
+ const mimeType = match[1].toLowerCase();
70
+ const base64Payload = match[2].replace(/\s+/g, "");
71
+
72
+ try {
73
+ const binary = atob(base64Payload);
74
+ const bytes = new Uint8Array(binary.length);
75
+ for (let index = 0; index < binary.length; index++) {
76
+ bytes[index] = binary.charCodeAt(index);
77
+ }
78
+ return new Blob([bytes], { type: mimeType });
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ function clearImageSource(imageElement) {
85
+ revokeObjectURL(imageElement);
86
+ imageElement.removeAttribute("src");
87
+ }
88
+
89
+ function applySafeImageSource(imageElement, rawValue) {
90
+ const imageBlob = decodeImageData(rawValue);
91
+ if (!imageBlob) {
92
+ console.warn("Ignoring unsafe obstacle image source");
93
+ return false;
94
+ }
95
+
96
+ revokeObjectURL(imageElement);
97
+ const objectUrl = URL.createObjectURL(imageBlob);
98
+ imageElement.dataset.objectUrl = objectUrl;
99
+ imageElement.src = objectUrl;
100
+ return true;
101
+ }
102
+
103
+ var socket = new WebSocket("ws://" + window.location.hostname + ":7906");
104
+
105
+ socket.onopen = () => {
106
+ console.log("Connected to WebSocket server");
107
+
108
+ socket.send(JSON.stringify({ command: "getRobots" }));
109
+ };
110
+
111
+ socket.onmessage = (event) => {
112
+ // console.log(`Received message: ${event.data}`);
113
+ data = JSON.parse(event.data);
114
+ var command = data.command;
115
+ // console.log(`Received message command: ${command}`);
116
+
117
+ switch (command) {
118
+ case "robotList":
119
+ console.log("Robot IDs: " + data.parameters);
120
+
121
+ selectedElement = document.getElementById("robotSelect");
122
+
123
+ if (selectedElement.childElementCount == 0) {
124
+ data.parameters.forEach((robot) => {
125
+ var option = document.createElement("option");
126
+ option.value = robot[0];
127
+ option.text = robot[1];
128
+ selectedElement.add(option);
129
+ });
130
+
131
+ getMap(selectedElement.value); // get map once for the selected robot
132
+ selectedElement.addEventListener("change", (event) => {
133
+ getMap(event.target.value);
134
+ });
135
+ }
136
+ break;
137
+
138
+ case "map":
139
+ map = data.map;
140
+ console.log(`map:`, map);
141
+
142
+ if (selectedElement.value == data.duid) {
143
+ mapBase64 = data.base64;
144
+ scaleFactor = data.scale;
145
+ left = map.IMAGE.position.left;
146
+ topMap = map.IMAGE.position.top;
147
+ drawBackgroundImage();
148
+ }
149
+ break;
150
+ case "get_status":
151
+ if (selectedElement.value == data.duid) {
152
+ if (data.parameters.isCleaning) {
153
+ // console.log("Cleaning in progress for robot: " + data.duid);
154
+ startButton.disabled = true;
155
+ stop.disabled = false;
156
+ } else {
157
+ // console.log("Cleaning not in progress for robot: " + data.duid);
158
+ startButton.disabled = false;
159
+ stop.disabled = true;
160
+ }
161
+ }
162
+ break;
163
+ }
164
+ };
165
+
166
+ socket.onclose = () => {
167
+ console.log("Disconnected from WebSocket server");
168
+ setTimeout(() => {
169
+ window.onload();
170
+ }, 10000);
171
+ };
172
+
173
+ function getMap(duid) {
174
+ const commandGetMap = {};
175
+ commandGetMap.command = "getMap";
176
+ commandGetMap.duid = duid;
177
+ socket.send(JSON.stringify(commandGetMap));
178
+ }
179
+
180
+ const canvas = document.getElementById("myCanvas");
181
+ const ctx = canvas.getContext("2d");
182
+ ctx.imageSmoothingEnabled = false;
183
+
184
+ const maxPanX = canvas.width / 2;
185
+ const maxPanY = canvas.height / 2;
186
+
187
+ canvasOffsetX = canvas.getBoundingClientRect().left;
188
+ canvasOffsetY = canvas.getBoundingClientRect().top;
189
+ console.log("canvasOffsetX: " + canvasOffsetX);
190
+ console.log("canvasOffsetY: " + canvasOffsetY);
191
+
192
+ async function localCoordsToRobotCoords(imagePoint) {
193
+ const image = new Image();
194
+ image.src = mapBase64;
195
+ const point = {};
196
+ await new Promise((resolve) => {
197
+ image.onload = function () {
198
+ point.x = Math.round((imagePoint.x + left) * 50.0);
199
+ point.y = Math.round(
200
+ (image.height / scaleFactor + topMap - imagePoint.y) * 50.0
201
+ );
202
+ resolve(point);
203
+ };
204
+ });
205
+ return point;
206
+ }
207
+
208
+ function mouseXtoCanvasX(e) {
209
+ return (e.pageX || e.touches[0].clientX) - canvasOffsetX - 2;
210
+ }
211
+ function mouseYtoCanvasY(e) {
212
+ return (e.pageY || e.touches[0].clientY) - canvasOffsetY - 2;
213
+ }
214
+
215
+ function canvasXtoMapX(x) {
216
+ return roundTwoDecimals(
217
+ ((x - panOffsetX) / wheelZoom / zoomLevel + mapMinX) / scaleFactor
218
+ );
219
+ }
220
+ function canvasYtoMapY(y) {
221
+ return roundTwoDecimals(
222
+ ((y - panOffsetY) / wheelZoom / zoomLevel + mapMinY) / scaleFactor
223
+ );
224
+ }
225
+
226
+ function getOriginalX(transformedX) {
227
+ return Math.floor(
228
+ (transformedX - map.IMAGE.position.left) * scaleFactor - 2 - mapMinX
229
+ );
230
+ }
231
+ function getOriginalY(transformedY) {
232
+ return Math.floor(
233
+ (map.IMAGE.dimensions.height / scaleFactor +
234
+ map.IMAGE.position.top -
235
+ transformedY) *
236
+ scaleFactor -
237
+ 2 -
238
+ mapMinY
239
+ );
240
+ }
241
+
242
+ function rectXtoRobotX(x) {
243
+ return Math.round(canvasXtoMapX((x + panOffsetX / wheelZoom) * wheelZoom));
244
+ }
245
+ function rectYtoRobotY(y) {
246
+ return Math.round(canvasYtoMapY((y + panOffsetY / wheelZoom) * wheelZoom));
247
+ }
248
+
249
+ let rects = [];
250
+ let zones = [];
251
+ let isDragging = false;
252
+ let isResizing = false;
253
+ let selectedRect = null;
254
+ let offsetRectX, offsetRectY;
255
+ let redDotOffsetX, redDotOffsetY;
256
+
257
+ async function downEvent(e) {
258
+ const mouseX = mouseXtoCanvasX(e);
259
+ const mouseY = mouseYtoCanvasY(e);
260
+ console.log(`mouseX: ${mouseX} mouseY: ${mouseY}`);
261
+
262
+ const mapX = canvasXtoMapX(mouseX);
263
+ const mapY = canvasYtoMapY(mouseY);
264
+
265
+ const point = await localCoordsToRobotCoords({ x: mapX, y: mapY });
266
+
267
+ // console.log("mousedown robot pos: " + JSON.stringify(point));
268
+ console.log("Robot coords: " + JSON.stringify([point.x, point.y]));
269
+ if (goToTarget == true) {
270
+ const data = {};
271
+ data.duid = selectedElement.value;
272
+ data.command = "app_goto_target";
273
+ data.parameters = [point.x, point.y];
274
+ socket.send(JSON.stringify(data));
275
+ goToTarget = false;
276
+ drawMap(true);
277
+ }
278
+
279
+ for (let i = 0; i < rects.length; i++) {
280
+ const rect = rects[i];
281
+
282
+ const canvasMinRectX = Math.round(
283
+ (rect.x + panOffsetX / wheelZoom) * wheelZoom
284
+ );
285
+ const canvasMinRectY = Math.round(
286
+ (rect.y + panOffsetY / wheelZoom) * wheelZoom
287
+ );
288
+
289
+ const canvasMaxRectX = Math.round(
290
+ (rect.x + rect.width + panOffsetX / wheelZoom) * wheelZoom
291
+ );
292
+ const canvasMaxRectY = Math.round(
293
+ (rect.y + rect.height + panOffsetY / wheelZoom) * wheelZoom
294
+ );
295
+
296
+ offsetRectX = mouseX - canvasMinRectX;
297
+ offsetRectY = mouseY - canvasMinRectY;
298
+
299
+ redDotOffsetX = (mouseX - canvasMaxRectX) / wheelZoom;
300
+ redDotOffsetY = (mouseY - canvasMaxRectY) / wheelZoom;
301
+ const distanceRedDot = Math.sqrt(
302
+ Math.pow(redDotOffsetX, 2) + Math.pow(redDotOffsetY, 2)
303
+ );
304
+
305
+ if (distanceRedDot < 10) {
306
+ console.log("Resizing");
307
+ selectedRect = i;
308
+ isResizing = true;
309
+ e.preventDefault();
310
+ } else if (
311
+ offsetRectX >= 0 &&
312
+ offsetRectY >= 0 &&
313
+ offsetRectX <= rect.width * wheelZoom + 1 &&
314
+ offsetRectY <= rect.height * wheelZoom + 1
315
+ ) {
316
+ console.log("Square selected: " + i);
317
+ selectedRect = i;
318
+ isDragging = true;
319
+ e.preventDefault();
320
+ break;
321
+ }
322
+ }
323
+
324
+ if (!isResizing && !isDragging) {
325
+ isPanning = true;
326
+ startX = mouseX - panOffsetX;
327
+ startY = mouseY - panOffsetY;
328
+ console.log("startX: " + startX + " startY: " + startY);
329
+ drawMap(true);
330
+ }
331
+
332
+ if (map?.OBSTACLES2) {
333
+ map?.OBSTACLES2?.forEach((obstacle) => {
334
+ const canvasX =
335
+ getOriginalX(obstacle[0] / 50) * zoomLevel * wheelZoom + panOffsetX;
336
+ const canvasY =
337
+ getOriginalY(obstacle[1] / 50) * zoomLevel * wheelZoom + panOffsetY;
338
+ const x = obstacle[0] / 50;
339
+ const y = obstacle[1] / 50;
340
+
341
+ const distance = Math.sqrt(
342
+ Math.pow(mouseX - canvasX, 2) + Math.pow(mouseY - canvasY, 2)
343
+ );
344
+ console.log(`distance: ${distance}`);
345
+
346
+ if (distance <= 5) {
347
+ selectedObstacleID = obstacle[6];
348
+
349
+ if (popupTimeout) {
350
+ clearTimeout(popupTimeout);
351
+ popupTimeout = null;
352
+ }
353
+ console.log(`obstacle coords x: ${x}, y: ${y}`);
354
+
355
+ popupX = x;
356
+ popupY = y;
357
+
358
+ const data = {};
359
+ data.duid = selectedElement.value;
360
+ data.command = "get_photo";
361
+ data.attribute = {
362
+ data_filter: {
363
+ img_id: selectedObstacleID,
364
+ type: 1, // 1 = small photo, 0 full photo
365
+ },
366
+ };
367
+
368
+ clearImageSource(popupImage);
369
+
370
+ triangle.style.left = "45px";
371
+ triangle.style.top = "100px";
372
+
373
+ popup.style.display = "block";
374
+
375
+ socket.send(JSON.stringify(data));
376
+ socket.onmessage = function (event) {
377
+ const serverData = JSON.parse(event.data);
378
+
379
+ if (
380
+ serverData.image &&
381
+ applySafeImageSource(popupImage, serverData.image)
382
+ ) {
383
+ socket.onmessage = null;
384
+
385
+ popupImage.addEventListener("click", function () {
386
+ console.log(`Request to large photo started!`);
387
+ const data = {};
388
+ data.duid = selectedElement.value;
389
+ data.command = "get_photo";
390
+ data.attribute = {
391
+ data_filter: {
392
+ img_id: selectedObstacleID,
393
+ type: 0, // 1 = small photo, 0 full photo
394
+ },
395
+ };
396
+ socket.send(JSON.stringify(data));
397
+
398
+ socket.onmessage = function (event) {
399
+ const serverData = JSON.parse(event.data);
400
+
401
+ if (
402
+ serverData.image &&
403
+ applySafeImageSource(largePhotoImage, serverData.image)
404
+ ) {
405
+ popup.style.display = "none";
406
+ clearImageSource(popupImage);
407
+ largePhoto.style.display = "block";
408
+
409
+ largePhotoImage.onclick = function () {
410
+ clearImageSource(largePhotoImage);
411
+ largePhoto.style.display = "none";
412
+ };
413
+ socket.onmessage = null;
414
+ }
415
+ };
416
+
417
+ setTimeout(() => {
418
+ clearImageSource(largePhotoImage);
419
+ largePhoto.style.display = "none";
420
+ }, 10000);
421
+ });
422
+ }
423
+ };
424
+
425
+ // Hide the popup after 10 seconds
426
+ timeoutStart = Date.now();
427
+ popupTimeout = setTimeout(() => {
428
+ popup.style.display = "none";
429
+ clearImageSource(popupImage);
430
+ socket.onmessage = null;
431
+ popupTimeout = null;
432
+ selectedObstacleID = null;
433
+ }, 10000);
434
+ updatePopupPosition();
435
+ isPanning = false;
436
+ }
437
+ });
438
+ }
439
+ drawMap(true);
440
+ }
441
+
442
+ canvas.addEventListener("mousedown", downEvent);
443
+ canvas.addEventListener("touchstart", downEvent);
444
+
445
+ function upEvent(e) {
446
+ isDragging = false;
447
+ isResizing = false;
448
+ isPanning = false;
449
+ if (rects.length > 0) {
450
+ updateRobotZones();
451
+ }
452
+
453
+ // This is needed to prevent the popup from hiding instantly after showing it
454
+ const elapsed = Date.now() - timeoutStart;
455
+ if (elapsed > 250) popup.style.display = "none";
456
+
457
+ e.preventDefault(false);
458
+ }
459
+ canvas.addEventListener("mouseup", upEvent);
460
+ canvas.addEventListener("touchend", upEvent);
461
+
462
+ canvas.addEventListener("mouseleave", () => {
463
+ isPanning = false;
464
+ });
465
+
466
+ let goToX = 0;
467
+ let goToY = 0;
468
+ async function moveEvent(e) {
469
+ const mouseX = mouseXtoCanvasX(e);
470
+ const mouseY = mouseYtoCanvasY(e);
471
+ if (isDragging) {
472
+ rects[selectedRect].x = roundTwoDecimals(
473
+ (mouseX - panOffsetX - offsetRectX) / wheelZoom
474
+ );
475
+ rects[selectedRect].y = roundTwoDecimals(
476
+ (mouseY - panOffsetY - offsetRectY) / wheelZoom
477
+ );
478
+ } else if (isResizing) {
479
+ rects[selectedRect].width = roundTwoDecimals(
480
+ (mouseX - panOffsetX - rects[selectedRect].x - redDotOffsetX) /
481
+ wheelZoom
482
+ );
483
+ rects[selectedRect].height = roundTwoDecimals(
484
+ (mouseY - panOffsetY - rects[selectedRect].y - redDotOffsetY) /
485
+ wheelZoom
486
+ );
487
+ } else if (goToTarget) {
488
+ goToX = (mouseX - panOffsetX) / wheelZoom;
489
+ goToY = (mouseY - panOffsetY) / wheelZoom;
490
+ } else if (isPanning) {
491
+ const deltaX = roundTwoDecimals(mouseX - startX);
492
+ const deltaY = roundTwoDecimals(mouseY - startY);
493
+
494
+ // Calculate the limits based on the map dimensions and zoom level
495
+ const minOffsetX = maxPanX - mapSizeX * zoomLevel * wheelZoom;
496
+ const maxOffsetX = maxPanX;
497
+ const minOffsetY = maxPanY - mapSizeY * zoomLevel * wheelZoom;
498
+ const maxOffsetY = maxPanY;
499
+
500
+ // Clamp the pan offsets within the defined boundaries
501
+ panOffsetX = clamp(deltaX, minOffsetX, maxOffsetX);
502
+ panOffsetY = clamp(deltaY, minOffsetY, maxOffsetY);
503
+
504
+ updatePopupPosition();
505
+ }
506
+ }
507
+ canvas.addEventListener("mousemove", moveEvent);
508
+ canvas.addEventListener("touchmove", moveEvent);
509
+
510
+ canvas.addEventListener("wheel", (e) => {
511
+ e.preventDefault();
512
+ const clientX = mouseXtoCanvasX(e);
513
+ const clientY = mouseYtoCanvasY(e);
514
+ const prevZoom = wheelZoom;
515
+
516
+ if (e.deltaY < 0) {
517
+ wheelZoom += zoomStep;
518
+ } else {
519
+ wheelZoom -= zoomStep;
520
+ }
521
+ wheelZoom =
522
+ roundTwoDecimals(Math.min(Math.max(wheelZoom, minZoom), maxZoom) * 100) /
523
+ 100;
524
+
525
+ panOffsetX = roundTwoDecimals(
526
+ clientX + panOffsetX - (clientX * wheelZoom) / prevZoom
527
+ );
528
+ panOffsetY = roundTwoDecimals(
529
+ clientY + panOffsetY - (clientY * wheelZoom) / prevZoom
530
+ );
531
+
532
+ updatePopupPosition();
533
+ drawMap(true);
534
+
535
+ if (rects.length > 0) {
536
+ updateRobotZones();
537
+ }
538
+
539
+ console.log(`wheelZoom: ${wheelZoom}`);
540
+ });
541
+
542
+ const deleteButton = document.getElementById("deleteButton");
543
+ deleteButton.addEventListener("click", function () {
544
+ if (rects[selectedRect]) {
545
+ rects.splice(selectedRect, 1);
546
+ console.log("rects.length: " + rects.length);
547
+ if (rects.length < 5) {
548
+ addButton.disabled = false;
549
+ }
550
+ if (rects.length < 1) {
551
+ deleteButton.disabled = true;
552
+ }
553
+ selectedRect = rects.length - 1;
554
+ drawMap(true);
555
+ }
556
+ });
557
+
558
+ const addButton = document.getElementById("addButton");
559
+ addButton.addEventListener("click", function () {
560
+ goToTarget = false;
561
+
562
+ const width = (25 * scaleFactor) / wheelZoom;
563
+ const height = (25 * scaleFactor) / wheelZoom;
564
+ const x = canvas.width / 2 - width / 2;
565
+ const y = canvas.height / 2 - height / 2;
566
+
567
+ rects.push({ x: x, y: y, width: width, height: height });
568
+ console.log("length: " + rects.length);
569
+ if (rects.length > 0) {
570
+ deleteButton.disabled = false;
571
+ }
572
+ if (rects.length > 4) {
573
+ addButton.disabled = true;
574
+ }
575
+ console.log("Square spawned at: " + x + ":" + y);
576
+ selectedRect = rects.length - 1;
577
+ drawMap(true);
578
+ updateRobotZones();
579
+ });
580
+
581
+ const startButton = document.getElementById("startButton");
582
+ startButton.addEventListener("click", function () {
583
+ const data = {};
584
+ data.duid = selectedElement.value;
585
+ if (zones.length > 0) {
586
+ data.command = "app_zoned_clean";
587
+ data.parameters = zones;
588
+ } else {
589
+ data.command = "app_start";
590
+ }
591
+ console.log("Zones to start with: " + JSON.stringify(zones));
592
+ socket.send(JSON.stringify(data));
593
+ rects = [];
594
+ drawMap(true);
595
+
596
+ startButton.style.display = "none";
597
+ pauseButton.style.display = "inline-block";
598
+ });
599
+
600
+ const pauseButton = document.getElementById("pauseButton");
601
+ pauseButton.addEventListener("click", function () {
602
+ const data = {};
603
+ data.duid = selectedElement.value;
604
+ data.command = "app_pause";
605
+ socket.send(JSON.stringify(data));
606
+
607
+ startButton.style.display = "inline-block";
608
+ pauseButton.style.display = "none";
609
+ });
610
+
611
+ const stop = document.getElementById("stopButton");
612
+ stop.addEventListener("click", function () {
613
+ data.duid = selectedElement.value;
614
+ console.log(map);
615
+ data.command = "app_stop";
616
+ socket.send(JSON.stringify(data));
617
+
618
+ startButton.style.display = "inline-block";
619
+ pauseButton.style.display = "none";
620
+ });
621
+
622
+ const dock = document.getElementById("dockButton");
623
+ dock.addEventListener("click", function () {
624
+ const data = {};
625
+ data.duid = selectedElement.value;
626
+ data.command = "app_charge";
627
+ socket.send(JSON.stringify(data));
628
+ });
629
+
630
+ const goTo = document.getElementById("goToButton");
631
+ goTo.addEventListener("click", function () {
632
+ goToTarget = true;
633
+ goToPin.src = go_to_pin_image;
634
+ drawMap(true);
635
+ });
636
+
637
+ const resetZoom = document.getElementById("resetZoomButton");
638
+ resetZoom.addEventListener("click", function () {
639
+ panOffsetX = 0;
640
+ panOffsetY = 0;
641
+ wheelZoom = 1;
642
+ updatePopupPosition();
643
+ drawMap(true);
644
+ });
645
+
646
+ const image = new Image();
647
+ const tempCanvas = document.createElement("canvas");
648
+
649
+ async function drawBackgroundImage() {
650
+ // console.log("mapBase64:" + mapBase64);
651
+ image.src = mapBase64;
652
+ image.onload = await function () {
653
+ const tempCtx = tempCanvas.getContext("2d", { willReadFrequently: true });
654
+
655
+ let mapMaxX = 0,
656
+ mapMaxY = 0;
657
+ mapMinX = image.width;
658
+ mapMinY = image.height;
659
+
660
+ tempCanvas.width = image.width;
661
+ tempCanvas.height = image.height;
662
+ tempCtx.drawImage(image, 0, 0);
663
+
664
+ // Get the image data and calculate the actual dimensions of the image
665
+ const imageData = tempCtx.getImageData(0, 0, image.width, image.height);
666
+ const pixels = imageData.data;
667
+ for (let i = 0; i < pixels.length; i += 4) {
668
+ const alpha = pixels[i + 3];
669
+ if (alpha > 0) {
670
+ // Check if the alpha value is non-zero
671
+ const x = (i / 4) % image.width;
672
+ const y = Math.floor(i / 4 / image.width);
673
+
674
+ mapMinX = Math.min(mapMinX, x);
675
+ mapMinY = Math.min(mapMinY, y);
676
+ mapMaxX = Math.max(mapMaxX, x);
677
+ mapMaxY = Math.max(mapMaxY, y);
678
+ }
679
+ }
680
+ // Add some padding to the map
681
+ mapMinX--;
682
+ mapMinY--;
683
+ mapMaxX++;
684
+ mapMaxY++;
685
+
686
+ mapSizeX = mapMaxX - mapMinX;
687
+ mapSizeY = mapMaxY - mapMinY;
688
+
689
+ const aspectRatio = canvas.width / canvas.height;
690
+
691
+ const contentAspectRatio = mapSizeX / mapSizeY;
692
+
693
+ if (contentAspectRatio > aspectRatio) {
694
+ console.log(
695
+ "Aspect ratio is greater than canvas aspect ratio mapSizeX: " +
696
+ mapSizeX +
697
+ " mapSizeY: " +
698
+ mapSizeY
699
+ );
700
+ zoomLevel = Math.round((canvas.width * 100) / mapSizeX) / 100;
701
+ } else {
702
+ console.log(
703
+ "Aspect ratio is less than canvas aspect ratio mapSizeX: " +
704
+ mapSizeX +
705
+ " mapSizeY: " +
706
+ mapSizeY
707
+ );
708
+ zoomLevel = Math.round((canvas.height * 100) / mapSizeY) / 100;
709
+ }
710
+ console.log("zoomLevel: " + zoomLevel);
711
+
712
+ drawMap(true);
713
+ };
714
+ }
715
+
716
+ function updateRobotZones() {
717
+ // console.log("rect.x : " + rect.x + " rect.y: " + rect.y);
718
+ // console.log("rect.x : " + rect.x + " rect.y: " + rect.y);
719
+
720
+ // console.log("canvasOffsetX : " + canvasOffsetX + " canvasOffsetY: " + canvasOffsetY);
721
+ // console.log("panOffsetX : " + panOffsetX + " panOffsetY: " + panOffsetY);
722
+
723
+ // console.log("x : " + getX(rect.x) + " y: " + getY(rect.y));
724
+ // console.log("rect.x : " + rect.x + " rect.y: " + rect.y);
725
+
726
+ zones = [];
727
+ for (const rect in rects) {
728
+ const zone = [];
729
+
730
+ // const x = canvasXtoMapX(rects[rect].x);
731
+ const x = rectXtoRobotX(rects[rect].x);
732
+ const y = rectYtoRobotY(rects[rect].y);
733
+ const xMax = rectXtoRobotX(rects[rect].x + rects[rect].width);
734
+ const yMax = rectYtoRobotY(rects[rect].y + rects[rect].height);
735
+
736
+ localCoordsToRobotCoords({ x: x, y: y }).then((coords1) => {
737
+ console.log("coords1.x : " + coords1.x + " coords1.y: " + coords1.y);
738
+ localCoordsToRobotCoords({ x: xMax, y: yMax }).then((coords2) => {
739
+ console.log("coords2.x : " + coords2.x + " coords2.y: " + coords2.y);
740
+
741
+ zone.push(Math.min(coords1.x, coords2.x));
742
+ zone.push(Math.min(coords1.y, coords2.y));
743
+ zone.push(Math.max(coords1.x, coords2.x));
744
+ zone.push(Math.max(coords1.y, coords2.y));
745
+ zone.push(parseInt(document.getElementById("cleanCount").value)); // clean count
746
+ zones.push(zone);
747
+ // console.log("zone: " + JSON.stringify(zone));
748
+ console.log("Zones length: " + zones.length);
749
+ console.log("Zones to start with: " + JSON.stringify(zones));
750
+ });
751
+ });
752
+ }
753
+ }
754
+
755
+ function updatePopupPosition() {
756
+ if (popupTimeout) {
757
+ const x = getOriginalX(popupX) * zoomLevel * wheelZoom + panOffsetX;
758
+ const y = getOriginalY(popupY) * zoomLevel * wheelZoom + panOffsetY;
759
+
760
+ popup.style.left = `${x - parseInt(popupImage.style.width, 10) / 2 + 10}px`;
761
+ popup.style.top = `${y - parseInt(popupImage.style.height, 10)}px`;
762
+ }
763
+ }
764
+
765
+ // Create a temporary canvas to draw the truncated image
766
+ const truncatedCanvas = document.createElement("canvas");
767
+ let rafId = null;
768
+ function drawMap(manualDraw = false) {
769
+ if (manualDraw || isDragging || isResizing || isPanning || goToTarget) {
770
+ truncatedCanvas.width = mapSizeX;
771
+ truncatedCanvas.height = mapSizeY;
772
+ const truncatedCtx = truncatedCanvas.getContext("2d");
773
+ truncatedCtx.drawImage(
774
+ image,
775
+ mapMinX,
776
+ mapMinY,
777
+ mapSizeX,
778
+ mapSizeY,
779
+ 0,
780
+ 0,
781
+ mapSizeX,
782
+ mapSizeY
783
+ );
784
+
785
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
786
+ ctx.save();
787
+ ctx.translate(panOffsetX, panOffsetY);
788
+ ctx.scale(wheelZoom * zoomLevel, wheelZoom * zoomLevel);
789
+
790
+ ctx.drawImage(truncatedCanvas, 0, 0, mapSizeX, mapSizeY);
791
+
792
+ for (let i = 0; i < rects.length; i++) {
793
+ const rect = rects[i];
794
+
795
+ ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
796
+ ctx.fillRect(
797
+ rect.x / zoomLevel,
798
+ rect.y / zoomLevel,
799
+ rect.width / zoomLevel,
800
+ rect.height / zoomLevel
801
+ );
802
+ ctx.lineWidth = 5;
803
+ ctx.strokeStyle = "rgba(255, 255, 255, 1)";
804
+ ctx.strokeRect(
805
+ rect.x / zoomLevel,
806
+ rect.y / zoomLevel,
807
+ rect.width / zoomLevel,
808
+ rect.height / zoomLevel
809
+ );
810
+
811
+ ctx.fillStyle = "red";
812
+ ctx.beginPath();
813
+ ctx.arc(
814
+ (rect.x + rect.width) / zoomLevel,
815
+ (rect.y + rect.height) / zoomLevel,
816
+ 10,
817
+ 0,
818
+ 2 * Math.PI
819
+ );
820
+ ctx.fill();
821
+
822
+ console.log("Square position: " + JSON.stringify(rect));
823
+ }
824
+
825
+ const goToPosX = (goToX - 8) / zoomLevel;
826
+ const goToPosY = (goToY - 12) / zoomLevel;
827
+ const goToSizeX = goToPin.width / 2;
828
+ const goToSizeY = goToPin.height / 2;
829
+
830
+ ctx.drawImage(goToPin, goToPosX, goToPosY, goToSizeX, goToSizeY);
831
+
832
+ ctx.restore();
833
+
834
+ // this is needed to update the canvas only when needed
835
+ if (!rafId) {
836
+ rafId = requestAnimationFrame(() => {
837
+ rafId = null;
838
+ drawMap();
839
+ });
840
+ }
841
+ }
842
+ }
843
+
844
+ function roundTwoDecimals(number) {
845
+ return Math.round(number * 100) / 100;
846
+ }
847
+
848
+ function clamp(value, min, max) {
849
+ return Math.min(Math.max(value, min), max);
850
+ }
851
+ };