homebridge-roborock-vacuum 0.1.0 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-vacuum",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Roborock Vacuum Cleaner - plugin for Homebridge.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -35,7 +35,6 @@
35
35
  "abstract-things": "0.9.0",
36
36
  "axios": "^1.7.7",
37
37
  "binary-parser": "^2.2.1",
38
- "canvas": "^2.11.2",
39
38
  "chalk": "4.1.2",
40
39
  "crc-32": "^1.2.2",
41
40
  "debug": "4.3.5",
@@ -1,4 +1,4 @@
1
1
  {
2
- "val": "{\"uid\":1466357,\"tokentype\":\"\",\"token\":\"d8fe0bd712334ee585bc2145140f07d1-1JZxT5AINkUhb5JDyL8S+w==\",\"rruid\":\"rr6238c899155830\",\"region\":\"us\",\"countrycode\":\"886\",\"country\":\"TW\",\"nickname\":\"tasict\",\"rriot\":{\"u\":\"1RQsQJ2o8bxMCo6F45pICu\",\"s\":\"cPt6Ps\",\"h\":\"Gm0QssB0Qa\",\"k\":\"R3Z3cNUh\",\"r\":{\"r\":\"US\",\"a\":\"https://api-us.roborock.com\",\"m\":\"ssl://mqtt-us.roborock.com:8883\",\"l\":\"https://wood-us.roborock.com\"}},\"tuyaDeviceState\":0,\"avatarurl\":\"https://files.roborock.com/iottest/default_avatar.png\"}",
2
+ "val": "{\"uid\":1466357,\"tokentype\":\"\",\"token\":\"d8fe0bd712334ee585bc2145140f07d1-1JZxT5AINkUhb5JDyL8S+w==\",\"rruid\":\"rr6238c899155830\",\"region\":\"us\",\"countrycode\":\"886\",\"country\":\"TW\",\"nickname\":\"tasict\",\"rriot\":{\"u\":\"1RQsQJ2o8bxMCo6F45pICu\",\"s\":\"l5UDP7\",\"h\":\"7h2O8mNvkt\",\"k\":\"kxF1J95i\",\"r\":{\"r\":\"US\",\"a\":\"https://api-us.roborock.com\",\"m\":\"ssl://mqtt-us.roborock.com:8883\",\"l\":\"https://wood-us.roborock.com\"}},\"tuyaDeviceState\":0,\"avatarurl\":\"https://files.roborock.com/iottest/default_avatar.png\"}",
3
3
  "ack": true
4
4
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const rrMessage = require("./message").message;
4
4
  const RRMapParser = require("./RRMapParser");
5
- const MapCreator = require("./mapCreator");
6
5
  const fs = require("fs");
7
6
  const zlib = require("zlib");
8
7
 
@@ -36,7 +35,6 @@ class vacuum {
36
35
  this.message = new rrMessage(this.adapter);
37
36
 
38
37
  this.mapParser = new RRMapParser(this.adapter);
39
- this.mapCreator = new MapCreator(this.adapter);
40
38
 
41
39
  this.parameterFolders = {
42
40
  get_mop_mode: "deviceStatus",
@@ -50,94 +48,10 @@ class vacuum {
50
48
  };
51
49
  }
52
50
 
53
- async getMap(duid) {
54
- if (this.adapter.config.enable_map_creation) {
55
- this.adapter.log.debug(`Update map`);
56
-
57
- try {
58
- // const map = await connector.sendRequest(duid, "get_map_v1", [], true);
59
- // const map = await this.adapter.rr_mqtt_connector.sendRequest(duid, "get_map_v1", [], true);
60
- const map = await this.adapter.messageQueueHandler.sendRequest(duid, "get_map_v1", [], true);
61
- // this.adapter.log.debug(`Map received: ${map}`);
62
- if (map != "retry") {
63
- const mappedRooms = await this.adapter.messageQueueHandler.sendRequest(duid, "get_room_mapping", []);
64
-
65
- // const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_status", []);
66
- const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_prop", ["get_status"]);
67
- const selectedMap = deviceStatus[0].map_status >> 2 ?? -1; // to get the currently selected map perform bitwise right shift
68
-
69
- // This is for testing and debugging maps. This can't be stored in a state.
70
- zlib.gzip(map, (error, buffer) => {
71
- if (error) {
72
- this.adapter.log.error(`Error compressing map to gz ${error}`);
73
- } else {
74
- fs.writeFile("./test.rrmap.gz", buffer, (error) => {
75
- if (error) {
76
- this.adapter.log.error(`Error writing map file ${error}`);
77
- }
78
- });
79
- }
80
- });
81
-
82
- const parsedData = await this.mapParser.parsedata(map);
83
-
84
- const [mapBase64, mapBase64Truncated] = this.mapCreator.canvasMap(parsedData, duid, selectedMap, mappedRooms);
85
-
86
- await this.adapter.setStateAsync(`Devices.${duid}.map.mapData`, { val: JSON.stringify(parsedData), ack: true });
87
- await this.adapter.setStateAsync(`Devices.${duid}.map.mapBase64`, { val: mapBase64, ack: true });
88
- await this.adapter.setStateAsync(`Devices.${duid}.map.mapBase64Truncated`, { val: mapBase64Truncated, ack: true });
89
-
90
- // Send current map with Scale factor
91
- const mapToSend = {
92
- duid: duid,
93
- command: "map",
94
- base64: mapBase64,
95
- map: parsedData,
96
- scale: this.adapter.config.map_scale,
97
- };
98
-
99
- if (this.adapter.socket != null) {
100
- this.adapter.socket.send(JSON.stringify(mapToSend));
101
- }
102
- }
103
- } catch (error) {
104
- this.adapter.catchError(error, "get_map_v1", duid), this.robotModel;
105
- }
106
- }
107
- }
108
-
109
- async getCleaningRecordMap(duid, startTime) {
110
- try {
111
- const cleaningRecordMap = await this.adapter.messageQueueHandler.sendRequest(duid, "get_clean_record_map", { start_time: startTime }, true);
112
- const parsedData = await this.mapParser.parsedata(cleaningRecordMap);
113
- const [mapBase64, mapBase64Truncated] = this.mapCreator.canvasMap(parsedData, duid);
114
-
115
- return {
116
- mapBase64: mapBase64,
117
- mapBase64Truncated: mapBase64Truncated,
118
- mapData: JSON.stringify(parsedData),
119
- };
120
- } catch (error) {
121
- this.adapter.catchError(error, "get_clean_record_map", duid, this.robotModel);
122
-
123
- return null;
124
- }
125
- }
126
51
 
127
52
  async command(duid, parameter, value) {
128
53
  try {
129
54
  switch (parameter) {
130
- case "load_multi_map": {
131
- const result = await this.adapter.messageQueueHandler.sendRequest(duid, "load_multi_map", value);
132
-
133
- if (result[0] == "ok") {
134
- await this.getMap(duid).then(async () => {
135
- await this.getParameter(duid, "get_room_mapping");
136
- });
137
- }
138
-
139
- break;
140
- }
141
55
  case "app_segment_clean": {
142
56
  this.adapter.log.debug("Start room cleaning");
143
57
 
@@ -304,7 +218,6 @@ class vacuum {
304
218
 
305
219
  if (mapFromCommand && mapFromCommand.val != currentMap) {
306
220
  await this.adapter.setStateAsync(`Devices.${duid}.commands.load_multi_map`, currentMap, true);
307
- await this.adapter.vacuums[duid].getMap(duid);
308
221
  }
309
222
  }
310
223
  }
@@ -549,13 +462,6 @@ class vacuum {
549
462
  });
550
463
  }
551
464
 
552
- if (this.adapter.config.enable_map_creation == true) {
553
- const mapArray = await this.getCleaningRecordMap(duid, cleaningAttributes[cleaningAttribute][cleaningRecord]);
554
- for (const mapType in mapArray) {
555
- const val = mapArray[mapType];
556
- this.adapter.setStateAsync(`Devices.${duid}.cleaningInfo.records.${cleaningRecord}.map.${mapType}`, { val: val, ack: true });
557
- }
558
- }
559
465
  }
560
466
 
561
467
  const objectString = `Devices.${duid}.cleaningInfo.JSON`;
@@ -1,692 +0,0 @@
1
- "use strict";
2
-
3
- const { createCanvas } = require("canvas");
4
- const { Image } = require("canvas");
5
-
6
- // Farben ändern
7
- const colors = {
8
- floor: "#23465e",
9
- obstacle: "#2b2e30",
10
- path: "rgba(255,255,255,0.5)",
11
- newmap: true,
12
- };
13
- const orgcolors = [
14
- "#017E82",
15
- "#BD7B00",
16
- "#C05A41",
17
- "#4579B5",
18
- "#434242", // wall
19
- "#dfdfdf", // desel floor
20
- ];
21
- const offset = 60;
22
-
23
- const robot =
24
- "data:image/png;base64,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";
25
- const robot1 =
26
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAfCAMAAAHGjw8oAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADbUExURQAAAICAgICAgICAgICAgICAgHx8fH19fX19fYCAgIGBgX5+foCAgH5+foCAgH9/f39/f35+foCAgH9/f39/f4CAgH5+foGBgYCAgICAgIGBgX9/f39/f35+foCAgH9/f39/f4CAgIODg4eHh4mJiZCQkJycnJ2dnZ6enqCgoKSkpKenp62trbGxsbKysry8vL29vcLCwsXFxcbGxsvLy87OztPT09XV1d/f3+Tk5Ojo6Ozs7O3t7e7u7vHx8fLy8vPz8/X19fb29vf39/j4+Pn5+f39/f7+/v///9yECocAAAAgdFJOUwAGChgcKCkzOT5PVWZnlJmfsLq7wcrS1Nre4OXz+vr7ZhJmqwAAAAlwSFlzAAAXEQAAFxEByibzPwAAAcpJREFUKFNlkolaWkEMhYPggliBFiwWhGOx3AqCsggI4lZt8/5P5ElmuEX5P5hMMjeZJBMRafCvUKnbIqpcioci96owTQWqP0QKC54nImUAyr9k7VD1me4YvibHlJKpVUzQhR+dmdTRSDUvdHh8NK8nhqUVch7cITmXA3rtYDmH+3OL4XI1T+BhJUcXczQxOBXJuve0/daeUr5A6g9muJzo5NI2kPKtyRSGBStKQZ5RC1hENWn6NSRTrDUqLD/lsNKoFTNRETlGMn9dDoGdoDcT1fHPi7EuUDD9dMBw4+6vMQVyInnPXDsdW+8tjWfbYTbzg/OstcagzSlb0+wL/6k+1KPhCrj6YFhzS5eXuHcYNF4bsGtDYhFLTOSMqTsx9e3iyKfynb1SK+RqtEq70RzZPwEGKwv7G0OK1QA42Y+HIgct9P3WWG9ItI/mQTgvoeuWAMdlTRclO/+Km2jwlhDvinGNbyJH6EWV84AJ1wl8JowejqTqTmv+0GqDmVLlg/wLX5Mp2rO3WRs2Zs5fznAVd1EzRh10OONr7hhhM4ctevhiVVxHdYsbq+JzHzaIfdjs5CZ9tGInSfoWEXuL7//fwtn9+Jp7wSryDjBFqnOGeuUxAAAAAElFTkSuQmCC";
27
- const charger =
28
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAdVBMVEUAAAA44Yo44Yo44Yo44Yo44Yo44Yo44Yo44Yp26q844Yr///9767Kv89DG9t2g8Md26q5C44/5/vvz/fjY+ei19NNV5ZtJ45T2/fmY78KP7r1v6atq6Kjs/PPi+u7e+uvM9+Gb8MSS7r+H7bhm6KVh56JZ5p3ZkKITAAAACnRSTlMABTr188xpJ4aepd0A4wAAANZJREFUKM9VklmCgzAMQwkQYCSmLKWl2+zL/Y9YcIUL7wvkJHIUJyKkVcyy+JIGCZILGF//QLEqlTmMdsBEXi56igfH/QVGqvXSu49+1KftCbn+dtxB5LOPfNGQNRaKaQNkTJ46OMGczZg8wJB/9TB+J3nFkyqJMp44vBrnWYhJJmOn/5uVzAotV/zACnbUtTbOpHcQzVx8kxw6mavdpYP90dsNcE5k6xd8RoIb2Xgk6xAbfm5C9NiHtxGiXD/U2P96UJunrS/LOeV2GG4wfBi241P5+NwBnAEUFx9FUdUAAAAASUVORK5CYII=";
29
- const tank =
30
- "data:image/png;base64,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";
31
- const spaceship =
32
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAFMklEQVRYR+2YfUxVZRjAf+cAfiAOHZaIFxXD0g2V3LVipZFuOrRcm6KBHw0b/GHERpFGfoQokcV0M6x5mbiKyFK31NRls666RmUrP0gtUVQU8IP8AEGBe297zznXe+71fsBB+4v3D8Z97/M85/c+X+9zj8SDW7eBUJ25XsDdrpqXumpAp29jSoZMTDxsXCS2+wJNXbX/YAFTVsqMeg5WTO4GNBIZG90eNOI3l063B434zwK8oCnGkrISXRWfARzAT0CGEeNCp6ttppHIYWFExYIkQc4mCOsHeclw6wY01MC/taIXip5oaHUdMPmtMBa85/7w+jq4egX2bQRrWTegv9A00u1BQ6l7T6nbgx2tYiGXCsia7yqAKqAzHowFEjR9O1Cu9Um/Mewo4ETgQJAsYXc4cDi4CER3ErBGkjDJkoTNLvo3zwMHA2VYRwGFMaujLonk9D/Z9l29IcBZL0aatpY8iTRor+BKFId+oIDj48Opqm7m+s02Q4D9w0NMsTGhHD5y8+EAYhoJDZegpdEQIL37mogYDBdPPSTA9w9B+XKotBoDjEs0kboK3p3QacBSYIEuHyZ5JLCSg/gDnPkotLeqJuRgyN/veRfX4B9QFOKPOobPgYXOIjk8tFc/85LoZ8ms2iMqdSGwWSfsH3D+CpgRzkcrRioqb+efgoKD8INFPywEAkyTJam0OHYaa2p+5vydG78D4+8BxodFmnePnsuQX9Zhc9gDAV4CHgOuKXexBlhVIc4BsQkHPAEHAGeISxzsJ8RpQZJceuGZbKYf/5IjTfXugIBZ5zHvgJZTsHkJVOxwic5dCnMWKx50A7SchN2fwo71Ltn4qTB7ma8cTANEqjmXB2DUE2bS18OqaWC3eQfcdUtVbm50mQnti+jc9wHuvKkOsXrZM2dVPe9FkoYcVMryPVCSBbV/ewCaRppZVALLEvWAInHF64zRwIc4AZ14l8/DpdMqYN5MNq0TYvBa9nHI264CDh4BA4eqGseP6gEXix1xXK0gVcDVVvgkXbSigIBlQKvyELFCesL2K7oIALOjoKVJERGM92QdDtde7zD4plbVa9Ve1aREu/5XfrbQA5hnDHBLDfQJdwdzfkoeRG56JAW5jyNH7QWL5qGMsdhrk1ha+A+FJfWwtc5d32aDE5VwpwnykwwBVgLi2FMIADh8oIMRMaF8b73mBjg1cQCnq5s5e1nqCOA+IAo5KM53iEeMM7PWCgWpcP6EeuLgECj+FWTnlOXhyOIsOKYNJELmYzGFAW8kgF1MVMCYiZCpq2S9CSGT+TS0t6m7EdGQutpHDjoBvQfz/9mtOg0tzT4AJcmseEy/JBm+vgjBIoe9rJxJcPaY9oUEX4gZFpgvZlMl+WH4GCjS32CauMhD4bnUIeDQvK1UGtDeLvTdqljcCi97IAQBawLl4ISeA5jcP4a8c1a3HMwblsj+69UcunutIzm4BLB5PP9bcfv4G1iFO1spP+e7iueYyIqII3fIBAZVFLkB1iXkUHjhEOsbKtUo6JdSxX+pVbx6uvhGhEhLRI8g+kmuYF9KPnWKtILJEf29U0s4Q8T1vhVo5I8QLdpDyxLZI+yl/WNf5ak/LNy2tYkS/UBpSzlbVNGiV8TfKOCdPkEhWb+Ny2Dy0c+ob23a5eVFkvBcg6/jBAL0prf1kZA+s8pHzWRG5Ve02NsKgaWAnXkFqnyZ+Kj8AizoLYfk7oxLIfXkdq623d4GJHfGt0YA1wLZuodkAhsUQNfbMlGOAvB1oFgnuw54szOA/wGAoJRHR2vq3wAAAABJRU5ErkJggg==";
33
- const robot2 =
34
- "data:image/png;base64,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";
35
- const originalRobot =
36
- "data:image/png;base64,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";
37
- const go_to_pin =
38
- "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=";
39
-
40
- //const ctximg = canvasImg.getContext('2d');
41
-
42
- const img = new Image(); // Create a new Image
43
- img.src = originalRobot;
44
-
45
- const img_charger = new Image();
46
- img_charger.src = charger;
47
-
48
- const obstacleTitles = {
49
- 0: "Wire",
50
- 1: "Pet waste",
51
- 2: "Footwear",
52
- 3: "Pedestal",
53
- 4: "Pedestal",
54
- 5: "Power strip",
55
- 9: "Scale",
56
- 10: "Fabric",
57
- 18: "Dustpan",
58
- 25: "Dustpan",
59
- 26: "Bar",
60
- 27: "Bar",
61
- };
62
-
63
- class MapCreator {
64
- constructor(adapter) {
65
- this.adapter = adapter;
66
- this.scaleFactor = adapter.config.map_scale;
67
- }
68
-
69
- getX(dimensions, px) {
70
- return (px * this.scaleFactor) % dimensions.width;
71
- }
72
- getY(dimensions, px) {
73
- return dimensions.height - Math.floor(px / (dimensions.width / this.scaleFactor)) * this.scaleFactor - this.scaleFactor;
74
- }
75
-
76
- robotXtoPixelX(image, robotCoord) {
77
- return (robotCoord - image.position.left) * this.scaleFactor - 2;
78
- }
79
- robotYtoPixelY(image, robotCoord) {
80
- return (image.dimensions.height / this.scaleFactor + image.position.top - robotCoord) * this.scaleFactor - 2;
81
- }
82
-
83
- rotateCanvas(img, angleInDegrees) {
84
- const canvasImg = createCanvas(img.width, img.height);
85
- const ctx = canvasImg.getContext("2d");
86
- const angleOffset = 90;
87
- let angleInRadians = ((angleInDegrees - angleOffset) * Math.PI) / 180;
88
- angleInRadians = ((angleInRadians + Math.PI) % (2 * Math.PI)) - Math.PI; // Normalize angle to -π to π
89
-
90
- ctx.translate(img.width / 2, img.height / 2);
91
- ctx.rotate(-angleInRadians);
92
- ctx.translate(-img.width / 2, -img.height / 2);
93
- ctx.drawImage(img, 0, 0);
94
-
95
- return canvasImg;
96
- }
97
-
98
- drawLineBresenham(imageData, x1, y1, x2, y2) {
99
- const pixels = imageData.data;
100
-
101
- const dx = Math.abs(x2 - x1);
102
- const dy = Math.abs(y2 - y1);
103
- const sx = x1 < x2 ? 1 : -1;
104
- const sy = y1 < y2 ? 1 : -1;
105
- let err = dx - dy;
106
-
107
- for(;;) {
108
- // Setze Pixel im ImageData
109
- if (x1 >= 0 && x1 < imageData.width && y1 >= 0 && y1 < imageData.height) { // handle out of bounds. lineto would already do this but we need to set pixels directly
110
- const index = (x1 + y1 * imageData.width) * 4;
111
- pixels[index] = 128; // r
112
- pixels[index + 1] = 128; // g
113
- pixels[index + 2] = 128; // b
114
- pixels[index + 3] = 128; // a
115
- }
116
-
117
- if (x1 === x2 && y1 === y2) break;
118
- const e2 = 2 * err;
119
- if (e2 > -dy) {
120
- err -= dy;
121
- x1 += sx;
122
- }
123
- if (e2 < dx) {
124
- err += dx;
125
- y1 += sy;
126
- }
127
- }
128
- }
129
-
130
- areRoomsAdjacent(roomA, roomB) {
131
- const horizontalOverlap = roomA.minX <= roomB.maxX && roomA.maxX >= roomB.minX;
132
- const verticalOverlap = roomA.minY <= roomB.maxY && roomA.maxY >= roomB.minY;
133
- return horizontalOverlap && verticalOverlap;
134
- }
135
-
136
- hexToRGBA(hex, alpha = 1) {
137
- const r = parseInt(hex.slice(1, 3), 16);
138
- const g = parseInt(hex.slice(3, 5), 16);
139
- const b = parseInt(hex.slice(5, 7), 16);
140
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
141
- }
142
-
143
- canvasMap(mapdata, duid, selectedMap, mappedRooms, options) {
144
- if (options) {
145
- colors.floor = options.FLOORCOLOR;
146
- colors.obstacle = options.WALLCOLOR;
147
- colors.path = options.PATHCOLOR;
148
- colors.newmap = options && options.newmap ? options.newmap : true;
149
- if (options.ROBOT === "robot") {
150
- img.src = robot;
151
- } else if (options.ROBOT === "robot1") {
152
- img.src = robot1;
153
- } else if (options.ROBOT === "tank") {
154
- img.src = tank;
155
- } else if (options.ROBOT === "spaceship") {
156
- img.src = spaceship;
157
- } else if (options.ROBOT === "robot2") {
158
- img.src = robot2;
159
- } else if (options.ROBOT === "originalRobot") {
160
- img.src = originalRobot;
161
- }
162
- }
163
- let maxtop = 0;
164
- let maxleft = 0;
165
- let minleft = 0;
166
- let mintop = 0;
167
-
168
- mapdata.IMAGE.dimensions.width = mapdata.IMAGE.dimensions.width * this.scaleFactor;
169
- mapdata.IMAGE.dimensions.height = mapdata.IMAGE.dimensions.height * this.scaleFactor;
170
-
171
- const canvas = createCanvas(mapdata.IMAGE.dimensions.width, mapdata.IMAGE.dimensions.height);
172
- const ctx = canvas.getContext("2d");
173
-
174
- if (mapdata.IMAGE.pixels.floor && mapdata.IMAGE.pixels.floor.length !== 0) {
175
- if (typeof mapdata.IMAGE.pixels.floor[0] === "number") {
176
- // init min
177
- minleft = mapdata.IMAGE.pixels.floor[0] % mapdata.IMAGE.dimensions.width;
178
- mintop = mapdata.IMAGE.dimensions.height - 1 - Math.floor(mapdata.IMAGE.pixels.floor[0] / mapdata.IMAGE.dimensions.width);
179
-
180
- ["floor", "obstacle"].forEach((key) => {
181
- ctx.beginPath();
182
- mapdata.IMAGE.pixels[key].forEach((px) => {
183
- if (key === "obstacle") {
184
- ctx.fillStyle = colors.newmap ? orgcolors[4] : colors.obstacle;
185
- } else {
186
- ctx.fillStyle = colors.newmap ? orgcolors[5] : colors.floor;
187
- }
188
- //ctx.fillStyle = colors[key];
189
- ctx.rect(this.getX(mapdata.IMAGE.dimensions, px), this.getY(mapdata.IMAGE.dimensions, px), this.scaleFactor, this.scaleFactor);
190
-
191
- maxtop = Math.max(maxtop, this.getY(mapdata.IMAGE.dimensions, px));
192
- maxleft = Math.max(maxleft, this.getX(mapdata.IMAGE.dimensions, px));
193
- minleft = Math.min(minleft, this.getX(mapdata.IMAGE.dimensions, px));
194
- mintop = Math.min(mintop, this.getY(mapdata.IMAGE.dimensions, px));
195
- });
196
- ctx.fill();
197
- });
198
- }
199
- }
200
-
201
- // Zeichne Alle Räume
202
- const segmentsData = {};
203
- const segmentsBounds = {};
204
- const assignedColors = {};
205
- const availableColors = [...orgcolors];
206
-
207
- if (mapdata.IMAGE.pixels.segments && !mapdata.CURRENTLY_CLEANED_BLOCKS && colors.newmap) {
208
- mapdata.IMAGE.pixels.segments.forEach((px) => {
209
- const segnum = (px >> 21);
210
- const x = this.getX(mapdata.IMAGE.dimensions, px & 0xfffff);
211
- const y = this.getY(mapdata.IMAGE.dimensions, px & 0xfffff);
212
-
213
- if (!segmentsData[segnum]) {
214
- segmentsData[segnum] = { points: [], minX: x, maxX: x, minY: y, maxY: y };
215
- } else {
216
- const segment = segmentsData[segnum];
217
- segment.points.push({ x, y });
218
- segment.minX = Math.min(segment.minX, x);
219
- segment.maxX = Math.max(segment.maxX, x);
220
- segment.minY = Math.min(segment.minY, y);
221
- segment.maxY = Math.max(segment.maxY, y);
222
- }
223
- });
224
-
225
- Object.keys(segmentsData).forEach(segnum => {
226
- const segment = segmentsData[segnum];
227
- segmentsBounds[segnum] = {
228
- minX: segment.minX,
229
- maxX: segment.maxX,
230
- minY: segment.minY,
231
- maxY: segment.maxY
232
- };
233
- });
234
-
235
- Object.keys(segmentsBounds).forEach(segnum => {
236
- const currentBounds = segmentsBounds[segnum];
237
- const adjacentSegments = Object.keys(segmentsBounds).filter(otherSegnum => {
238
- const otherBounds = segmentsBounds[otherSegnum];
239
- return segnum !== otherSegnum && this.areRoomsAdjacent(currentBounds, otherBounds);
240
- });
241
-
242
- const usedColors = adjacentSegments.map(adjSegnum => assignedColors[adjSegnum]);
243
- const availableColor = availableColors.find(color => !usedColors.includes(color));
244
-
245
- if (availableColor) {
246
- assignedColors[segnum] = availableColor;
247
- } else {
248
- assignedColors[segnum] = availableColors[Math.floor(Math.random() * availableColors.length)];
249
- }
250
- });
251
-
252
- Object.keys(segmentsData).forEach(segnum => {
253
- const segment = segmentsData[segnum];
254
- ctx.fillStyle = assignedColors[segnum] || availableColors[0];
255
- ctx.beginPath();
256
- segment.points.forEach(point => {
257
- ctx.rect(point.x, point.y, this.scaleFactor, this.scaleFactor);
258
- });
259
- ctx.fill();
260
- });
261
-
262
- ctx.closePath();
263
- }
264
-
265
- if (mapdata.CURRENTLY_CLEANED_BLOCKS && colors.newmap) {
266
- let segnum, lastcolor;
267
- ctx.beginPath();
268
- mapdata.IMAGE.pixels.segments.forEach((px) => {
269
- segnum = px >> 21;
270
- if (mapdata.CURRENTLY_CLEANED_BLOCKS.includes(segnum)) {
271
- if (segnum !== lastcolor) {
272
- ctx.fill();
273
- ctx.beginPath();
274
- ctx.fillStyle = orgcolors[segnum % 4];
275
- lastcolor = segnum;
276
- }
277
- px = px & 0xfffff;
278
- ctx.rect(this.getX(mapdata.IMAGE.dimensions, px), this.getY(mapdata.IMAGE.dimensions, px), this.scaleFactor, this.scaleFactor);
279
- }
280
- });
281
- ctx.fill();
282
- ctx.closePath();
283
- }
284
-
285
- // Zeichne Zonen active Zonen
286
- if (mapdata.CURRENTLY_CLEANED_ZONES) {
287
- if (mapdata.CURRENTLY_CLEANED_ZONES[0]) {
288
- ctx.beginPath();
289
- mapdata.CURRENTLY_CLEANED_ZONES.forEach((coord) => {
290
- ctx.fillStyle = "rgba(46,139,87,0.1)";
291
- ctx.fillRect(
292
- this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
293
- this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50),
294
- this.robotXtoPixelX(mapdata.IMAGE, coord[2] / 50) - this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
295
- this.robotYtoPixelY(mapdata.IMAGE, coord[3] / 50) - this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)
296
- );
297
- ctx.strokeStyle = "#2e8b57";
298
- ctx.lineWidth = 4;
299
- ctx.strokeRect(
300
- this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
301
- this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50),
302
- this.robotXtoPixelX(mapdata.IMAGE, coord[2] / 50) - this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50),
303
- this.robotYtoPixelY(mapdata.IMAGE, coord[3] / 50) - this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)
304
- );
305
- });
306
- }
307
- }
308
-
309
- // Zeichne Teppich
310
- if (mapdata.CARPET_MAP) {
311
- const offset = 8 * this.scaleFactor;
312
- ctx.fillStyle = "rgba(0,0,0,0.5)";
313
-
314
- const imageData = ctx.getImageData(0, 0, mapdata.IMAGE.dimensions.width, mapdata.IMAGE.dimensions.height);
315
- mapdata.CARPET_MAP.forEach((px) => {
316
- const x2 = this.getX(mapdata.IMAGE.dimensions, px) - offset;
317
- const y1 = this.getY(mapdata.IMAGE.dimensions, px);
318
- const x1 = x2 + this.scaleFactor - 1;
319
- const y2 = y1 + this.scaleFactor - 1;
320
-
321
- this.drawLineBresenham(imageData, x1, y1, x2, y2);
322
- });
323
-
324
- // Zeichne das ganze ImageData auf einmal
325
- ctx.putImageData(imageData, 0, 0);
326
- }
327
-
328
- // Male den Wischpfad
329
- if (mapdata.PATH && mapdata.MOP_PATH) {
330
- const mopOffset = -12; // i dont know why this offset?? Maybe the value from the end
331
- if (mapdata.PATH.points && mapdata.PATH.points.length !== 0) {
332
- let startX, startY; // this is needed to avoid weird spikes in sharp corners! don't remove this ever!
333
-
334
- ctx.beginPath();
335
- ctx.lineWidth = 7 * this.scaleFactor; // 7 makes the mop path look the same as on the Roborock app
336
- ctx.lineCap = "round";
337
- ctx.strokeStyle = "rgba(255,255,255,0.2)";
338
-
339
- mapdata.PATH.points.forEach((coord, index) => {
340
- if (mapdata.MOP_PATH && mapdata.MOP_PATH[index + mopOffset] !== 0) {
341
- if (mapdata.MOP_PATH[index - 1 + mopOffset] === 0) {
342
- startX = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
343
- startY = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
344
- ctx.moveTo(startX, startY);
345
- if (mapdata.MOP_PATH[index + mopOffset] !== 1) {
346
- // see value 9 and 12 in mop_path both in front of charger
347
- }
348
- } else if (mapdata.MOP_PATH[index - 1 + mopOffset] === 1) {
349
- ctx.moveTo(startX, startY);
350
- ctx.lineTo(this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50), this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50));
351
- startX = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
352
- startY = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
353
- }
354
- } else if (mapdata.MOP_PATH && mapdata.MOP_PATH[index + mopOffset] === 0) {
355
- if (mapdata.MOP_PATH[index - 1 + mopOffset] !== 0) {
356
- // do nothing ??
357
- }
358
- }
359
- });
360
- ctx.stroke();
361
- ctx.closePath();
362
- }
363
- }
364
-
365
- // Male den Pfad
366
- if (mapdata.PATH) {
367
- if (mapdata.PATH.points && mapdata.PATH.points.length !== 0) {
368
- ctx.fillStyle = colors.path;
369
- let first = true;
370
- let cold1, cold2;
371
-
372
- ctx.beginPath();
373
- mapdata.PATH.points.forEach((coord) => {
374
- if (first) {
375
- (cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)),
376
- (cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)),
377
- ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);
378
- first = false;
379
- } else {
380
- ctx.lineWidth = this.scaleFactor / 2;
381
- ctx.strokeStyle = colors.path;
382
-
383
- ctx.moveTo(cold1, cold2);
384
- (cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)), (cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)), ctx.lineTo(cold1, cold2);
385
- // ctx.stroke();
386
- }
387
- });
388
- ctx.stroke();
389
- ctx.closePath();
390
- }
391
- }
392
-
393
- // Male geplanten Pfad
394
- if (mapdata.GOTO_PREDICTED_PATH) {
395
- if (mapdata.GOTO_PREDICTED_PATH.points && mapdata.GOTO_PREDICTED_PATH.points.length !== 0) {
396
- let cold1, cold2;
397
- ctx.lineWidth = (3 * this.scaleFactor) / 2;
398
- ctx.strokeStyle = "rgba(255, 255, 255, 1)";
399
- ctx.setLineDash([3 * this.scaleFactor, 3 * this.scaleFactor]);
400
- ctx.lineCap = "round";
401
- ctx.beginPath();
402
- mapdata.GOTO_PREDICTED_PATH.points.forEach((coord, index) => {
403
- if (index === 0) {
404
- cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
405
- cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
406
- ctx.fillStyle = "rgba(255, 255, 255, 1)";
407
- ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);
408
- ctx.moveTo(cold1, cold2);
409
- } else {
410
- const newCold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
411
- const newCold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
412
- if (cold1 !== newCold1 || cold2 !== newCold2) {
413
- ctx.lineTo(newCold1, newCold2);
414
- cold1 = newCold1;
415
- cold2 = newCold2;
416
- }
417
- }
418
- });
419
- ctx.stroke();
420
- ctx.setLineDash([]);
421
- ctx.lineCap = "butt";
422
- }
423
- }
424
-
425
- if (mapdata.FORBIDDEN_ZONES) {
426
- mapdata.FORBIDDEN_ZONES.forEach((zone) => {
427
- const forbiddenMinX = Math.min(zone[0], zone[2], zone[4], zone[6]);
428
- const forbiddenMinY = Math.min(zone[1], zone[3], zone[5], zone[7]);
429
- const forbiddenMaxX = Math.max(zone[0], zone[2], zone[4], zone[6]);
430
- const forbiddenMaxY = Math.max(zone[1], zone[3], zone[5], zone[7]);
431
-
432
- const forbiddenSizeX = forbiddenMaxX - forbiddenMinX;
433
- const forbiddenSizeY = forbiddenMaxY - forbiddenMinY;
434
-
435
- ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
436
- ctx.fillRect(
437
- this.robotXtoPixelX(mapdata.IMAGE, forbiddenMinX / 50),
438
- this.robotYtoPixelY(mapdata.IMAGE, forbiddenMaxY / 50),
439
- (forbiddenSizeX / 50) * this.scaleFactor,
440
- (forbiddenSizeY / 50) * this.scaleFactor
441
- );
442
- ctx.lineWidth = (1 * this.scaleFactor) / 2;
443
- ctx.strokeStyle = "rgba(255, 0, 0, 1)";
444
- ctx.strokeRect(
445
- this.robotXtoPixelX(mapdata.IMAGE, forbiddenMinX / 50),
446
- this.robotYtoPixelY(mapdata.IMAGE, forbiddenMaxY / 50),
447
- (forbiddenSizeX / 50) * this.scaleFactor,
448
- (forbiddenSizeY / 50) * this.scaleFactor
449
- );
450
- });
451
- }
452
-
453
- if (mapdata.VIRTUAL_WALLS) {
454
- mapdata.VIRTUAL_WALLS.forEach((wall) => {
455
- const startX = this.robotXtoPixelX(mapdata.IMAGE, wall[0] / 50) + this.scaleFactor;
456
- const startY = this.robotYtoPixelY(mapdata.IMAGE, wall[1] / 50) + this.scaleFactor;
457
- const endX = this.robotXtoPixelX(mapdata.IMAGE, wall[2] / 50) + this.scaleFactor;
458
- const endY = this.robotYtoPixelY(mapdata.IMAGE, wall[3] / 50) + this.scaleFactor;
459
-
460
- // Calculate start end end of vector
461
- let vecX = endX - startX;
462
- let vecY = endY - startY;
463
-
464
- // Normalize vector
465
- const len = Math.sqrt(vecX * vecX + vecY * vecY);
466
- vecX /= len;
467
- vecY /= len;
468
-
469
- // Line width
470
- const lineWidth = 1 * this.scaleFactor;
471
-
472
- // Adjust start and end of the line
473
- const adjustedStartX = startX + vecX * (lineWidth / 2);
474
- const adjustedStartY = startY + vecY * (lineWidth / 2);
475
- const adjustedEndX = endX - vecX * (lineWidth / 2);
476
- const adjustedEndY = endY - vecY * (lineWidth / 2);
477
-
478
- ctx.lineWidth = lineWidth;
479
- ctx.strokeStyle = "rgba(255, 0, 0, 1)";
480
-
481
- ctx.beginPath();
482
- ctx.moveTo(adjustedStartX, adjustedStartY);
483
- ctx.lineTo(adjustedEndX, adjustedEndY);
484
- ctx.stroke();
485
- });
486
- }
487
-
488
- if (mapdata.NO_MOP_ZONE) {
489
- mapdata.NO_MOP_ZONE.forEach((zone) => {
490
- const noMopMinX = Math.min(zone[0], zone[2], zone[4], zone[6]);
491
- const noMopMinY = Math.min(zone[1], zone[3], zone[5], zone[7]);
492
- const noMopMaxX = Math.max(zone[0], zone[2], zone[4], zone[6]);
493
- const noMopMaxY = Math.max(zone[1], zone[3], zone[5], zone[7]);
494
-
495
- const noMopSizeX = noMopMaxX - noMopMinX;
496
- const noMopSizeY = noMopMaxY - noMopMinY;
497
-
498
- ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
499
- ctx.fillRect(
500
- this.robotXtoPixelX(mapdata.IMAGE, noMopMinX / 50),
501
- this.robotYtoPixelY(mapdata.IMAGE, noMopMaxY / 50),
502
- (noMopSizeX / 50) * this.scaleFactor,
503
- (noMopSizeY / 50) * this.scaleFactor
504
- );
505
- ctx.lineWidth = (1 * this.scaleFactor) / 2;
506
- ctx.strokeStyle = "rgba(0, 0, 255, 1)";
507
- ctx.strokeRect(
508
- this.robotXtoPixelX(mapdata.IMAGE, noMopMinX / 50),
509
- this.robotYtoPixelY(mapdata.IMAGE, noMopMaxY / 50),
510
- (noMopSizeX / 50) * this.scaleFactor,
511
- (noMopSizeY / 50) * this.scaleFactor
512
- );
513
- });
514
- }
515
-
516
- if (mapdata.OBSTACLES2) {
517
- mapdata.OBSTACLES2.forEach((obstacle) => {
518
- const obstacleType = obstacle[2];
519
- const obstacleTitle = obstacleTitles[obstacleType] || "Unknown";
520
- const obstacleConfidence = Math.round(obstacle[3] / 100);
521
- const obstacleText = obstacleTitle[obstacleType] ? obstacleTitle + "(" + obstacleConfidence + "%)" : obstacleTitle;
522
-
523
- const x = this.robotXtoPixelX(mapdata.IMAGE, obstacle[0] / 50);
524
- const y = this.robotYtoPixelY(mapdata.IMAGE, obstacle[1] / 50);
525
-
526
- ctx.fillStyle = "red";
527
- ctx.beginPath();
528
- ctx.arc(x, y, 5, 0, 2 * Math.PI);
529
- ctx.fill();
530
-
531
- // Set the text properties
532
- ctx.font = "14px sans-serif";
533
- ctx.fillStyle = "white";
534
- ctx.textBaseline = "middle";
535
- ctx.textAlign = "center";
536
-
537
- // Calculate the text width and height
538
- const textWidth = ctx.measureText(obstacleText).width;
539
- const textHeight = parseInt(ctx.font, 10);
540
-
541
- // Calculate the position and dimensions of the background rectangle
542
- const padding = 5;
543
- const borderRadius = 5;
544
- const rectX = x - textWidth / 2 - padding;
545
- const rectY = y + 5 + padding / 2;
546
- const rectWidth = textWidth + 2 * padding;
547
- const rectHeight = textHeight + padding;
548
-
549
- // Draw the background rectangle with rounded corners
550
- ctx.fillStyle = "red";
551
- ctx.beginPath();
552
- ctx.moveTo(rectX + borderRadius, rectY);
553
- ctx.lineTo(rectX + rectWidth - borderRadius, rectY);
554
- ctx.quadraticCurveTo(rectX + rectWidth, rectY, rectX + rectWidth, rectY + borderRadius);
555
- ctx.lineTo(rectX + rectWidth, rectY + rectHeight - borderRadius);
556
- ctx.quadraticCurveTo(rectX + rectWidth, rectY + rectHeight, rectX + rectWidth - borderRadius, rectY + rectHeight);
557
- ctx.lineTo(rectX + borderRadius, rectY + rectHeight);
558
- ctx.quadraticCurveTo(rectX, rectY + rectHeight, rectX, rectY + rectHeight - borderRadius);
559
- ctx.lineTo(rectX, rectY + borderRadius);
560
- ctx.quadraticCurveTo(rectX, rectY, rectX + borderRadius, rectY);
561
- ctx.closePath();
562
- ctx.fill();
563
-
564
- // Draw the white text centered within the background rectangle
565
- ctx.fillStyle = "white";
566
- ctx.fillText(obstacleText, x, y + 5 + padding + textHeight / 2);
567
- });
568
- }
569
-
570
- // Zeichne Ladestation wenn vorhanden
571
- if (mapdata.CHARGER_LOCATION) {
572
- if (mapdata.CHARGER_LOCATION.position[0] && mapdata.CHARGER_LOCATION.position[1]) {
573
- ctx.beginPath();
574
- const img_charger_rotated = this.rotateCanvas(img_charger, mapdata.CHARGER_LOCATION.angle);
575
- ctx.drawImage(
576
- img_charger_rotated,
577
- this.robotXtoPixelX(mapdata.IMAGE, mapdata.CHARGER_LOCATION.position[0] / 50) - img_charger_rotated.width / 2,
578
- this.robotYtoPixelY(mapdata.IMAGE, mapdata.CHARGER_LOCATION.position[1] / 50) - img_charger_rotated.height / 2,
579
- img_charger_rotated.width,
580
- img_charger_rotated.height
581
- );
582
- }
583
- }
584
-
585
- // Zeichne Roboter
586
- if (mapdata.ROBOT_POSITION) {
587
- if (mapdata.PATH.current_angle && mapdata.ROBOT_POSITION[0] && mapdata.ROBOT_POSITION[1]) {
588
- ctx.beginPath();
589
- const canvasImg = this.rotateCanvas(img, mapdata.PATH.current_angle);
590
- ctx.drawImage(
591
- canvasImg,
592
- this.robotXtoPixelX(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[0] / 50) - img.width / 4,
593
- this.robotYtoPixelY(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[1] / 50) - img.height / 4,
594
- canvasImg.width / 2,
595
- canvasImg.height / 2
596
- );
597
- } else {
598
- const img_robot_rotated = this.rotateCanvas(img, mapdata.ROBOT_POSITION.angle);
599
- ctx.drawImage(
600
- img_robot_rotated,
601
- this.robotXtoPixelX(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[0] / 50) - img_robot_rotated.width / 4,
602
- this.robotYtoPixelY(mapdata.IMAGE, mapdata.ROBOT_POSITION.position[1] / 50) - img_robot_rotated.height / 4,
603
- img_robot_rotated.width / 2,
604
- img_robot_rotated.height / 2
605
- );
606
- }
607
- }
608
-
609
- // Zeichne Zielposition wenn vorhanden
610
- if (mapdata.GOTO_TARGET) {
611
- const go_to_pin_img = new Image();
612
- go_to_pin_img.src = go_to_pin;
613
-
614
- if (mapdata.GOTO_TARGET[0] && mapdata.GOTO_TARGET[1]) {
615
- ctx.beginPath();
616
- ctx.drawImage(
617
- go_to_pin_img,
618
- this.robotXtoPixelX(mapdata.IMAGE, mapdata.GOTO_TARGET[0] / 50) - go_to_pin_img.width / 2,
619
- this.robotYtoPixelY(mapdata.IMAGE, mapdata.GOTO_TARGET[1] / 50) - (go_to_pin_img.height + 6),
620
- go_to_pin_img.width,
621
- go_to_pin_img.height
622
- );
623
- }
624
- }
625
-
626
- // Draw room names with background
627
- ctx.beginPath();
628
- Object.keys(segmentsData).forEach((segnum) => {
629
- const segment = segmentsData[segnum];
630
-
631
- let roomName = "";
632
-
633
- if (typeof selectedMap != "undefined") {
634
- // cannot get room name if map is from map history
635
-
636
- for (const mappedRoom of mappedRooms) {
637
- const segmentID = mappedRoom[0];
638
- const roomID = mappedRoom[1];
639
-
640
- if (segmentID == segnum) {
641
- roomName = this.adapter.roomIDs[roomID];
642
- break;
643
- }
644
- }
645
-
646
- if (roomName != "") {
647
- const centerX = segment.minX + (segment.maxX - segment.minX) / 2;
648
- const centerY = segment.minY + (segment.maxY - segment.minY) / 2;
649
-
650
- ctx.font = "bold 16px Arial";
651
- const textWidth = ctx.measureText(roomName).width;
652
- const textHeight = 16;
653
- const padding = 10;
654
- const backgroundWidth = textWidth + 2 * padding;
655
- const backgroundHeight = textHeight + 2 * padding;
656
-
657
- // fake square for shadow
658
- const imgdata = ctx.getImageData(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
659
- ctx.shadowOffsetX = 4;
660
- ctx.shadowOffsetY = 4;
661
- ctx.shadowBlur = 5;
662
- ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
663
- ctx.fillStyle = "rgba(0, 0, 0, 0)";
664
- ctx.fillRect(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
665
- ctx.putImageData(imgdata, centerX - backgroundWidth / 2, centerY - backgroundHeight / 2);
666
-
667
- // draw actual square over fake square to have a sharp shadow
668
- ctx.shadowColor = "transparent";
669
- ctx.fillStyle = this.hexToRGBA(assignedColors[segnum], 0.75);
670
- ctx.fillRect(centerX - backgroundWidth / 2, centerY - backgroundHeight / 2, backgroundWidth, backgroundHeight);
671
-
672
- ctx.fillStyle = "black";
673
- ctx.textAlign = "center";
674
- ctx.textBaseline = "middle";
675
- ctx.fillText(roomName, centerX, centerY);
676
- }
677
- }
678
- });
679
- ctx.closePath();
680
-
681
- // crop image
682
- const canvas_trimmed = createCanvas(maxleft - minleft + 2 * offset, maxtop - mintop + 2 * offset);
683
- const ctx_trimmed = canvas_trimmed.getContext("2d");
684
- const trimmed = ctx.getImageData(minleft - offset, mintop - offset, maxleft - minleft + 2 * offset, maxtop - mintop + offset);
685
-
686
- ctx_trimmed.putImageData(trimmed, 0, 0);
687
-
688
- return [canvas.toDataURL(), canvas_trimmed.toDataURL()];
689
- }
690
- }
691
-
692
- module.exports = MapCreator;