Skip to content

Commit

Permalink
Dev (#676)
Browse files Browse the repository at this point in the history
* Fix dss for S8 MaxV (Ultra)

* Change min of update interval to 60s to prevent issues

* Add web interface to sidebar

* Small fixes

* Remove unreachable code

* Refactor selected map code

* Add cleaned_area to Q Revo

* Add cleaned_area to Q5 Pro

* Q5 Pro does not support mop options

* Add missing attributes to Q5 Pro

* Code refactor

* Code refactor

* Some more code refactor

* Fix lint

* Refactor map path drawing code
  • Loading branch information
copystring authored Oct 14, 2024
1 parent e7bf671 commit 081deb1
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 125 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ This feature only works when map creation is enabled in the adapter options!
Placeholder for the next version (at the beginning of the line):
### **WORK IN PROGRESS**
-->

### **WORK IN PROGRESS**
* (copystring) Add some missing attributes
* (copystring) Change min of update interval to 60s to prevent issues
* (copystring) Add web interface to sidebar

### 0.6.16 (2024-10-02)
* (copystring) Bugfixes
* (copystring) update test-and-release.yml
Expand Down
2 changes: 1 addition & 1 deletion admin/jsonConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"type": "number",
"label": "Update interval",
"newLine": true,
"min": 1,
"min": 60,
"max": 240,
"style": {
"width": "198px"
Expand Down
20 changes: 19 additions & 1 deletion io-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,26 @@
"connectionType": "cloud",
"dataSource": "poll",
"adminUI": {
"config": "json"
"config": "json",
"tab": "html"
},
"adminTab": {
"name": {
"en": "Roborock",
"de": "Roborock",
"ru": "Roborock",
"pt": "Roborock",
"nl": "Roborock",
"fr": "Roborock",
"it": "Roborock",
"es": "Roborock",
"pl": "Roborock",
"zh-cn": "Roborock"
},
"link": "%web_protocol%://%ip%:%webserverPort%/map.html",
"fa-icon": "</i><img style='width:24px;margin-bottom:-6px;' src='/adapter/admin/roborock.png'><i>"
},
"localLink": "%web_protocol%://%ip%:%webserverPort%/map.html",
"dependencies": [
{
"js-controller": ">=5.0.19"
Expand Down
19 changes: 7 additions & 12 deletions lib/RRMapParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,7 @@ class RRMapParser {
}

BytesToInt(buffer, offset, len) {
let result = 0;

for (let i = 0; i < len; i++) {
result |= (0x000000FF & parseInt(buffer[i + offset])) << 8 * i;
}

return result;
return buffer.slice(offset, offset + len).reduce((acc, byte, i) => acc | (byte << (8 * i)), 0);
}

async parsedata(buf) {
Expand All @@ -90,8 +84,7 @@ class RRMapParser {

let dataPosition = 0x14; // Skip header

const result = {};
result.metaData = metaData;
const result = { metaData };

while (dataPosition < metaData.data_length) {
const type = buf.readUInt16LE(dataPosition);
Expand All @@ -104,7 +97,6 @@ class RRMapParser {
// this.adapter.log.debug("Known values: type=" + type + ", hlength=" + hlength + ", length=" + length);

if (TYPES_REVERSE[type]) {

// this.adapter.log.debug("Test length: " + TYPES_REVERSE[type] + " " + length);
// if (length < 100) this.adapter.log.debug("Test data type: " + TYPES_REVERSE[type] + " " + buf.toString("hex", dataPosition, dataPosition + length));

Expand Down Expand Up @@ -294,9 +286,12 @@ class RRMapParser {
major: mapBuf.readUInt16LE(0x08),
minor: mapBuf.readUInt16LE(0x0a),
},
map_index: mapBuf.readUInt32LE(0x0C),
map_index: mapBuf.readUInt32LE(0x0c),
map_sequence: mapBuf.readUInt32LE(0x10),
SHA1: crypto.createHash("sha1").update(Uint8Array.prototype.slice.call(mapBuf, 0, mapBuf.length - 20)).digest("hex"),
SHA1: crypto
.createHash("sha1")
.update(Uint8Array.prototype.slice.call(mapBuf, 0, mapBuf.length - 20))
.digest("hex"),
expectedSHA1: Buffer.from(Uint8Array.prototype.slice.call(mapBuf, mapBuf.length - 20)).toString("hex"),
};
} else {
Expand Down
9 changes: 7 additions & 2 deletions lib/deviceFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,6 @@ class deviceFeatures {
"roborock.vacuum.a62", // S7 Pro Ultra
"roborock.vacuum.a51", // S8
"roborock.vacuum.a15", // S7
"roborock.vacuum.a72", // Q5 Pro
"roborock.vacuum.a27", // S7 MaxV (Ultra)
"roborock.vacuum.a19", // S4 Max
"roborock.vacuum.a40", // Q7
Expand Down Expand Up @@ -766,6 +765,10 @@ class deviceFeatures {
"set_back_type",
"set_charge_status",
"set_clean_percent",
"set_cleaned_area",
"set_switch_status",
"set_common_status",
"set_in_warmup",
],
// Q8 Max
"roborock.vacuum.a73": [
Expand Down Expand Up @@ -796,6 +799,7 @@ class deviceFeatures {
"set_clean_percent",
"set_rdt",
"set_switch_status",
"set_cleaned_area",
],
// S4
"roborock.vacuum.s4": ["setCleaningRecordsInt", "setConsumablesString"],
Expand Down Expand Up @@ -855,6 +859,7 @@ class deviceFeatures {
"set_in_warmup",
"set_map_flag",
"set_task_id",
"set_dss",
],
// Roborock Qrevo S
"roborock.vacuum.a104": [
Expand Down Expand Up @@ -905,7 +910,7 @@ class deviceFeatures {
}
}
} else {
this.adapter.catchError(`This robot ${robotModel} is not fully supported just yet. Contact the dev to get this robot fully supported!`);
this.adapter.catchError(`This robot is not fully supported just yet. Contact the dev to get this robot fully supported!`, null, null, robotModel);
}

this.adapter.createBaseRobotObjects(this.duid);
Expand Down
9 changes: 9 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
localConnector: require("./localConnector").localConnector,
roborock_mqtt_connector: require("./roborock_mqtt_connector").roborock_mqtt_connector,
message: require("./message").message,
vacuum: require("./vacuum").vacuum,
roborockPackageHelper: require("./roborockPackageHelper").roborockPackageHelper,
deviceFeatures: require("./deviceFeatures").deviceFeatures,
messageQueueHandler: require("./messageQueueHandler").messageQueueHandler,
};
68 changes: 30 additions & 38 deletions lib/mapCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,12 @@ class MapCreator {
const sy = y1 < y2 ? 1 : -1;
let err = dx - dy;

for(;;) {
for (;;) {
// Setze Pixel im ImageData
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
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
const index = (x1 + y1 * imageData.width) * 4;
pixels[index] = 128; // r
pixels[index] = 128; // r
pixels[index + 1] = 128; // g
pixels[index + 2] = 128; // b
pixels[index + 3] = 128; // a
Expand Down Expand Up @@ -157,8 +158,7 @@ class MapCreator {
} else if (options.ROBOT === "originalRobot") {
img = await loadImage(originalRobot);
}
}
else {
} else {
img = await loadImage(originalRobot);
}
img_charger = await loadImage(charger);
Expand Down Expand Up @@ -210,7 +210,7 @@ class MapCreator {

if (mapdata.IMAGE.pixels.segments && !mapdata.CURRENTLY_CLEANED_BLOCKS && colors.newmap) {
mapdata.IMAGE.pixels.segments.forEach((px) => {
const segnum = (px >> 21);
const segnum = px >> 21;
const x = this.getX(mapdata.IMAGE.dimensions, px & 0xfffff);
const y = this.getY(mapdata.IMAGE.dimensions, px & 0xfffff);

Expand All @@ -226,25 +226,25 @@ class MapCreator {
}
});

Object.keys(segmentsData).forEach(segnum => {
Object.keys(segmentsData).forEach((segnum) => {
const segment = segmentsData[segnum];
segmentsBounds[segnum] = {
minX: segment.minX,
maxX: segment.maxX,
minY: segment.minY,
maxY: segment.maxY
maxY: segment.maxY,
};
});

Object.keys(segmentsBounds).forEach(segnum => {
Object.keys(segmentsBounds).forEach((segnum) => {
const currentBounds = segmentsBounds[segnum];
const adjacentSegments = Object.keys(segmentsBounds).filter(otherSegnum => {
const adjacentSegments = Object.keys(segmentsBounds).filter((otherSegnum) => {
const otherBounds = segmentsBounds[otherSegnum];
return segnum !== otherSegnum && this.areRoomsAdjacent(currentBounds, otherBounds);
});

const usedColors = adjacentSegments.map(adjSegnum => assignedColors[adjSegnum]);
const availableColor = availableColors.find(color => !usedColors.includes(color));
const usedColors = adjacentSegments.map((adjSegnum) => assignedColors[adjSegnum]);
const availableColor = availableColors.find((color) => !usedColors.includes(color));

if (availableColor) {
assignedColors[segnum] = availableColor;
Expand All @@ -253,11 +253,11 @@ class MapCreator {
}
});

Object.keys(segmentsData).forEach(segnum => {
Object.keys(segmentsData).forEach((segnum) => {
const segment = segmentsData[segnum];
ctx.fillStyle = assignedColors[segnum] || availableColors[0];
ctx.beginPath();
segment.points.forEach(point => {
segment.points.forEach((point) => {
ctx.rect(point.x, point.y, this.scaleFactor, this.scaleFactor);
});
ctx.fill();
Expand Down Expand Up @@ -367,31 +367,24 @@ class MapCreator {
}

// Male den Pfad
if (mapdata.PATH) {
if (mapdata.PATH.points && mapdata.PATH.points.length !== 0) {
ctx.fillStyle = colors.path;
let first = true;
let cold1, cold2;
if (mapdata.PATH?.points?.length) {
ctx.fillStyle = colors.path;
ctx.lineWidth = this.scaleFactor / 2;
ctx.strokeStyle = colors.path;

ctx.beginPath();
mapdata.PATH.points.forEach((coord) => {
if (first) {
(cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)),
(cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)),
ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);
first = false;
} else {
ctx.lineWidth = this.scaleFactor / 2;
ctx.strokeStyle = colors.path;
ctx.beginPath();
let [cold1, cold2] = [this.robotXtoPixelX(mapdata.IMAGE, mapdata.PATH.points[0][0] / 50), this.robotYtoPixelY(mapdata.IMAGE, mapdata.PATH.points[0][1] / 50)];
ctx.fillRect(cold1, cold2, (1 * this.scaleFactor) / 2, (1 * this.scaleFactor) / 2);

mapdata.PATH.points.slice(1).forEach((coord) => {
ctx.moveTo(cold1, cold2);
cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50);
cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50);
ctx.lineTo(cold1, cold2);
});

ctx.moveTo(cold1, cold2);
(cold1 = this.robotXtoPixelX(mapdata.IMAGE, coord[0] / 50)), (cold2 = this.robotYtoPixelY(mapdata.IMAGE, coord[1] / 50)), ctx.lineTo(cold1, cold2);
// ctx.stroke();
}
});
ctx.stroke();
ctx.closePath();
}
ctx.stroke();
ctx.closePath();
}

// Male geplanten Pfad
Expand Down Expand Up @@ -692,7 +685,6 @@ class MapCreator {
} else {
return [createCanvas(1, 1).toDataURL(), createCanvas(1, 1).toDataURL()]; // return empty canvas
}

}
}

Expand Down
8 changes: 4 additions & 4 deletions lib/vacuum.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class vacuum {

// const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_status", []);
const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_prop", ["get_status"]);
const selectedMap = deviceStatus[0].map_status >> 2 ?? -1; // to get the currently selected map perform bitwise right shift
const selectedMap = this.adapter.getSelectedMap(deviceStatus);

// This is for testing and debugging maps. This can't be stored in a state.
zlib.gzip(map, (error, buffer) => {
Expand Down Expand Up @@ -101,7 +101,7 @@ class vacuum {
}
}
} catch (error) {
this.adapter.catchError(error, "get_map_v1", duid), this.robotModel;
this.adapter.catchError(error, "get_map_v1", duid, this.robotModel);
}
}
}
Expand Down Expand Up @@ -286,7 +286,7 @@ class vacuum {
}
break;
case "map_status": {
deviceStatus[0][attribute] = deviceStatus[0][attribute] >> 2 ?? -1; // to get the currently selected map perform bitwise right shift
deviceStatus[0][attribute] = this.adapter.getSelectedMap(deviceStatus);

if (isCleaning) {
this.adapter.startMapUpdater(duid);
Expand Down Expand Up @@ -331,7 +331,7 @@ class vacuum {
}
} else if (parameter == "get_room_mapping") {
const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_status", []);
const roomFloor = deviceStatus[0]["map_status"] >> 2 ?? -1; // to get the currently selected map perform bitwise right shift
const roomFloor = this.adapter.getSelectedMap(deviceStatus);
const mappedRooms = await this.adapter.messageQueueHandler.sendRequest(duid, "get_room_mapping", []);

// if no rooms have been named, processing them can't work
Expand Down
Loading

0 comments on commit 081deb1

Please sign in to comment.