-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.ts
More file actions
121 lines (101 loc) · 3.99 KB
/
app.ts
File metadata and controls
121 lines (101 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import Homey from 'homey';
import { MqttClient, type MqttConnectionOptions } from './lib/MqttClient.js';
import { DiscoveryManager } from './lib/DiscoveryManager.js';
export default class TasmotaMqttApp extends Homey.App {
mqttClient!: MqttClient;
discoveryManager!: DiscoveryManager;
async onInit(): Promise<void> {
this.mqttClient = new MqttClient();
this.discoveryManager = new DiscoveryManager(this.mqttClient);
this.mqttClient.on('connected', () => {
this.log('MQTT connected');
this.discoveryManager.start();
// Subscribe to stat/tele wildcards for all devices
// Default Tasmota ft is "%prefix%/%topic%/" → stat/<topic>/RESULT, tele/<topic>/STATE
this.mqttClient.subscribe(['stat/#', 'tele/#']);
});
this.mqttClient.on('disconnected', () => {
this.log('MQTT disconnected');
});
this.mqttClient.on('error', (err: Error) => {
this.error('MQTT error:', err.message);
});
// Route incoming messages to paired devices
this.mqttClient.on('message', (topic: string, payload: string) => {
this.routeMessage(topic, payload);
});
this.discoveryManager.on('device_discovered', (mac: string) => {
this.log(`Discovered Tasmota device: ${mac}`);
});
this.discoveryManager.on('device_removed', (mac: string) => {
this.log(`Tasmota device removed: ${mac}`);
});
// Connect using saved settings
this.connectFromSettings();
// Re-connect when settings change (debounced to avoid spam from multiple fields saving)
let reconnectTimer: NodeJS.Timeout | null = null;
this.homey.settings.on('set', (key: string) => {
if (key.startsWith('mqtt_')) {
if (reconnectTimer) this.homey.clearTimeout(reconnectTimer);
reconnectTimer = this.homey.setTimeout(() => {
this.log('MQTT settings changed, reconnecting...');
this.connectFromSettings();
reconnectTimer = null;
}, 1000);
}
});
this.log('Tasmota MQTT app initialized');
}
private connectFromSettings(): void {
const host = this.homey.settings.get('mqtt_host') as string;
if (!host) {
this.log('MQTT host not configured — skipping connection');
return;
}
const options: MqttConnectionOptions = {
host,
port: (this.homey.settings.get('mqtt_port') as number) || 1883,
username: this.homey.settings.get('mqtt_username') as string | undefined,
password: this.homey.settings.get('mqtt_password') as string | undefined,
tls: (this.homey.settings.get('mqtt_tls') as boolean) || false,
verifyTls: (this.homey.settings.get('mqtt_verify_tls') as boolean) || false,
};
this.mqttClient.connect(options);
}
/**
* Route stat/tele messages to the matching paired device.
* Devices register themselves via `registerMessageHandler`.
*/
// eslint-disable-next-line no-spaced-func, func-call-spacing
private messageHandlers = new Map<string, Set<(topic: string, payload: string) => void>>();
registerMessageHandler(deviceTopic: string, handler: (topic: string, payload: string) => void): void {
if (!this.messageHandlers.has(deviceTopic)) {
this.messageHandlers.set(deviceTopic, new Set());
}
this.messageHandlers.get(deviceTopic)!.add(handler);
}
unregisterMessageHandler(deviceTopic: string, handler: (topic: string, payload: string) => void): void {
const handlers = this.messageHandlers.get(deviceTopic);
if (handlers) {
handlers.delete(handler);
if (handlers.size === 0) {
this.messageHandlers.delete(deviceTopic);
}
}
}
private routeMessage(topic: string, payload: string): void {
const parts = topic.split('/');
if (parts.length < 3) return;
const deviceTopic = parts[1];
const handlers = this.messageHandlers.get(deviceTopic);
if (handlers) {
for (const handler of handlers) {
handler(topic, payload);
}
}
}
async onUninit(): Promise<void> {
this.mqttClient.disconnect();
}
}
module.exports = TasmotaMqttApp;