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