UNPKG

homebridge-tasmota

Version:

Homebridge plugin for Tasmota devices leveraging home assistant auto discovery.

117 lines 4.41 kB
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