Skip to content

Commit

Permalink
Moved MQTT PNG Drawing to seperate project
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Apr 12, 2019
1 parent 3594a71 commit 2e39ecd
Show file tree
Hide file tree
Showing 10 changed files with 27 additions and 175 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PKG ?= pkg
.PHONY: all clean

all:
$(PKG) --targets node8-linux-armv7 --no-bytecode --options max-old-space-size=72 --public-packages=exif-parser,omggif,trim,prettycron .
npm run build

clean:
rm -f valetudo
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ The Valetudo binary however does not so if you are upgrading your firmware, you
* #valetudo on irc.freenode.net
* [Valetudo Telegram group](https://t.me/joinchat/AR1z8xOGJQwkApTulyBx1w)

### Resources
* [I can't believe it's not Valetudo](https://github.com/Hypfer/ICantBelieveItsNotValetudo) - A companion service for PNG Maps

### FAQ
**Q:** Why the name?

Expand Down
2 changes: 1 addition & 1 deletion deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ bytecode. This is why we specify `--no-bytecode`.
git clone http://github.com/hypfer/Valetudo
cd Valetudo
npm install
./node_modules/.bin/pkg --targets node8-linux-armv7 --options max-old-space-size=72 --no-bytecode --public-packages=exif-parser,omggif,trim,prettycron .
npm run build
```
After that you'll find a binary named valetudo in that folder which you should scp to /usr/local/bin/

Expand Down
2 changes: 1 addition & 1 deletion develop/HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Modify the source code according to your needs, and restart the server as needed
When you're done with your modifications, here's how to build the executable for the robot:

```
./node_modules/.bin/pkg --targets latest-linux-armv7 --no-bytecode --options max-old-space-size=72 --public-packages=exif-parser,omggif,trim,prettycron .
npm run build
```

You can also create the build with the provider Dockerfile (e.g. when you're on a different OS than Linux). Create the build environment container with:
Expand Down
8 changes: 1 addition & 7 deletions lib/Configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ const Configuration = function() {
topicPrefix: "valetudo",
autoconfPrefix: "homeassistant",
broker_url: "mqtt://user:[email protected]",
mapSettings: {
drawPath: true,
drawCharger: true,
drawRobot: true,
border: 2,
scale: 4
}
provideMapData: true
},
"dummycloud": {
spoofedIP: "203.0.113.1",
Expand Down
46 changes: 10 additions & 36 deletions lib/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const FAN_SPEEDS = {
* @param options.identifier {string}
* @param options.topicPrefix {string}
* @param options.autoconfPrefix {string}
* @param options.mapSettings {object}
* @param options.attributesUpdateInterval {number}
* @param options.provideMapData {boolean}
* @param options.events {EventEmitter}
* @constructor
*/
Expand All @@ -42,19 +42,18 @@ const MqttClient = function(options) {
this.identifier = options.identifier || "rockrobo";
this.topicPrefix = options.topicPrefix || "valetudo";
this.autoconfPrefix = options.autoconfPrefix || "homeassistant";
this.mapSettings = options.mapSettings || {};
this.attributesUpdateInterval = options.attributesUpdateInterval || 60000;
this.provideMapData = options.provideMapData !== undefined || true;
this.events = options.events;

this.topics = {
command: this.topicPrefix + "/" + this.identifier + "/command",
set_fan_speed: this.topicPrefix + "/" + this.identifier + "/set_fan_speed",
send_command: this.topicPrefix + "/" + this.identifier + "/custom_command",
state: this.topicPrefix + "/" + this.identifier + "/state",
map: this.topicPrefix + "/" + this.identifier + "/map",
map_data: this.topicPrefix + "/" + this.identifier + "/map_data",
attributes: this.topicPrefix + "/" + this.identifier + "/attributes",
homeassistant_autoconf_vacuum: this.autoconfPrefix + "/vacuum/" + this.topicPrefix + "_" + this.identifier + "/config",
homeassistant_autoconf_map: this.autoconfPrefix + "/camera/" + this.topicPrefix + "_" + this.identifier + "_map/config"
};

this.autoconf_payloads = {
Expand Down Expand Up @@ -94,20 +93,13 @@ const MqttClient = function(options) {
],
send_command_topic: this.topics.send_command,
json_attributes_topic: this.topics.attributes
},
map: {
name: this.identifier + "_map",
unique_id: this.identifier + "_map",
topic: this.topics.map
}
};

this.lastMapDraw = new Date(0);

this.connect();
this.updateAttributesTopic();
this.events.on("valetudo.map", (mapDTO) => {
this.updateMapTopic(mapDTO);
this.updateMapDataTopic(mapDTO);
});

this.events.on("miio.status", (statusData) => {
Expand All @@ -129,11 +121,6 @@ MqttClient.prototype.connect = function() {
this.client.publish(this.topics.homeassistant_autoconf_vacuum, JSON.stringify(this.autoconf_payloads.vacuum), {
retain: true
});

this.client.publish(this.topics.homeassistant_autoconf_map, JSON.stringify(this.autoconf_payloads.map), {
retain: true
});

} else {
//TODO: needs more error handling
console.error(err);
Expand All @@ -147,31 +134,18 @@ MqttClient.prototype.connect = function() {
}
};


/**
* This _needs_ to be throttled since it will kill the CPU and block the event loop otherwise
* There's a second and unused GPU core though so if someone feels like hw accellerating this feel free to do so
* TODO: Running this in a second thread would also be an option. just keep an eye on the memory usage
*
* @param mapDTO {MapDTO}
*/
MqttClient.prototype.updateMapTopic = function(mapDTO) {
const now = new Date();
if(now - 10000 > this.lastMapDraw) {
this.lastMapDraw = now;
if(this.client && this.client.connected === true) {
Tools.DRAW_MAP_PNG({
mapDTO: mapDTO,
settings: this.mapSettings
}, (err, buf) => {
if(!err) {
this.client.publish(this.topics.map, buf, {retain: true});
} else {
console.error(err);
}
})
}
MqttClient.prototype.updateMapDataTopic = function(mapDTO) {
if(mapDTO && mapDTO.parsedData) {
this.client.publish(this.topics.map_data, JSON.stringify(mapDTO.parsedData), {retain: true});
}
};


MqttClient.prototype.updateAttributesTopic = function() {
if(this.attributesUpdateTimeout) {
clearTimeout(this.attributesUpdateTimeout);
Expand Down
125 changes: 0 additions & 125 deletions lib/Tools.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const fs = require("fs");
const path = require("path");
const Jimp = require("jimp");
const zlib = require("zlib");
const crypto = require("crypto");

Expand Down Expand Up @@ -28,130 +27,6 @@ const Tools = {
},
BUFFER_IS_GZIP: function (buf) {
return Buffer.isBuffer(buf) && buf[0] === 0x1f && buf[1] === 0x8b;
},
/**
*
* @param options {object}
* @param options.mapDTO {MapDTO}
* @param options.settings
* @param callback {function}
* @constructor
*/
DRAW_MAP_PNG: function (options, callback) {
const COLORS = {
floor: Jimp.rgbaToInt(0, 118, 255, 255),
obstacle_weak: Jimp.rgbaToInt(102, 153, 255, 255),
obstacle_strong: Jimp.rgbaToInt(82, 174, 255, 255),
path: Jimp.rgbaToInt(255, 255, 255, 255)
};
const DIMENSIONS = {
width: options.mapDTO.parsedData.image.dimensions.width,
height: options.mapDTO.parsedData.image.dimensions.height
};

const settings = Object.assign({
drawPath: true,
drawCharger: true,
drawRobot: true,
border: 2,
scale: 4
}, options.settings);

new Jimp(DIMENSIONS.width, DIMENSIONS.height, function (err, image) {
if (!err) {
//Step 1: Draw Map + calculate viewport
Object.keys(options.mapDTO.parsedData.image.pixels).forEach(key => {
const color = COLORS[key];

options.mapDTO.parsedData.image.pixels[key].forEach(function drawPixel(px) {
image.setPixelColor(color, px[0], px[1]);
})
});

//Step 2: Scale
image.scale(settings.scale, Jimp.RESIZE_NEAREST_NEIGHBOR);

//Step 3: Draw Path
const coords = options.mapDTO.parsedData.path.points.map(point => {
return [
Math.floor((point[0]/50 - options.mapDTO.parsedData.image.position.left) * settings.scale),
Math.floor((point[1]/50 - options.mapDTO.parsedData.image.position.top) * settings.scale)
]});
let first = true;
let oldPathX, oldPathY; // old Coordinates
let dx, dy; //delta x and y
let step, x, y, i;
coords.forEach(function (coord) {
if (!first && settings.drawPath) {
dx = (coord[0] - oldPathX);
dy = (coord[1] - oldPathY);
if (Math.abs(dx) >= Math.abs(dy)) {
step = Math.abs(dx);
} else {
step = Math.abs(dy);
}
dx = dx / step;
dy = dy / step;
x = oldPathX;
y = oldPathY;
i = 1;
while (i <= step) {
image.setPixelColor(COLORS.path, x, y);
x = x + dx;
y = y + dy;
i = i + 1;
}
}
oldPathX = coord[0];
oldPathY = coord[1];
first = false;
});

Jimp.read(chargerImagePath, function (err, chargerImage) {
if (!err) {
Jimp.read(robotImagePath, function (err, robotImage) {
if (!err) {
//Step 6: Draw Charger
if (settings.drawCharger === true && options.mapDTO.parsedData.charger) {
const chargerCoords = {
x: options.mapDTO.parsedData.charger[0] / 50 - options.mapDTO.parsedData.image.position.left,
y: options.mapDTO.parsedData.charger[1] / 50 - options.mapDTO.parsedData.image.position.top
};

image.composite(
chargerImage,
chargerCoords.x * settings.scale - chargerImage.bitmap.width / 2,
chargerCoords.y * settings.scale - chargerImage.bitmap.height / 2
);
}

//Step 7: Draw Robot
if (settings.drawRobot === true && options.mapDTO.parsedData.robot) {
const robotCoords = {
x: options.mapDTO.parsedData.robot[0] / 50 - options.mapDTO.parsedData.image.position.left,
y: options.mapDTO.parsedData.robot[1] / 50 - options.mapDTO.parsedData.image.position.top
};

image.composite(
robotImage.rotate(-1 * options.mapDTO.parsedData.path.current_angle),
robotCoords.x * settings.scale - robotImage.bitmap.width / 2,
robotCoords.y * settings.scale - robotImage.bitmap.height / 2
)
}

image.getBuffer(Jimp.AUTO, callback);
} else {
callback(err);
}
})
} else {
callback(err);
}
});
} else {
callback(err);
}
});
}
};

Expand Down
10 changes: 9 additions & 1 deletion lib/Valetudo.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const Valetudo = function() {
identifier: this.configuration.get("mqtt").identifier,
topicPrefix: this.configuration.get("mqtt").topicPrefix,
autoconfPrefix: this.configuration.get("mqtt").autoconfPrefix,
mapSettings: this.configuration.get("mqtt").mapSettings,
provideMapData: this.configuration.get("mqtt").provideMapData,
events: this.events
});
}
Expand Down Expand Up @@ -83,7 +83,11 @@ Valetudo.CLOUD_KEY_PROVIDER = function() {
if(Array.isArray(key) && key[1]) {
// noinspection JSConstructorReturnsPrimitive
return key[1];
} else {
console.error("Failed to fetch cloudKey");
}
} else {
console.error("Failed to read device.conf");
}

// noinspection JSConstructorReturnsPrimitive
Expand All @@ -109,7 +113,11 @@ Valetudo.DEVICE_ID_PROVIDER = function() { //TODO: merge with CLOUD_KEY_PROVIDER
if(Array.isArray(did) && did[1]) {
// noinspection JSConstructorReturnsPrimitive
return did[1];
} else {
console.error("Failed to fetch DID");
}
} else {
console.error("Failed to read device.conf");
}

// noinspection JSConstructorReturnsPrimitive
Expand Down
1 change: 0 additions & 1 deletion lib/webserver/WebServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const spawnSync = require("child_process").spawnSync;
const zlib = require("zlib");
const crypto = require("crypto");
const bodyParser = require("body-parser");
const Jimp = require("jimp");
const url = require("url");
const WebSocket = require("ws");
const RRMapParser = require("../RRMapParser");
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "pkg --targets node8-linux-armv7 --no-bytecode --options max-old-space-size=72 --public-packages=exif-parser,omggif,trim,prettycron,mqtt ."
"build": "pkg --targets node8-linux-armv7 --no-bytecode --options max-old-space-size=72 --public-packages=prettycron ."
},
"author": "",
"dependencies": {
"body-parser": "^1.18.3",
"compression": "^1.7.2",
"express": "^4.16.3",
"jimp": "0.3.2",
"mqtt": "^2.18.8",
"prettycron": "^0.10.0",
"ws": "^6.1.4"
Expand Down

0 comments on commit 2e39ecd

Please sign in to comment.