homebridge-slwf-01pro 0.5.2 → 0.5.4
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/CHANGELOG.md +26 -0
- package/README.md +3 -3
- package/config.schema.json +1 -1
- package/lib/DeviceAccessory.js +49 -15
- package/lib/esphome.js +32 -6
- package/lib/state.js +16 -4
- package/lib/stateManager.js +16 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ This package is a maintained fork of [`homebridge-esphome-ac`](https://github.co
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.5.4] — 2026-05-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Fault indicator (⚠️) no longer persists after a Homebridge restart.** Apple Home subscribes to HAP events asynchronously after the bridge starts; when a device had a fault from the previous session, the startup `StatusFault = NO_FAULT` push arrived before Apple Home subscribed and was silently missed, leaving the ⚠️ visible until the user manually interacted with the tile. A 3-second delayed re-push (`STARTUP_FAULT_CLEAR_DELAY_MS`) after accessory construction ensures the cleared state reaches Apple Home once it has subscribed.
|
|
15
|
+
- **Fault indicator no longer flashes on transient standby disconnects.** ESPHome dongles that briefly lose their TCP connection when the AC unit enters standby would immediately set `StatusFault = GENERAL_FAULT`. A 15-second grace period (`DISCONNECT_FAULT_DELAY_MS`) now absorbs brief drops: if the device reconnects within the window, no fault is ever shown. Persistent disconnects (> 15 s) still correctly surface the fault. Both new timers call `.unref()` so they don't prevent clean process exit.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [0.5.3] — 2026-05-06
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Heat-only / auto-only devices no longer restore an unsupported COOL mode when turned back on.** `chooseInitialTargetMode` now considers `supportedModesList`, and `Active` uses that helper for cached restore modes too.
|
|
24
|
+
- **External or stale HomeKit writes for unsupported HEAT/COOL target states are ignored** instead of sending unsupported ESPHome modes.
|
|
25
|
+
- **Newly-enabled companion services on cached accessories now get `ConfiguredName` seeded.** Existing Apple Home renames are still preserved, but services that did not exist in the cache yet no longer start with a blank ConfiguredName.
|
|
26
|
+
- **Hostless manual device entries are non-destructive.** Empty Homebridge UI form scaffolding is dropped before orchestration, and real but invalid manual entries now keep cached accessories instead of allowing orphan pruning while the config is incomplete.
|
|
27
|
+
- **Main current-temperature updates are clamped to the HAP-safe range** before writing to `CurrentTemperature`.
|
|
28
|
+
- **Linked services are not re-linked redundantly** when the service is already present in HAP-NodeJS's `linkedServices` list.
|
|
29
|
+
|
|
30
|
+
### Internal
|
|
31
|
+
|
|
32
|
+
- Added regression coverage for capability-aware restore modes, cached-service `ConfiguredName` seeding, current-temperature clamping, manual-device filtering, and invalid-config prune protection. 159 tests total.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
10
36
|
## [0.5.2] — 2026-05-06
|
|
11
37
|
|
|
12
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ The upstream plugin works for single-device setups but has a few sharp edges tha
|
|
|
15
15
|
- **Multi-device command coalescing.** A module-level send timeout meant changing one AC could cancel a pending command on another. Each AC now owns its own debouncer.
|
|
16
16
|
- **Crash on disconnected device.** The upstream `device disconnected` error path referenced an undefined `log` symbol → `ReferenceError` instead of a clean HomeKit error. Fixed.
|
|
17
17
|
- **Auto-discovery via mDNS** — listed `_esphomelib._tcp` services are picked up automatically; you don't need to type six IP addresses for six ACs.
|
|
18
|
-
- **Multi-entity bundling** — humidity, outdoor temperature, power consumption, beeper, display toggle, DRY, and FAN_ONLY
|
|
18
|
+
- **Multi-entity bundling** — humidity, outdoor temperature, power consumption, beeper, display toggle, DRY, and FAN_ONLY can be exposed when the device supports them; fresh installs hide the extras by default for a clean Apple Home pairing.
|
|
19
19
|
|
|
20
20
|
See [CHANGELOG.md](CHANGELOG.md) for the full restoration story.
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ Anything that publishes a `Climate` entity over the ESPHome native API will work
|
|
|
30
30
|
| Alpine, Pioneer, Samsung, Toshiba, Zanussi | ~30 brands total — see [smartlight.me](https://smartlight.me/smart-home-devices/wifi-devices/wifi-dongle-air-conditioners-midea-idea-electrolux-for-home-assistant) for the full list |
|
|
31
31
|
| Newer 2024+ AC firmwares with proprietary protocols | May not work — check the manufacturer's compatibility notes before buying |
|
|
32
32
|
|
|
33
|
-
DRY and FAN_ONLY ESPHome modes
|
|
33
|
+
DRY and FAN_ONLY ESPHome modes can be surfaced as **companion `Switch` services** ("AC Dry", "AC Fan Only") next to the main `HeaterCooler` tile. Set `disableDryMode: false` / `disableFanOnlyMode: false` to opt in. Toggling one ON puts the AC into that mode; toggling OFF restores the previous primary mode (HEAT/COOL/AUTO).
|
|
34
34
|
|
|
35
35
|
ESPHome's **custom fan modes** (`silent`, `turbo` on Midea) and **presets** (`eco`, `boost`, `sleep`, `away`) are not yet surfaced — the plugin only handles the standard fan-modes list. See [ROADMAP.md](ROADMAP.md).
|
|
36
36
|
|
|
@@ -291,7 +291,7 @@ git clone https://github.com/nookied/homebridge-SLWF-01Pro.git
|
|
|
291
291
|
cd homebridge-SLWF-01Pro
|
|
292
292
|
npm install
|
|
293
293
|
npm run lint # ESLint
|
|
294
|
-
npm test # Jest —
|
|
294
|
+
npm test # Jest — 159 unit tests across 8 suites
|
|
295
295
|
node -e "require('./index.js')" # smoke test (loads cleanly)
|
|
296
296
|
```
|
|
297
297
|
|
package/config.schema.json
CHANGED
package/lib/DeviceAccessory.js
CHANGED
|
@@ -26,6 +26,8 @@ let Characteristic;
|
|
|
26
26
|
const { UUID_NAMESPACE, ACCESSORY_SCHEMA_VERSION } = require('./constants');
|
|
27
27
|
|
|
28
28
|
const SET_DEBOUNCE_MS = 600;
|
|
29
|
+
const DISCONNECT_FAULT_DELAY_MS = 15000;
|
|
30
|
+
const STARTUP_FAULT_CLEAR_DELAY_MS = 3000;
|
|
29
31
|
const PRIMARY_MODES = [ESP_MODE.COOL, ESP_MODE.HEAT, ESP_MODE.AUTO, ESP_MODE.HEAT_COOL];
|
|
30
32
|
|
|
31
33
|
const DEFAULT_VISUAL_MIN_TEMP = 16;
|
|
@@ -119,7 +121,7 @@ class DeviceAccessory {
|
|
|
119
121
|
this.accessory.context.schemaVersion = ACCESSORY_SCHEMA_VERSION;
|
|
120
122
|
this.accessory.context.deviceId = this.id;
|
|
121
123
|
this.accessory.context.host = this.host;
|
|
122
|
-
this.accessory.context.lastTargetState = chooseInitialTargetMode(this.state.mode);
|
|
124
|
+
this.accessory.context.lastTargetState = chooseInitialTargetMode(this.state.mode, this.config.supportedModesList);
|
|
123
125
|
platform.accessories.push(this.accessory);
|
|
124
126
|
this.api.registerPlatformAccessories(platform.PLUGIN_NAME, platform.PLATFORM_NAME, [this.accessory]);
|
|
125
127
|
} else {
|
|
@@ -128,7 +130,7 @@ class DeviceAccessory {
|
|
|
128
130
|
this.accessory.context.schemaVersion = ACCESSORY_SCHEMA_VERSION;
|
|
129
131
|
this.accessory.context.host = this.host;
|
|
130
132
|
if (PRIMARY_MODES.includes(this.state.mode)) {
|
|
131
|
-
this.accessory.context.lastTargetState = this.state.mode;
|
|
133
|
+
this.accessory.context.lastTargetState = chooseInitialTargetMode(this.state.mode, this.config.supportedModesList);
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
@@ -145,6 +147,19 @@ class DeviceAccessory {
|
|
|
145
147
|
|
|
146
148
|
this.esphome.on('state', this.updateClimateState.bind(this));
|
|
147
149
|
this.attachOptionalEntityListeners();
|
|
150
|
+
|
|
151
|
+
// Apple Home subscribes to HAP events asynchronously after startup.
|
|
152
|
+
// Re-push the cleared fault state after a short delay so it isn't
|
|
153
|
+
// missed if Apple Home hadn't subscribed when addClimateService ran.
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
if (this.connected !== false && this.HeaterCoolerService && Characteristic) {
|
|
156
|
+
try {
|
|
157
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusActive).updateValue(true);
|
|
158
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusFault)
|
|
159
|
+
.updateValue(Characteristic.StatusFault.NO_FAULT);
|
|
160
|
+
} catch (_e) { /* characteristic update on startup */ }
|
|
161
|
+
}
|
|
162
|
+
}, STARTUP_FAULT_CLEAR_DELAY_MS).unref();
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
linkOptionalServices() {
|
|
@@ -161,6 +176,7 @@ class DeviceAccessory {
|
|
|
161
176
|
for (const svc of candidates) {
|
|
162
177
|
if (!svc) continue;
|
|
163
178
|
try {
|
|
179
|
+
if (Array.isArray(this.HeaterCoolerService.linkedServices) && this.HeaterCoolerService.linkedServices.includes(svc)) continue;
|
|
164
180
|
this.HeaterCoolerService.addLinkedService(svc);
|
|
165
181
|
} catch (err) {
|
|
166
182
|
this.log.easyDebug(`linkOptionalServices: could not link ${svc.displayName}: ${err.message || err}`);
|
|
@@ -201,11 +217,10 @@ class DeviceAccessory {
|
|
|
201
217
|
if (service.testCharacteristic && !service.testCharacteristic(Characteristic.ConfiguredName)) {
|
|
202
218
|
service.addOptionalCharacteristic(Characteristic.ConfiguredName);
|
|
203
219
|
}
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
if (!this.isNewAccessory) return;
|
|
220
|
+
const configuredName = service.getCharacteristic(Characteristic.ConfiguredName);
|
|
221
|
+
// Preserve user-chosen names on cached services, but still seed ConfiguredName
|
|
222
|
+
// when a newly-enabled companion service didn't exist in the cache yet.
|
|
223
|
+
if (!this.isNewAccessory && typeof configuredName.value === 'string' && configuredName.value.length > 0) return;
|
|
209
224
|
service.setCharacteristic(Characteristic.ConfiguredName, name);
|
|
210
225
|
} catch (err) {
|
|
211
226
|
this.log.easyDebug(`setConfiguredName(${name}) failed: ${err.message || err}`);
|
|
@@ -270,7 +285,7 @@ class DeviceAccessory {
|
|
|
270
285
|
const visualStep = isPresent(this.config.visualTargetTemperatureStep) ? this.config.visualTargetTemperatureStep : DEFAULT_VISUAL_TEMP_STEP;
|
|
271
286
|
|
|
272
287
|
const safeCurrent = isPresent(this.state.currentTemperature)
|
|
273
|
-
? this.state.currentTemperature
|
|
288
|
+
? clampRange(this.state.currentTemperature, -100, 100)
|
|
274
289
|
: (visualMin + visualMax) / 2;
|
|
275
290
|
this.HeaterCoolerService.getCharacteristic(Characteristic.CurrentTemperature)
|
|
276
291
|
.setProps({ minValue: -100, maxValue: 100, minStep: 0.1 })
|
|
@@ -322,12 +337,31 @@ class DeviceAccessory {
|
|
|
322
337
|
setConnectedStatus(connected) {
|
|
323
338
|
this.connected = connected;
|
|
324
339
|
if (!this.HeaterCoolerService || !Characteristic) return;
|
|
325
|
-
|
|
326
|
-
this.
|
|
327
|
-
|
|
328
|
-
.
|
|
329
|
-
|
|
330
|
-
|
|
340
|
+
if (connected) {
|
|
341
|
+
if (this._disconnectFaultTimer) {
|
|
342
|
+
clearTimeout(this._disconnectFaultTimer);
|
|
343
|
+
this._disconnectFaultTimer = null;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusActive).updateValue(true);
|
|
347
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusFault)
|
|
348
|
+
.updateValue(Characteristic.StatusFault.NO_FAULT);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
this.log.easyDebug(`setConnectedStatus failed for ${this.name}: ${err.message || err}`);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
this._disconnectFaultTimer = setTimeout(() => {
|
|
354
|
+
this._disconnectFaultTimer = null;
|
|
355
|
+
if (!this.connected) {
|
|
356
|
+
try {
|
|
357
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusActive).updateValue(false);
|
|
358
|
+
this.HeaterCoolerService.getCharacteristic(Characteristic.StatusFault)
|
|
359
|
+
.updateValue(Characteristic.StatusFault.GENERAL_FAULT);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
this.log.easyDebug(`setConnectedStatus failed for ${this.name}: ${err.message || err}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}, DISCONNECT_FAULT_DELAY_MS).unref();
|
|
331
365
|
}
|
|
332
366
|
}
|
|
333
367
|
|
|
@@ -587,7 +621,7 @@ class DeviceAccessory {
|
|
|
587
621
|
this.state = state;
|
|
588
622
|
this.syncModeSwitches(this.state.mode);
|
|
589
623
|
|
|
590
|
-
safeUpdate(this.HeaterCoolerService.getCharacteristic(Characteristic.CurrentTemperature), this.state.currentTemperature);
|
|
624
|
+
safeUpdate(this.HeaterCoolerService.getCharacteristic(Characteristic.CurrentTemperature), clampRange(this.state.currentTemperature, -100, 100));
|
|
591
625
|
|
|
592
626
|
if (this.state.mode === ESP_MODE.OFF) {
|
|
593
627
|
this.HeaterCoolerService.getCharacteristic(Characteristic.Active).updateValue(0);
|
package/lib/esphome.js
CHANGED
|
@@ -79,11 +79,7 @@ async function init() {
|
|
|
79
79
|
evictStaleSchemaAccessories(platform);
|
|
80
80
|
detectOrphanedAccessories(platform);
|
|
81
81
|
|
|
82
|
-
const manualDevices = (platform.devices || [])
|
|
83
|
-
...d,
|
|
84
|
-
port: d.port || 6053,
|
|
85
|
-
discovered: false,
|
|
86
|
-
}));
|
|
82
|
+
const { manualDevices, invalidManualDeviceCount } = collectManualDevices(platform.devices || [], platform.log);
|
|
87
83
|
|
|
88
84
|
const allDevices = manualDevices.slice();
|
|
89
85
|
let discoverySucceeded = !platform.autoDiscover;
|
|
@@ -111,6 +107,11 @@ async function init() {
|
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
if (allDevices.length === 0) {
|
|
110
|
+
if (invalidManualDeviceCount > 0) {
|
|
111
|
+
platform.log.warn('No valid manual device hosts were configured. Keeping cached accessories until the device list is fixed.');
|
|
112
|
+
if (platform.autoDiscover) pruneOrphanedAccessories(platform, []);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
114
115
|
if (manualDevices.length === 0 && (!platform.autoDiscover || discoverySucceeded)) {
|
|
115
116
|
platform.log('No SLWF-01Pro / ESPHome devices configured and none discovered. Plugin will idle.');
|
|
116
117
|
pruneOrphanedAccessories(platform, []);
|
|
@@ -135,9 +136,34 @@ async function init() {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
const liveHosts = allDevices.map(d => d.host).filter(Boolean);
|
|
139
|
+
if (invalidManualDeviceCount > 0 && !platform.autoDiscover) {
|
|
140
|
+
platform.log.warn('One or more manual device entries are missing a host. Skipping orphan pruning to avoid removing cached accessories while the config is invalid.');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
138
143
|
pruneOrphanedAccessories(platform, liveHosts);
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
function collectManualDevices(devices, log) {
|
|
147
|
+
const manualDevices = [];
|
|
148
|
+
let invalidManualDeviceCount = 0;
|
|
149
|
+
for (const d of devices) {
|
|
150
|
+
const device = {
|
|
151
|
+
...d,
|
|
152
|
+
port: d.port || 6053,
|
|
153
|
+
discovered: false,
|
|
154
|
+
};
|
|
155
|
+
if (device.host) {
|
|
156
|
+
manualDevices.push(device);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (looksLikeRealEntry(device)) {
|
|
160
|
+
invalidManualDeviceCount++;
|
|
161
|
+
log.warn(`Skipping device "${device.name || '(unnamed)'}" — no host configured.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { manualDevices, invalidManualDeviceCount };
|
|
165
|
+
}
|
|
166
|
+
|
|
141
167
|
function spawnClient(platform, device) {
|
|
142
168
|
const client = new Client({
|
|
143
169
|
host: device.host,
|
|
@@ -264,4 +290,4 @@ function pruneOrphanedAccessories(platform, liveHosts) {
|
|
|
264
290
|
}
|
|
265
291
|
}
|
|
266
292
|
|
|
267
|
-
module.exports = { init, pruneOrphanedAccessories, looksLikeRealEntry };
|
|
293
|
+
module.exports = { init, pruneOrphanedAccessories, looksLikeRealEntry, collectManualDevices };
|
package/lib/state.js
CHANGED
|
@@ -79,10 +79,6 @@ function fanModeToSpeed(fanMode, fanModesList) {
|
|
|
79
79
|
return Math.ceil((idx + 1) * (100 / fanModesList.length));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function chooseInitialTargetMode(stateMode) {
|
|
83
|
-
return [ESP_MODE.COOL, ESP_MODE.HEAT, ESP_MODE.AUTO, ESP_MODE.HEAT_COOL].includes(stateMode) ? stateMode : ESP_MODE.COOL;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
function nonEmpty(value) {
|
|
87
83
|
return typeof value === 'string' && value.length > 0;
|
|
88
84
|
}
|
|
@@ -106,6 +102,22 @@ function pickAutoMode(supportedModesList) {
|
|
|
106
102
|
return null;
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
function chooseInitialTargetMode(stateMode, supportedModesList) {
|
|
106
|
+
const supported = Array.isArray(supportedModesList) ? supportedModesList : null;
|
|
107
|
+
const isSupported = mode => !supported || supported.includes(mode);
|
|
108
|
+
|
|
109
|
+
if ([ESP_MODE.COOL, ESP_MODE.HEAT, ESP_MODE.AUTO, ESP_MODE.HEAT_COOL].includes(stateMode) && isSupported(stateMode)) {
|
|
110
|
+
return stateMode;
|
|
111
|
+
}
|
|
112
|
+
if (supported) {
|
|
113
|
+
if (supported.includes(ESP_MODE.COOL)) return ESP_MODE.COOL;
|
|
114
|
+
if (supported.includes(ESP_MODE.HEAT)) return ESP_MODE.HEAT;
|
|
115
|
+
const autoMode = pickAutoMode(supported);
|
|
116
|
+
if (autoMode !== null) return autoMode;
|
|
117
|
+
}
|
|
118
|
+
return ESP_MODE.COOL;
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
function supportsCool(supportedModesList) {
|
|
110
122
|
if (!supportedModesList) return false;
|
|
111
123
|
return supportedModesList.includes(ESP_MODE.COOL)
|
package/lib/stateManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { fanSpeedToFanMode, pickAutoMode, ESP_MODE, HK_TARGET } = require('./state');
|
|
1
|
+
const { fanSpeedToFanMode, pickAutoMode, chooseInitialTargetMode, ESP_MODE, HK_TARGET } = require('./state');
|
|
2
2
|
|
|
3
3
|
const ACTIVE_BATCH_MS = 100;
|
|
4
4
|
const TEMP_BATCH_MS = 50;
|
|
@@ -70,7 +70,9 @@ module.exports = {
|
|
|
70
70
|
const wantOn = !!active;
|
|
71
71
|
const isOn = this.state.mode != null && this.state.mode !== ESP_MODE.OFF;
|
|
72
72
|
if (wantOn === isOn) return resolve();
|
|
73
|
-
this.state.mode = wantOn
|
|
73
|
+
this.state.mode = wantOn
|
|
74
|
+
? chooseInitialTargetMode(this.accessory.context.lastTargetState, this.config.supportedModesList)
|
|
75
|
+
: ESP_MODE.OFF;
|
|
74
76
|
markDirty(this, 'mode');
|
|
75
77
|
this.log(`${this.name} - Setting AC Active to ${wantOn ? 'ON' : 'OFF'}`);
|
|
76
78
|
sendState(this).then(resolve).catch(reject);
|
|
@@ -80,16 +82,25 @@ module.exports = {
|
|
|
80
82
|
|
|
81
83
|
TargetHeaterCoolerState(hkTarget) {
|
|
82
84
|
return new Promise((resolve, reject) => {
|
|
85
|
+
const supportedModes = Array.isArray(this.config.supportedModesList) ? this.config.supportedModesList : [];
|
|
83
86
|
let espMode;
|
|
84
87
|
let logMode;
|
|
85
88
|
switch (hkTarget) {
|
|
86
89
|
case HK_TARGET.AUTO:
|
|
87
|
-
espMode = pickAutoMode(
|
|
90
|
+
espMode = pickAutoMode(supportedModes);
|
|
88
91
|
if (espMode === null) return resolve();
|
|
89
92
|
logMode = espMode === ESP_MODE.HEAT_COOL ? 'HEAT_COOL' : 'AUTO';
|
|
90
93
|
break;
|
|
91
|
-
case HK_TARGET.HEAT:
|
|
92
|
-
|
|
94
|
+
case HK_TARGET.HEAT:
|
|
95
|
+
if (!supportedModes.includes(ESP_MODE.HEAT)) return resolve();
|
|
96
|
+
espMode = ESP_MODE.HEAT;
|
|
97
|
+
logMode = 'HEAT';
|
|
98
|
+
break;
|
|
99
|
+
case HK_TARGET.COOL:
|
|
100
|
+
if (!supportedModes.includes(ESP_MODE.COOL)) return resolve();
|
|
101
|
+
espMode = ESP_MODE.COOL;
|
|
102
|
+
logMode = 'COOL';
|
|
103
|
+
break;
|
|
93
104
|
default: return resolve();
|
|
94
105
|
}
|
|
95
106
|
if (this.state.mode === espMode) return resolve();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-slwf-01pro",
|
|
3
3
|
"description": "Homebridge plugin for the SMLIGHT SLWF-01Pro Wi-Fi AC dongle. Auto-discovery, multi-entity bundling, and full Midea-protocol support over the ESPHome native API.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/nookied/homebridge-SLWF-01Pro.git"
|