Skip to content

Commit

Permalink
feat(mqtt): Home Assistant Autodiscovery for Maps by embedding the ma…
Browse files Browse the repository at this point in the history
…p data in a png
  • Loading branch information
Hypfer committed Feb 11, 2021
1 parent e14935d commit 83d1d13
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 14 deletions.
Empty file.
4 changes: 2 additions & 2 deletions client/settings-mqtt.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@
</ons-list-item>
<ons-list-item>
<div class="left">
Base64 Encode compressed map data:
Homeassistant Map Hack:
</div>
<label class="right">
<ons-checkbox id="settings-mqtt-input-base64-encode-map-data"></ons-checkbox>
<ons-checkbox id="settings-mqtt-input-homeassistant-maphack"></ons-checkbox>
</label>
</ons-list-item>
</ons-list>
Expand Down
10 changes: 5 additions & 5 deletions client/settings-mqtt.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function updateSettingsMqttPage() {
var mqttInputTopicPrefix = document.getElementById("settings-mqtt-input-topic-prefix");
var mqttInputAutoconfPrefix = document.getElementById("settings-mqtt-input-autoconf-prefix");
var mqttInputProvideMapData = document.getElementById("settings-mqtt-input-provide-map-data");
var mqttInputBase64EncodeMapData = document.getElementById("settings-mqtt-input-base64-encode-map-data");
var mqttInputHomeassistantMapHack = document.getElementById("settings-mqtt-input-homeassistant-maphack");

mqttInputEnabled.addEventListener("input", updateMqttSaveButton);
mqttInputServer.addEventListener("input", updateMqttSaveButton);
Expand All @@ -33,7 +33,7 @@ async function updateSettingsMqttPage() {
mqttInputTopicPrefix.addEventListener("input", updateMqttSaveButton);
mqttInputAutoconfPrefix.addEventListener("input", updateMqttSaveButton);
mqttInputProvideMapData.addEventListener("input", updateMqttSaveButton);
mqttInputBase64EncodeMapData.addEventListener("input", updateMqttSaveButton);
mqttInputHomeassistantMapHack.addEventListener("input", updateMqttSaveButton);

loadingBarSettingsMqtt.setAttribute("indeterminate", "indeterminate");
try {
Expand All @@ -55,7 +55,7 @@ async function updateSettingsMqttPage() {
mqttInputTopicPrefix.value = res.topicPrefix || "valetudo";
mqttInputAutoconfPrefix.value = res.autoconfPrefix || "";
mqttInputProvideMapData.checked = (res.provideMapData === true);
mqttInputBase64EncodeMapData.checked = (res.base64EncodeMapData === true);
mqttInputHomeassistantMapHack.checked = (res.homeassistantMapHack !== false);
} catch (err) {
ons.notification.toast(err.message,
{buttonLabel: "Dismiss", timeout: window.fn.toastErrorTimeout});
Expand Down Expand Up @@ -98,7 +98,7 @@ async function handleMqttSettingsSaveButton() {
var mqttInputTopicPrefix = document.getElementById("settings-mqtt-input-topic-prefix");
var mqttInputAutoconfPrefix = document.getElementById("settings-mqtt-input-autoconf-prefix");
var mqttInputProvideMapData = document.getElementById("settings-mqtt-input-provide-map-data");
var mqttInputBase64EncodeMapData = document.getElementById("settings-mqtt-input-base64-encode-map-data");
var mqttInputHomeassistantMapHack = document.getElementById("settings-mqtt-input-homeassistant-maphack");

loadingBarSettingsMqtt.setAttribute("indeterminate", "indeterminate");
try {
Expand All @@ -118,7 +118,7 @@ async function handleMqttSettingsSaveButton() {
topicPrefix: mqttInputTopicPrefix.value,
autoconfPrefix: mqttInputAutoconfPrefix.value,
provideMapData: mqttInputProvideMapData.checked,
base64EncodeMapData: mqttInputBase64EncodeMapData.checked
homeassistantMapHack: mqttInputHomeassistantMapHack.checked
});
ons.notification.toast(
"MQTT settings saved. MQTT Client will apply changes now.",
Expand Down
2 changes: 1 addition & 1 deletion lib/miio/RetryWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class RetryWrapper {

this.state = STATES.HANDSHAKING;

return await new Promise((resolve, reject) => {
return await new Promise((resolve, reject) => { //TODO: uhm? return await new promise?
this.loopHandshake(() => {
resolve();
});
Expand Down
13 changes: 10 additions & 3 deletions lib/mqtt/MqttAutoConfManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,22 @@ class MqttAutoConfManager { //TODO: does this thing even make sense?
set_fan_speed_topic: this.supportedFeatures.includes("fan_speed") ? this.topics.set_fan_speed : undefined,
fan_speed_list: this.supportedFeatures.includes("fan_speed") ? this.robot.capabilities.FanSpeedControlCapability.getPresets() : undefined
}
},
{
topic: this.autoconfPrefix + "/camera/" + this.topicPrefix + "_" + this.identifier + "/config",
payload: {
name: "Map",
unique_id: this.identifier + "_map",
device: this.deviceSpecification,
availability_topic: this.topics.availability,
topic: this.topics.map_data
}
}
//Since the map_data will kill the recorder: component, we can't add autoconfig for it (yet) :(
];


this.registeredTopics = {};
this.registeredAutoconfData = [];


}

get deviceSpecification() {
Expand Down
44 changes: 42 additions & 2 deletions lib/mqtt/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ require("../DnsHack");

const mqtt = require("mqtt");
const zlib = require("zlib");
const fs = require("fs");
const path = require("path");
const crc = require("crc");
const Logger = require("../Logger");
const MqttAutoConfManager = require("./MqttAutoConfManager");
const attributes = require("../entities/state/attributes");
Expand Down Expand Up @@ -90,7 +93,7 @@ class MqttClient {

this.attributesUpdateInterval = mqttConfig.attributesUpdateInterval || 60000;
this.provideMapData = mqttConfig.provideMapData !== undefined ? mqttConfig.provideMapData : true;
this.base64EncodeMapData = mqttConfig.base64EncodeMapData !== undefined ? mqttConfig.base64EncodeMapData : true;
this.homeassistantMapHack = mqttConfig.homeassistantMapHack !== undefined ? mqttConfig.homeassistantMapHack : true;


this.registerCapabilityAttributeHandlers();
Expand Down Expand Up @@ -359,9 +362,37 @@ class MqttClient {
if (this.client && this.client.connected === true && map) {
zlib.deflate(JSON.stringify(map), (err, buf) => {
if (!err) {
let payload;

if (this.homeassistantMapHack === true) {
const length = Buffer.alloc(4);
const checksum = Buffer.alloc(4);

const textChunkData = Buffer.concat([
HOMEASSISTANT_MAP_HACK.TEXT_CHUNK_TYPE,
HOMEASSISTANT_MAP_HACK.TEXT_CHUNK_METADATA,
buf
]);

length.writeInt32BE(HOMEASSISTANT_MAP_HACK.TEXT_CHUNK_METADATA.length + buf.length, 0);
checksum.writeUInt32BE(crc.crc32(textChunkData), 0);


payload = Buffer.concat([
HOMEASSISTANT_MAP_HACK.IMAGE_WITHOUT_END_CHUNK,
length,
textChunkData,
checksum,
HOMEASSISTANT_MAP_HACK.END_CHUNK
]);
} else {
payload = buf;
}


this.client.publish(
this.autoConfManager.topics.map_data,
this.base64EncodeMapData ? buf.toString("base64") : buf,
payload,
{retain: true, qos:this.qos}
);
} else {
Expand Down Expand Up @@ -654,4 +685,13 @@ const CAPABILITY_TYPE_TO_HANDLER_MAPPING = {
[capabilities.GoToLocationCapability.TYPE]: attributeHandlers.capability.GoToLocationCapabilityBasedAttributeMqttHandler
};

const HOMEASSISTANT_MAP_HACK = {
TEXT_CHUNK_TYPE: Buffer.from("zTXt"),
TEXT_CHUNK_METADATA: Buffer.from("ValetudoMap\0\0"),
IMAGE: fs.readFileSync(path.join(__dirname, "../res/valetudo_home_assistant_mqtt_camera_hack.png"))
};
HOMEASSISTANT_MAP_HACK.IMAGE_WITHOUT_END_CHUNK = HOMEASSISTANT_MAP_HACK.IMAGE.slice(0, HOMEASSISTANT_MAP_HACK.IMAGE.length - 12);
//The PNG IEND chunk is always the last chunk and consists of a 4-byte length, the 4-byte chunk type, 0-byte chunk data and a 4-byte crc
HOMEASSISTANT_MAP_HACK.END_CHUNK = HOMEASSISTANT_MAP_HACK.IMAGE.slice(HOMEASSISTANT_MAP_HACK.IMAGE.length - 12);

module.exports = MqttClient;
2 changes: 1 addition & 1 deletion lib/res/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"topicPrefix": "valetudo",
"autoconfPrefix": "homeassistant",
"provideMapData": true,
"base64EncodeMapData": true
"homeassistantMapHack": true
},
"ntpClient": {
"enabled": true,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@destinationstransfers/ntp": "^2.0.0",
"body-parser": "^1.18.3",
"compression": "^1.7.2",
"crc": "^3.8.0",
"express": "^4.17.1",
"express-basic-auth": "^1.2.0",
"express-dynamic-middleware": "^1.0.0",
Expand Down
25 changes: 25 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,11 @@ base64-js@^1.3.0:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==

base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==

basic-auth@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
Expand Down Expand Up @@ -437,6 +442,14 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==

buffer@^5.1.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"

busboy@^0.2.11:
version "0.2.14"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
Expand Down Expand Up @@ -647,6 +660,13 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=

crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
dependencies:
buffer "^5.1.0"

cross-env@7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9"
Expand Down Expand Up @@ -1498,6 +1518,11 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"

ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==

ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
Expand Down

0 comments on commit 83d1d13

Please sign in to comment.