homebridge-tasmota-discovery 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "extends": [
4
+ "eslint:recommended",
5
+ "plugin:@typescript-eslint/recommended"
6
+ ],
7
+ "parserOptions": {
8
+ "ecmaVersion": 2022,
9
+ "sourceType": "module"
10
+ },
11
+ "ignorePatterns": ["dist"]
12
+ }
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # homebridge-tasmota-discovery
2
+
3
+ > Automatic Tasmota device discovery for [Homebridge](https://homebridge.io) via MQTT — works like Zigbee2MQTT but for Tasmota devices.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/homebridge-tasmota-discovery)](https://www.npmjs.com/package/homebridge-tasmota-discovery)
6
+ [![homebridge verified](https://img.shields.io/badge/homebridge-verified-blueviolet?logo=homebridge)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ ---
10
+
11
+ ## How it works
12
+
13
+ 1. When Tasmota boots it publishes a **retained** discovery payload on `tasmota/discovery/{MAC}/config`.
14
+ 2. This plugin subscribes to that topic and **auto-registers** every device in Apple Home — no manual config per device.
15
+ 3. If the device IP changes (DHCP reassignment) the plugin detects it on the next boot and updates the accessory Serial Number automatically.
16
+
17
+ ---
18
+
19
+ ## Requirements
20
+
21
+ | Dependency | Version |
22
+ |---|---|
23
+ | Homebridge | ≥ 2.0.0 |
24
+ | Node.js | ≥ 18.15.0 |
25
+ | Tasmota firmware | ≥ 9.1.0 (MQTT Discovery supported) |
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install -g homebridge-tasmota-discovery
33
+ ```
34
+
35
+ Or install via the **Homebridge UI** (search for `homebridge-tasmota-discovery`).
36
+
37
+ ---
38
+
39
+ ## Tasmota device setup
40
+
41
+ MQTT Discovery must be enabled on each device. In the Tasmota console run:
42
+
43
+ ```
44
+ SetOption19 1
45
+ ```
46
+
47
+ Or navigate to **Configuration → MQTT** and make sure the **Discovery** option is enabled.
48
+
49
+ The device will then publish its discovery payload on `tasmota/discovery/{MAC}/config` (retained).
50
+
51
+ ---
52
+
53
+ ## Homebridge config
54
+
55
+ Add the platform block to your `config.json`:
56
+
57
+ ```json
58
+ {
59
+ "platforms": [
60
+ {
61
+ "platform": "TasmotaDiscovery",
62
+ "name": "TasmotaDiscovery",
63
+ "mqttHost": "192.168.1.10",
64
+ "mqttPort": 1883,
65
+ "mqttUsername": "myuser",
66
+ "mqttPassword": "mypassword"
67
+ }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ | Key | Required | Default | Description |
73
+ |---|---|---|---|
74
+ | `mqttHost` | ✅ | — | MQTT broker hostname or IP |
75
+ | `mqttPort` | | 1883 | MQTT broker port |
76
+ | `mqttUsername` | | — | MQTT auth username |
77
+ | `mqttPassword` | | — | MQTT auth password |
78
+ | `mqttTls` | | false | Connect with TLS (port 8883) |
79
+
80
+ ---
81
+
82
+ ## Supported device types
83
+
84
+ | Tasmota `rl` value | HomeKit type |
85
+ |---|---|
86
+ | `1` — Relay/switch | **Switch** service |
87
+ | — `lt_st: 1` | **Lightbulb** (on/off + brightness) |
88
+ | — `lt_st: 2` | **Lightbulb** (on/off + brightness + color temperature) |
89
+ | — `lt_st: 3` | **Lightbulb** (on/off + brightness + hue + saturation) |
90
+ | — `lt_st: 4-5` | **Lightbulb** (full RGBW/RGBWW: brightness + CT + hue + saturation) |
91
+
92
+ Multi-relay devices (e.g. Sonoff 4CH) are represented as a **single accessory with multiple Switch services**, one per relay.
93
+
94
+ ---
95
+
96
+ ## Accessory metadata
97
+
98
+ | HomeKit field | Source |
99
+ |---|---|
100
+ | **Manufacturer** | `"Tasmota"` (fixed) |
101
+ | **Model** | `md` field from discovery payload |
102
+ | **Serial Number** | Device IP address (`ip` field) |
103
+ | **Firmware** | Tasmota firmware version (`sw` field) |
104
+ | **Name** | Display name (`dn` field) |
105
+
106
+ ---
107
+
108
+ ## MQTT topics used
109
+
110
+ | Topic | Direction | Purpose |
111
+ |---|---|---|
112
+ | `tasmota/discovery/+/config` | Subscribe | Device discovery (retained) |
113
+ | `tele/+/LWT` | Subscribe | Online / Offline status |
114
+ | `tele/+/STATE` | Subscribe | Periodic state updates |
115
+ | `stat/+/RESULT` | Subscribe | Command acknowledgements |
116
+ | `stat/+/POWER[N]` | Subscribe | Direct power state responses |
117
+ | `cmnd/{topic}/POWER[N]` | Publish | Switch on/off |
118
+ | `cmnd/{topic}/Dimmer` | Publish | Light brightness |
119
+ | `cmnd/{topic}/CT` | Publish | Light colour temperature |
120
+ | `cmnd/{topic}/HSBColor` | Publish | Light hue/saturation/brightness |
121
+ | `cmnd/{topic}/STATE` | Publish | Request current state on startup |
122
+
123
+ ---
124
+
125
+ ## Building from source
126
+
127
+ ```bash
128
+ git clone https://github.com/youruser/homebridge-tasmota-discovery
129
+ cd homebridge-tasmota-discovery
130
+ npm install
131
+ npm run build
132
+ ```
133
+
134
+ ---
135
+
136
+ ## License
137
+
138
+ MIT © 2025
@@ -0,0 +1,64 @@
1
+ {
2
+ "pluginAlias": "TasmotaDiscovery",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "headerDisplay": "Automatically discovers Tasmota devices via MQTT and adds them to Apple Home. Devices are picked up from the `tasmota/discovery/+/config` retained messages published by Tasmota's MQTT Discovery feature.",
6
+ "footerDisplay": "Enable MQTT Discovery in your Tasmota devices: **Configuration → MQTT → SetOption19 1** (or `SetOption19 1` in the console).",
7
+ "schema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "name": {
11
+ "title": "Platform Name",
12
+ "type": "string",
13
+ "default": "TasmotaDiscovery",
14
+ "required": true
15
+ },
16
+ "mqttHost": {
17
+ "title": "MQTT Broker Host",
18
+ "type": "string",
19
+ "required": true,
20
+ "description": "Hostname or IP address of your MQTT broker (e.g. 192.168.1.10 or localhost).",
21
+ "placeholder": "192.168.1.10"
22
+ },
23
+ "mqttPort": {
24
+ "title": "MQTT Broker Port",
25
+ "type": "integer",
26
+ "default": 1883,
27
+ "minimum": 1,
28
+ "maximum": 65535,
29
+ "description": "MQTT broker port. Default is 1883 (plain) or 8883 (TLS)."
30
+ },
31
+ "mqttUsername": {
32
+ "title": "MQTT Username",
33
+ "type": "string",
34
+ "description": "Optional MQTT broker username."
35
+ },
36
+ "mqttPassword": {
37
+ "title": "MQTT Password",
38
+ "type": "string",
39
+ "description": "Optional MQTT broker password."
40
+ },
41
+ "mqttTls": {
42
+ "title": "Use TLS (mqtts://)",
43
+ "type": "boolean",
44
+ "default": false,
45
+ "description": "Enable TLS for the MQTT connection."
46
+ }
47
+ },
48
+ "required": ["mqttHost"]
49
+ },
50
+ "layout": [
51
+ {
52
+ "type": "fieldset",
53
+ "title": "MQTT Broker",
54
+ "items": ["mqttHost", "mqttPort", "mqttTls"]
55
+ },
56
+ {
57
+ "type": "fieldset",
58
+ "title": "Authentication (optional)",
59
+ "expandable": true,
60
+ "expanded": false,
61
+ "items": ["mqttUsername", "mqttPassword"]
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,61 @@
1
+ import { PlatformAccessory } from 'homebridge';
2
+ import { TasmotaDiscoveryPlatform } from './platform.js';
3
+ import { TasmotaDiscoveryConfig } from './tasmota-types.js';
4
+ /**
5
+ * TasmotaAccessory
6
+ *
7
+ * Wraps a single Tasmota device as a HomeKit accessory.
8
+ * Supports:
9
+ * - Multiple relay/switch channels (rl[i] === 1)
10
+ * - Single-channel light (lt_st === 1) with Brightness
11
+ * - Multi-channel lights (lt_st 2-5): On + Brightness + ColorTemperature/Hue/Saturation
12
+ *
13
+ * POWER commands:
14
+ * Single relay → cmnd/{t}/POWER
15
+ * Multi relay → cmnd/{t}/POWER{N} (1-based)
16
+ */
17
+ export declare class TasmotaAccessory {
18
+ private readonly platform;
19
+ private readonly accessory;
20
+ /** Switch services keyed by 0-based relay index */
21
+ private switchServices;
22
+ /** The Lightbulb service (if device has a light) */
23
+ private lightService?;
24
+ /** Runtime power states keyed by 0-based relay index */
25
+ private relayStates;
26
+ /** 0-based indices of active relays */
27
+ private relayIndices;
28
+ private lightOn;
29
+ private lightBri;
30
+ private lightCT;
31
+ private lightHue;
32
+ private lightSat;
33
+ private config;
34
+ constructor(platform: TasmotaDiscoveryPlatform, accessory: PlatformAccessory, config: TasmotaDiscoveryConfig);
35
+ private setupAccessoryInfo;
36
+ private setupSwitchServices;
37
+ private relayFriendlyName;
38
+ private setRelay;
39
+ /**
40
+ * lt_st values:
41
+ * 1 = single channel (white / dimmable)
42
+ * 2 = cold + warm white
43
+ * 3 = RGB
44
+ * 4 = RGBW
45
+ * 5 = RGBWW (RGB + cold + warm white)
46
+ */
47
+ private setupLightService;
48
+ private sendHSBColor;
49
+ /** Called when the device IP or firmware version changes */
50
+ updateConfig(newCfg: TasmotaDiscoveryConfig): void;
51
+ /** Update reachability status (LWT Online / Offline) */
52
+ setOnlineStatus(online: boolean): void;
53
+ /**
54
+ * Update switch/light state from any MQTT state message
55
+ * (tele/STATE, stat/RESULT, stat/POWER[N])
56
+ */
57
+ updateState(state: Record<string, unknown>): void;
58
+ /** The Tasmota device topic (e.g. "sonoff-cabinet-compresor") */
59
+ getTopic(): string;
60
+ }
61
+ //# sourceMappingURL=accessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessory.d.ts","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAgB,MAAM,oBAAoB,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,qBAAa,gBAAgB;IAoBzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IApB5B,mDAAmD;IACnD,OAAO,CAAC,cAAc,CAAmC;IACzD,oDAAoD;IACpD,OAAO,CAAC,YAAY,CAAC,CAAU;IAE/B,wDAAwD;IACxD,OAAO,CAAC,WAAW,CAAmC;IACtD,uCAAuC;IACvC,OAAO,CAAC,YAAY,CAAgB;IAEpC,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IAEtB,OAAO,CAAC,MAAM,CAAyB;gBAGpB,QAAQ,EAAE,wBAAwB,EAClC,SAAS,EAAE,iBAAiB,EAC7C,MAAM,EAAE,sBAAsB;IAYhC,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,mBAAmB;IA+C3B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,QAAQ;IAWhB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAsDzB,OAAO,CAAC,YAAY;IAWpB,4DAA4D;IACrD,YAAY,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAwBzD,wDAAwD;IACjD,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAkB7C;;;OAGG;IACI,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAqDxD,iEAAiE;IAC1D,QAAQ,IAAI,MAAM;CAG1B"}
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TasmotaAccessory = void 0;
4
+ /**
5
+ * TasmotaAccessory
6
+ *
7
+ * Wraps a single Tasmota device as a HomeKit accessory.
8
+ * Supports:
9
+ * - Multiple relay/switch channels (rl[i] === 1)
10
+ * - Single-channel light (lt_st === 1) with Brightness
11
+ * - Multi-channel lights (lt_st 2-5): On + Brightness + ColorTemperature/Hue/Saturation
12
+ *
13
+ * POWER commands:
14
+ * Single relay → cmnd/{t}/POWER
15
+ * Multi relay → cmnd/{t}/POWER{N} (1-based)
16
+ */
17
+ class TasmotaAccessory {
18
+ platform;
19
+ accessory;
20
+ /** Switch services keyed by 0-based relay index */
21
+ switchServices = new Map();
22
+ /** The Lightbulb service (if device has a light) */
23
+ lightService;
24
+ /** Runtime power states keyed by 0-based relay index */
25
+ relayStates = new Map();
26
+ /** 0-based indices of active relays */
27
+ relayIndices = [];
28
+ lightOn = false;
29
+ lightBri = 100; // 0-100
30
+ lightCT = 327; // mireds (warm white default)
31
+ lightHue = 0;
32
+ lightSat = 0;
33
+ config;
34
+ constructor(platform, accessory, config) {
35
+ this.platform = platform;
36
+ this.accessory = accessory;
37
+ this.config = config;
38
+ this.setupAccessoryInfo();
39
+ this.setupSwitchServices();
40
+ if (config.lt_st > 0) {
41
+ this.setupLightService();
42
+ }
43
+ }
44
+ // ─── Accessory Information ─────────────────────────────────────────────────
45
+ setupAccessoryInfo() {
46
+ const { Service: S, Characteristic: C } = this.platform;
47
+ const info = this.accessory.getService(S.AccessoryInformation)
48
+ ?? this.accessory.addService(S.AccessoryInformation);
49
+ info
50
+ .setCharacteristic(C.Manufacturer, 'Tasmota')
51
+ .setCharacteristic(C.Model, this.config.md)
52
+ .setCharacteristic(C.SerialNumber, this.config.ip)
53
+ .setCharacteristic(C.FirmwareRevision, this.config.sw)
54
+ .setCharacteristic(C.Name, this.config.dn);
55
+ }
56
+ // ─── Switch Services ───────────────────────────────────────────────────────
57
+ setupSwitchServices() {
58
+ const { Service: S, Characteristic: C } = this.platform;
59
+ // Collect active relay indices (rl value 1 = relay)
60
+ this.relayIndices = this.config.rl
61
+ .map((v, i) => ({ v, i }))
62
+ .filter(({ v }) => v === 1)
63
+ .map(({ i }) => i);
64
+ const isSingle = this.relayIndices.length === 1;
65
+ for (const idx of this.relayIndices) {
66
+ const relayNum = idx + 1; // 1-based
67
+ const name = this.relayFriendlyName(idx);
68
+ const subtype = `relay_${relayNum}`;
69
+ let svc;
70
+ if (isSingle) {
71
+ svc = this.accessory.getService(S.Switch)
72
+ ?? this.accessory.addService(S.Switch, name);
73
+ }
74
+ else {
75
+ svc = this.accessory.getServiceById(S.Switch, subtype)
76
+ ?? this.accessory.addService(S.Switch, name, subtype);
77
+ }
78
+ svc.setCharacteristic(C.Name, name);
79
+ svc.getCharacteristic(C.On)
80
+ .onGet(() => this.relayStates.get(idx) ?? false)
81
+ .onSet((value) => this.setRelay(idx, value));
82
+ this.switchServices.set(idx, svc);
83
+ this.relayStates.set(idx, false);
84
+ }
85
+ // Remove stale Switch services from cache
86
+ this.accessory.services
87
+ .filter(s => s.UUID === S.Switch.UUID && s.subtype)
88
+ .forEach(s => {
89
+ const n = parseInt((s.subtype ?? '').replace('relay_', ''));
90
+ if (!this.relayIndices.includes(n - 1)) {
91
+ this.platform.log.debug(`[${this.config.dn}] Removing stale switch service: ${s.subtype}`);
92
+ this.accessory.removeService(s);
93
+ }
94
+ });
95
+ }
96
+ relayFriendlyName(idx) {
97
+ const fn = this.config.fn[idx];
98
+ if (fn && fn.trim().length > 0)
99
+ return fn.trim();
100
+ if (this.relayIndices.length === 1)
101
+ return this.config.dn;
102
+ return `${this.config.dn} ${idx + 1}`;
103
+ }
104
+ setRelay(idx, on) {
105
+ const relayNum = idx + 1;
106
+ // Single relay: use "POWER"; multi: "POWER1", "POWER2", etc.
107
+ const cmd = this.relayIndices.length === 1 ? 'POWER' : `POWER${relayNum}`;
108
+ this.platform.log.info(`[${this.config.dn}] ${cmd} → ${on ? 'ON' : 'OFF'}`);
109
+ this.platform.publishCommand(this.config.t, cmd, on ? 'ON' : 'OFF');
110
+ this.relayStates.set(idx, on);
111
+ }
112
+ // ─── Light Service ─────────────────────────────────────────────────────────
113
+ /**
114
+ * lt_st values:
115
+ * 1 = single channel (white / dimmable)
116
+ * 2 = cold + warm white
117
+ * 3 = RGB
118
+ * 4 = RGBW
119
+ * 5 = RGBWW (RGB + cold + warm white)
120
+ */
121
+ setupLightService() {
122
+ const { Service: S, Characteristic: C } = this.platform;
123
+ const name = `${this.config.dn} Light`;
124
+ this.lightService = this.accessory.getService(S.Lightbulb)
125
+ ?? this.accessory.addService(S.Lightbulb, name, 'light');
126
+ this.lightService.setCharacteristic(C.Name, name);
127
+ // On/Off (always present)
128
+ this.lightService.getCharacteristic(C.On)
129
+ .onGet(() => this.lightOn)
130
+ .onSet((v) => {
131
+ this.lightOn = v;
132
+ this.platform.publishCommand(this.config.t, 'POWER', this.lightOn ? 'ON' : 'OFF');
133
+ });
134
+ // Brightness (all light types)
135
+ this.lightService.getCharacteristic(C.Brightness)
136
+ .onGet(() => this.lightBri)
137
+ .onSet((v) => {
138
+ this.lightBri = v;
139
+ this.platform.publishCommand(this.config.t, 'Dimmer', String(this.lightBri));
140
+ });
141
+ // Color Temperature (lt_st 2, 4, 5 — cold/warm channels)
142
+ if (this.config.lt_st === 2 || this.config.lt_st >= 4) {
143
+ this.lightService.getCharacteristic(C.ColorTemperature)
144
+ .setProps({ minValue: 153, maxValue: 500 }) // 153 = cool white, 500 = warm white
145
+ .onGet(() => this.lightCT)
146
+ .onSet((v) => {
147
+ this.lightCT = v;
148
+ this.platform.publishCommand(this.config.t, 'CT', String(this.lightCT));
149
+ });
150
+ }
151
+ // Hue + Saturation (lt_st 3, 4, 5 — has RGB channels)
152
+ if (this.config.lt_st >= 3) {
153
+ this.lightService.getCharacteristic(C.Hue)
154
+ .onGet(() => this.lightHue)
155
+ .onSet((v) => {
156
+ this.lightHue = v;
157
+ this.sendHSBColor();
158
+ });
159
+ this.lightService.getCharacteristic(C.Saturation)
160
+ .onGet(() => this.lightSat)
161
+ .onSet((v) => {
162
+ this.lightSat = v;
163
+ this.sendHSBColor();
164
+ });
165
+ }
166
+ }
167
+ sendHSBColor() {
168
+ // Tasmota accepts HSBColor as "hue,sat,bri"
169
+ this.platform.publishCommand(this.config.t, 'HSBColor', `${Math.round(this.lightHue)},${Math.round(this.lightSat)},${Math.round(this.lightBri)}`);
170
+ }
171
+ // ─── Public API (called by platform) ──────────────────────────────────────
172
+ /** Called when the device IP or firmware version changes */
173
+ updateConfig(newCfg) {
174
+ const { Service: S, Characteristic: C } = this.platform;
175
+ if (newCfg.ip !== this.config.ip) {
176
+ this.platform.log.info(`[${this.config.dn}] IP changed: ${this.config.ip} → ${newCfg.ip}`);
177
+ this.accessory.getService(S.AccessoryInformation)
178
+ ?.setCharacteristic(C.SerialNumber, newCfg.ip);
179
+ }
180
+ if (newCfg.sw !== this.config.sw) {
181
+ this.accessory.getService(S.AccessoryInformation)
182
+ ?.setCharacteristic(C.FirmwareRevision, newCfg.sw);
183
+ }
184
+ if (newCfg.md !== this.config.md) {
185
+ this.accessory.getService(S.AccessoryInformation)
186
+ ?.setCharacteristic(C.Model, newCfg.md);
187
+ }
188
+ this.config = newCfg;
189
+ }
190
+ /** Update reachability status (LWT Online / Offline) */
191
+ setOnlineStatus(online) {
192
+ this.platform.log.info(`[${this.config.dn}] ${online ? 'Online' : 'Offline'}`);
193
+ // Set StatusFault on all Switch services to indicate device offline
194
+ const { Characteristic: C } = this.platform;
195
+ const fault = online
196
+ ? C.StatusFault.NO_FAULT
197
+ : C.StatusFault.GENERAL_FAULT;
198
+ for (const svc of this.switchServices.values()) {
199
+ if (svc.testCharacteristic(C.StatusFault)) {
200
+ svc.getCharacteristic(C.StatusFault).updateValue(fault);
201
+ }
202
+ }
203
+ if (this.lightService?.testCharacteristic(C.StatusFault)) {
204
+ this.lightService.getCharacteristic(C.StatusFault).updateValue(fault);
205
+ }
206
+ }
207
+ /**
208
+ * Update switch/light state from any MQTT state message
209
+ * (tele/STATE, stat/RESULT, stat/POWER[N])
210
+ */
211
+ updateState(state) {
212
+ const { Characteristic: C } = this.platform;
213
+ // ── Relay states ─────────────────────────────────────────────
214
+ for (const idx of this.relayIndices) {
215
+ const relayNum = idx + 1;
216
+ // Single relay may report POWER or POWER1
217
+ const keys = this.relayIndices.length === 1
218
+ ? ['POWER', 'POWER1']
219
+ : [`POWER${relayNum}`];
220
+ for (const key of keys) {
221
+ if (key in state) {
222
+ const on = state[key] === 'ON';
223
+ this.relayStates.set(idx, on);
224
+ this.switchServices.get(idx)
225
+ ?.getCharacteristic(C.On)
226
+ .updateValue(on);
227
+ this.platform.log.debug(`[${this.config.dn}] ${key} = ${on ? 'ON' : 'OFF'}`);
228
+ }
229
+ }
230
+ }
231
+ // ── Light state (only if device has a light) ──────────────────
232
+ if (this.lightService && this.config.lt_st > 0) {
233
+ const s = state;
234
+ if ('POWER' in s || 'POWER1' in s) {
235
+ this.lightOn = (s.POWER ?? s.POWER1) === 'ON';
236
+ this.lightService.getCharacteristic(C.On).updateValue(this.lightOn);
237
+ }
238
+ if (typeof s.Dimmer === 'number') {
239
+ this.lightBri = s.Dimmer;
240
+ this.lightService.getCharacteristic(C.Brightness).updateValue(this.lightBri);
241
+ }
242
+ if (typeof s.CT === 'number') {
243
+ this.lightCT = s.CT;
244
+ if (this.lightService.testCharacteristic(C.ColorTemperature)) {
245
+ this.lightService.getCharacteristic(C.ColorTemperature).updateValue(this.lightCT);
246
+ }
247
+ }
248
+ if (typeof s.HSBColor === 'string') {
249
+ const [h, sat, bri] = s.HSBColor.split(',').map(Number);
250
+ if (!isNaN(h)) {
251
+ this.lightHue = h;
252
+ this.lightService.getCharacteristic(C.Hue).updateValue(h);
253
+ }
254
+ if (!isNaN(sat)) {
255
+ this.lightSat = sat;
256
+ this.lightService.getCharacteristic(C.Saturation).updateValue(sat);
257
+ }
258
+ if (!isNaN(bri)) {
259
+ this.lightBri = bri;
260
+ this.lightService.getCharacteristic(C.Brightness).updateValue(bri);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ /** The Tasmota device topic (e.g. "sonoff-cabinet-compresor") */
266
+ getTopic() {
267
+ return this.config.t;
268
+ }
269
+ }
270
+ exports.TasmotaAccessory = TasmotaAccessory;
271
+ //# sourceMappingURL=accessory.js.map
@@ -0,0 +1,4 @@
1
+ import { API } from 'homebridge';
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;yBAIvB,KAAK,GAAG,KAAG,IAAI;AAAzB,kBAEE"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const platform_js_1 = require("./platform.js");
3
+ const settings_js_1 = require("./settings.js");
4
+ module.exports = (api) => {
5
+ api.registerPlatform(settings_js_1.PLUGIN_NAME, settings_js_1.PLATFORM_NAME, platform_js_1.TasmotaDiscoveryPlatform);
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,32 @@
1
+ import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';
2
+ import { TasmotaAccessory } from './accessory.js';
3
+ export declare class TasmotaDiscoveryPlatform implements DynamicPlatformPlugin {
4
+ readonly log: Logger;
5
+ readonly config: PlatformConfig;
6
+ readonly api: API;
7
+ readonly Service: typeof Service;
8
+ readonly Characteristic: typeof Characteristic;
9
+ /** Accessories restored from Homebridge cache, keyed by UUID */
10
+ readonly cachedAccessories: Map<string, PlatformAccessory>;
11
+ /** Live accessory handlers, keyed by MAC address (uppercase) */
12
+ readonly tasmotaAccessories: Map<string, TasmotaAccessory>;
13
+ private mqttClient?;
14
+ constructor(log: Logger, config: PlatformConfig, api: API);
15
+ /**
16
+ * Called by Homebridge for every accessory in the persistent cache.
17
+ * We store them so we can restore them when a discovery message arrives.
18
+ */
19
+ configureAccessory(accessory: PlatformAccessory): void;
20
+ private connectMqtt;
21
+ private handleMessage;
22
+ private handleDiscovery;
23
+ private findByTopic;
24
+ /**
25
+ * Publish a command to a Tasmota device.
26
+ * topic = device topic (e.g. "sonoff-cabinet-compresor")
27
+ * cmd = command name (e.g. "POWER", "POWER2", "STATE")
28
+ * value = payload (e.g. "ON", "OFF", "" for query)
29
+ */
30
+ publishCommand(topic: string, cmd: string, value: string): void;
31
+ }
32
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,cAAc,EACd,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACR,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD,qBAAa,wBAAyB,YAAW,qBAAqB;aAalD,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAd1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAC;IACxC,SAAgB,cAAc,EAAE,OAAO,cAAc,CAAC;IAEtD,gEAAgE;IAChE,SAAgB,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAa;IAE9E,gEAAgE;IAChE,SAAgB,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAa;IAE9E,OAAO,CAAC,UAAU,CAAC,CAAkB;gBAGnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAqB1B;;;OAGG;IACH,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAOtD,OAAO,CAAC,WAAW;IA4DnB,OAAO,CAAC,aAAa;IAiDrB,OAAO,CAAC,eAAe;IA6CvB,OAAO,CAAC,WAAW;IAOnB;;;;;OAKG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAQvE"}