homebridge-tasmota
Version:
Homebridge plugin for Tasmota devices leveraging home assistant auto discovery.
117 lines • 4.41 kB
JavaScript
import createDebug from "debug";
import { EventEmitter } from "events";
import mqttClient from "mqtt";
const debug = createDebug("Tasmota:mqtt");
export class Mqtt extends EventEmitter {
connection;
wildCardTopics = [];
constructor(config) {
super();
const options = {
username: config.mqttUsername || "",
password: config.mqttPassword || "",
};
this.connection = mqttClient.connect(`mqtt://${config.mqttHost}`, options);
this.setupConnectionHandlers(config);
}
setupConnectionHandlers(config) {
this.connection.on("connect", () => {
debug("Connected to MQTT broker");
this.connection.subscribe("homeassistant/#");
});
this.connection.on("reconnect", () => {
console.error("ERROR: MQTT reconnecting to", config.mqttHost);
});
this.connection.on("error", (err) => {
console.error("ERROR: MQTT connection error", config.mqttHost, err);
});
this.connection.on("message", (topic, message) => this.handleMessage(topic, message));
}
handleMessage(topic, message) {
const subject = topic.split("/");
switch (subject[0]) {
case "homeassistant":
this.handleHomeAssistantMessage(topic, message, subject);
break;
default:
this.handleDefaultMessage(topic, message);
break;
}
}
handleHomeAssistantMessage(topic, message, subject) {
if (message.length > 5 && topic.endsWith("config")) {
try {
const device = JSON.parse(message.toString());
device.tasmotaType = subject[1];
const validTypes = ["switch", "sensor", "binary_sensor", "light", "fan", "garageDoor"];
if (validTypes.includes(subject[1])) {
this.emit("Discovered", topic, device);
}
}
catch (error) {
debug("Error parsing message:", error);
debug("Triggered by message:", message.toString());
}
}
else if (topic.endsWith("config")) {
this.emit("Remove", topic);
}
}
handleDefaultMessage(topic, message) {
debug("Emit topic:", topic, message.toString());
this.emit(topic, topic, message);
if (this.isWildcardTopic(topic)) {
const wildcard = this.getWildcardTopic(topic);
debug("Emit wildcard:", wildcard, message.toString());
this.emit(wildcard, wildcard, message);
}
}
availabilitySubscribe(topic) {
debug("Availability subscribe:", topic);
this.connection.subscribe(topic);
}
statusSubscribe(topic) {
debug("Status subscribe:", topic);
this.connection.subscribe(topic);
if (topic.includes("+") || topic.includes("#")) {
this.wildCardTopics.push({ topic });
}
}
sendMessage(topic, message) {
debug("Send message:", topic, message);
if (!topic || !message) {
throw new Error("sendMessage requires both topic and message");
}
this.connection.publish(topic, message);
}
isWildcardTopic(topic) {
return this.wildCardTopics.some((wildcard) => this.mqttWildcard(topic, wildcard.topic));
}
getWildcardTopic(topic) {
const match = this.wildCardTopics.find((wildcard) => this.mqttWildcard(topic, wildcard.topic));
return match?.topic || "";
}
mqttWildcard(topic, wildcard) {
if (topic === wildcard)
return [];
if (wildcard === "#")
return [topic];
const topicSegments = topic.split("/");
const wildcardSegments = wildcard.split("/");
const result = [];
for (let i = 0; i < topicSegments.length; i++) {
if (wildcardSegments[i] === "+") {
result.push(topicSegments[i]);
}
else if (wildcardSegments[i] === "#") {
result.push(topicSegments.slice(i).join("/"));
return result;
}
else if (wildcardSegments[i] !== topicSegments[i]) {
return null;
}
}
return wildcardSegments.length === topicSegments.length ? result : null;
}
}
//# sourceMappingURL=Mqtt.js.map