* MIELHVAC New features and bug fixes
## Changelog
### Bug Fixes
**Memory leak in `miel_hvac_pre_init()`** — `goto del` jumped past the `free(sc)` label, leaking the allocated struct on serial init failure. Replaced with explicit `delete`/`free`/`return`.
**`remotetemp_clear` semantics inverted** — variable was `true` on boot causing a CLR frame to be sent before any sensor registered. Renamed to `remotetemp_active`, initialised `false`.
**`HVACSetPurify` used wrong map** — `airdirection_map` was passed instead of `purifier_map`.
**`0x08` Set Run State flags wrong byte order** — flags are little-endian on the wire (ref: muart-group/muart-group.github.io#17). Removed `htons()` from all `0x08` flag assignments.
**`widevane_isee` false positives** — values like `0x84`/`0x85`/`0x8c` (ISEE bit + position nibble) incorrectly reported `AirDirection:"even"`. Reverted to exact-match for `0x80`, `0x28`, `0xaa`.
---
### New Features
**`0x42` HVAC Options polling** — added `miel_hvac_data_hvac_options` struct and `MIEL_HVAC_REQUEST_HVAC_OPTIONS` request. The unit is polled for Purifier, NightMode and EconoCool state. Requires short request form (len=1). Results stored in `sc_hvac_options` and published in SENSOR and HVACSETTINGS when `cap_run_state=true`.
**Run State commands (`0x41 0x08`)** — new commands `HVACSetPurify`, `HVACSetNightMode`, `HVACSetEconoCool` and `HVACSetAirDirection` sent via `0x08` on units that report `cap_run_state=true`. Optimistic update to `sc_hvac_options` applied before confirmation.
**EconoCool** — `0x08` byte 14, flag `0x10`, COOL mode only. Command `HVACSetEconoCool on|off`.
**Base Capabilities (`0x5B 0xC9`)** — queried once after connecting. Parsed into `miel_hvac_capabilities`. Results published flat inside `MiElHVAC` as `*Supported` fields. Temperature ranges published as °C. Raw packet in `CapabilitiesHex`.
**Capabilities-aware command validation** — commands blocked when unit reports feature unavailable: modes (heat/dry/fan), temperature range from capabilities, fan speeds, run state commands return `NotSupported` or `ControlNotSupported`.
**`0x42` polling skip** — skipped on units with `cap_run_state=false`, eliminating recurring timeouts.
**`miel_hvac_append_settings_json()`** — shared helper replacing ~80 lines of duplicated JSON code between SENSOR and HVACSETTINGS topics.
---
### JSON Changes
- `Power` (W) and `Energy` (kWh) published inside `MiElHVAC` and also in a separate `ENERGY{}` object outside `MiElHVAC` for Home Assistant auto-discovery
- `Purifier`, `NightMode`, `EconoCool` shown in SENSOR and HVACSETTINGS when `cap_run_state=true`; otherwise omitted
- `AirDirection` always shows current state from `0x62 0x02` regardless of control support
- `*Supported` capability fields published flat inside `MiElHVAC`: `HeatSupported`, `DrySupported`, `FanSupported`, `VaneVSupported`, `SwingSupported`, `AutoFanSupported`, `OutdoorTempSupported`, `AirDirectionSupported`, `PurifierSupported`, `NightModeSupported`, `EconoCoolSupported`
---
### Tested on
MSZ-LN25VG2W — `cap_run_state=false`, temp ranges 16–31 °C cool/auto, 10–31 °C heat, 5 fan speeds.
* Extend AirDirection capability, added web ui energy sensor, cleanup
### AirDirection — respect i-See sensor presence
`AirDirection` now requires three conditions instead of just the vertical vane capability:
- `cap_vane_v` — unit has a vertical vane
- `sc_has_isee` — i-See sensor observed at runtime (widevane ever seen with bit `0x80`, or value `0x28`/`0xaa`)
- `cap_run_state` — unit supports `0x08` for control
Resulting behaviour:
| `cap_vane_v` | `sc_has_isee` | `cap_run_state` | `AirDirection` visible | `AirDirectionSupported` |
|:-:|:-:|:-:|:-:|:-:|
| ❌ | — | — | hidden | `not_supported` |
| ✅ | ❌ | — | hidden | `not_supported` |
| ✅ | ✅ | ❌ | shown | `control_not_supported` |
| ✅ | ✅ | ✅ | shown | `on` |
Units with vertical + horizontal vanes but no i-See sensor now correctly report `AirDirectionSupported:"not_supported"` and omit `AirDirection` from both `MiElHVAC` and `HVACSettings` JSON.
### Capability field renames
For naming consistency across the `*Supported` fields:
- `cap_heat` → `cap_mode_heat`, `HeatSupported` → `ModeHeatSupported`
- `cap_dry` → `cap_mode_dry`, `DrySupported` → `ModeDrySupported`
- `cap_fan_mode` → `cap_mode_fan`, `FanSupported` → `ModeFanSupported`
- `cap_auto_fan` → `cap_fan_auto`, `AutoFanSupported` → `FanAutoSupported`
- `OutdoorTempSupported` → `OutdoorTemperatureSupported`
- `TempCool` / `TempHeat` / `TempAuto` → `SetTemperatureCoolMinMax` / `SetTemperatureHeatMinMax` / `SetTemperatureAutoMinMax`
### Energy values
- `Power` (W) and `Energy` (kWh) published both inside `MiElHVAC` and in a standard Tasmota `ENERGY{}` sub-object (`Power`/`Total`) for Home Assistant auto-discovery.
- Added Web UI rows via `FUNC_WEB_SENSOR` showing instantaneous power (W) and cumulative total (kWh) on the Tasmota main page.
### Runtime i-See detection
New `sc_has_isee` flag in `miel_hvac_softc`, set in `miel_hvac_input_settings` on the first widevane value indicating i-See state. Once set, stays set for the session.
**Practical note:** after boot, the flag starts `false`. Units with i-See that has never been activated since Tasmota started will show `AirDirection` as hidden until i-See is first used (via IR remote or `HVACSetAirDirection`). Safe default — prevents showing misleading values on units without i-See.
* MiElHVAC Full support of AirDirection control
* MiELHVAC fix globals, VLA, duplicate JSON fields and float formatting
## Summary
- **Remove file-scope globals** (`temp_type`, `remotetemp_active`,
`remotetemp_auto_clear_time`, `remotetemp_last_call_time`,
`remotetemp_half`): moved into `miel_hvac_softc` as `sc_temp_type`
and `sc_remotetemp_*`. All references updated. Eliminates potential
conflict with other drivers sharing the same translation unit.
- **Fix VLA in `miel_hvac_send`**: `char hex_d[(len + 1) * 2]` replaced
with fixed-size `(MIEL_HVAC_DATABUFLEN + 1) * 2`. Variable-length
arrays on the stack are disallowed by `-Wvla` and unreliable on
embedded targets.
- **Make `temp_type` explicit**: helper functions `miel_hvac_deg2temp`,
`miel_hvac_temp2deg`, `miel_hvac_roomtemp2deg` now take `bool
temp_type` as a parameter. Detection of the extended encoding moved
from render functions to `miel_hvac_input_data()`, removing a hidden
side effect inside JSON serialisation code.
- **Remove duplicate JSON fields**: `Purifier`, `NightMode`, `EconoCool`
were emitted twice (from both the settings block and the options
block). Options block now only emits `OptionsHex`.
- **Fix `RemoteTemperatureSensorAutoClearTime` JSON type**: was
serialised as a string (`"10000"`), now a proper JSON number (`10000`).
- **Bool consistency**: assignments `= 1` / `= 0` on `bool` fields
replaced with `= true` / `= false`.
* ESP8266: fix ESP_getMaxAllocHeap() and ESP_getHeapFragmentation()
Both functions returned wrong values: ESP_getMaxAllocHeap() returned
ESP.getFreeHeap() (total free, not largest contiguous block);
ESP_getHeapFragmentation() read ummHeapInfo.maxFreeContiguousBlocks,
which is only valid immediately after a umm_info() heap walk.
Fix: cache the result of umm_max_block_size() in ESP_UpdateHeapMetrics().
The cache is refreshed once per second when SetOption130 is active, and
on demand in CmndStatus() before Status 4 is output. Both getter functions
read the cached value; the call site overhead is a single integer read.
umm_max_block_size() is unconditionally available (UMM_INFO is hardcoded
in the Arduino ESP8266 umm_malloc_cfg.h), so no build flags are required.
Status 4 (StatusMEM) gains two ESP8266-specific fields: MaxFreeBlock (KB)
and Frag (%).
* ESP8266: add heap OOM diagnostics, Status 4 fields, Status 44 dump
OOM event monitoring (requires UMM_INLINE_METRICS or UMM_STATS_FULL):
- ESP_HeapOomCheck(): called once per second; logs the OOM counter delta
when the counter changes ("OOM: count N (+M)")
- ESP_HeapOomTest(): logs current OOM count on demand
Status 4 (StatusMEM) gains additional ESP8266-specific fields when the
corresponding build flags are active:
- OomCount: cumulative out-of-memory events (UMM_INLINE_METRICS or
UMM_STATS_FULL)
- HeapLwm (KB): heap low-watermark since boot (UMM_STATS_FULL)
- MaxAllocSz (bytes): peak single allocation size (UMM_STATS_FULL)
Status 44 (ESP8266-only diagnostic command):
- Triggers umm_info(nullptr, true) to print a full heap block map to
the serial console
- Calls ESP_HeapOomTest() to log the current OOM count
- Returns {"Status44":{"HeapDump":"serial"}}
- Status 44 is accepted regardless of MAX_STATUS
Build flags UMM_STATS_FULL and UMM_INLINE_METRICS can be enabled via
platformio_override.ini build_flags; documented in
platformio_override_sample.ini.
* Replace old software-defined library arduino-vid6608 with hw-defined esp-32-vid6608
* VID6608: Rework integration to allow select RMT/SW method by using the `VID6608_RMT` option
* VID6608: Tune library inclusion for RMT version
* The RMT version is not supported on ESP32-C2, library excluded
* Add more clear error message, if RMT is requested, but not supported
* Add support for multi-byte characters like emojis (💡) in WebButton labels for light device toggle buttons
- Remove the 3-character buffer truncation of light device toggle button labels allowing multi-byte characters like emojis
- Rely on CSS to handle truncation and add `max-width:0` to the td so it respects the provided percentage width instead of expanding to fit content
- Wrap button content in a span with overflow handling to prevent layout breakage
* Use localized `D_BUTTON_TOGGLE` for light device button default text
* Improve UTF-8 support in light device WebButton text
- Add `Utf8Truncate` function in `support.ino` to truncate UTF-8 strings to a max chars limit or max visual width limit
- Truncate light device button text to max 4 chars or max width of 4 before passing to button HTML template
- cache calls to GetWebButton so only called once and update bool var from `set_button` to `has_web_button`.
* Small tweaks to improve clarity in Utf8Truncate on codepoint limits and visual width handling
* Correctness fixes
---------
Co-authored-by: s-hadinger <49731213+s-hadinger@users.noreply.github.com>
- Replace 8-bit per-channel math with 10-bit intermediates to preserve color ratios at low brightness.
- When SetOption105 (white blend mode) is enabled on a 4-byte RGBW strip, extract the minimum RGB component as a white channel (scaled by rgbwwTable[3]) rather than zeroing it, mirroring the logic in xdrv_04_light.ino.
- Gamma correction is applied via ledGamma10_10 throughout, including the extracted white channel.
- Route single-specifier hostnames through Format() so patterns like %NX (last N MAC hex chars) work correctly, while preserving the existing two-specifier %s-%04d behavior via snprintf
- Update comment in my_user_config.h to accurately describe chip ID rather than MAC
- Position tracking (abs_position, rel_position) was previously skipped when SEESAW_ENCODER_LIKE_ROTARY light control mode was active due to an early return. Moved position updates before the light control block so Show() and Telemetry always reflect current position.
- Also adds Pos (global relative position) alongside Pos1/Pos2 in JSON telemetry responses, rule triggers, and the web UI display.
# SML driver: catch-up of accumulated fork changes
This PR rolls up the SML driver work that's accumulated on a downstream fork
over the last ~2 years. **One file** (`tasmota/tasmota_xsns_sensor/xsns_53_sml.ino`,
+288 / −27), squashed for review. Three logical groups, each with its own
opt-out path so existing builds get bit-for-bit-identical behaviour where
nothing actually changed.
## TL;DR
| Group | What | Gating | Default |
|---|---|---|---|
| **A** — bug fixes | 10 narrow correctness fixes | always-on | enabled |
| **B** — scripter-free operation | drive descriptor from `/sml_meter.def` without the Tasmota Scripter | existing `#ifdef USE_SCRIPT` / `USE_UFILESYS` guards | inherits build config |
| **C** — `USE_BAT_CTRL` (Modbus write queue, TCP-reset cleanup, dynamic MBAP SIZE) | new feature for Modbus-write-heavy descriptors | new `#ifdef USE_BAT_CTRL` | ESP32 ON, ESP8266 OFF |
If you want to merge only Group A, the gate-out for B and C is a 2-line
revert (drop the `USE_BAT_CTRL` auto-define and the missing-`USE_SCRIPT`
fallbacks). Happy to split into 3 separate PRs if that's preferred.
## Group A — bug fixes (always-on)
Each is a documented repro on a real or emulated meter; failure modes
listed are observed not theoretical.
### A1. FC-aware Modbus RTU response framing
`mlen = sbuff[2] + 5` was used for ALL RTU responses. Correct for FC01-04
reads but wrong for FC05/06/15/16 write echoes (fixed 8 B with `reg_hi`
in `sbuff[2]`) and exception replies (`FC|0x80`, fixed 5 B). Mixing reads
with writes corrupted receive-buffer alignment for one cycle after each
write. Now switches on the FC byte. Inline comment block documents all
four framing cases.
### A2. `@bN:iM:` bit-extract dropped when combined with mbus index filter
The `b` block updated `ebus_dval` only; the `@i` path immediately below
took `dval = mbus_dval`, so the bit was thrown away. Visible on FC01
coil descriptors that fan one response byte across 8 per-coil JSON
fields — all read 17 (raw byte) instead of 1/0/0/1. One added line:
`mbus_dval = ebus_dval;` inside the `b` block.
### A3. Heap corruption when loading descriptor from `/sml_meter.def`
Without scripter, `lp1` points into `file_md` whose lines end with
`SCRIPT_EOL`, not `'\0'`. The descriptor copy loop only checked `'\0'`,
so it ran past line end and clobbered adjacent heap (observed reliably
with 30+-row meter defs on SD card). Added `*lp1 == SCRIPT_EOL` to the
loop terminator.
### A4. `SML_SRCBSIZE` 256 → 512
With 30+ register requests per meter, the `+1` line easily exceeds
256 chars. Pre-fix, line-buffer overran → parser saw mid-line content
as new line → `maxvars=0` → "sml memory error".
### A5. Reverted PR #24587's early-bail in `SML_Immediate_MQTT`
PR #24587 added `if (!sml_globs.dvalid[index]) return;` at the top of
`SML_Immediate_MQTT`. Three reasons it was wrong:
- the math `@`-chain branch sets `dvalid` AFTER calling
`SML_Immediate_MQTT` → first emission lost on every meter
- the Modbus/eBus/PZEM/VBus/raw value branch never sets `dvalid` in
this code path at all → those meters lost ALL immediate-MQTT
emissions
- encrypted SML decoders feed `SML_Decode` → match fires →
`SML_Immediate_MQTT`, but encrypted descriptors don't hit the
same `dvalid`-set sites that PR #24587 assumed
`SML_Immediate_MQTT` is by definition called immediately after a fresh
value has been parsed, so the gate adds no safety and breaks legitimate
use. The gate in `SML_Show` (TelePeriod JSON path) is correct and is
**preserved** — that one needs to suppress slots that haven't seen
data yet because it fires regardless of whether new data arrived.
### A6. OBIS literal-pattern `/n` and `/r` escape support
OBIS descriptors that match line terminators (CR/LF) had no way to
express them. Now `/n` → `\n` and `/r` → `\r` in the literal-byte
match block (only for type `o`/`c`).
### A7. ESP32 hardware-serial validation
Reject configurations where `rx_pin == trx_pin`, fall back to software
serial when `srcpin < 0`, and log which path was taken. Pre-fix, an
accidental same-pin config would silently fail at `Serial.begin()` and
the meter would just stay quiet.
### A8. Sanitised log messages
Several `AddLog` lines didn't have an `SML:` prefix → meter errors got
lost in the general log stream. Now consistent.
### A9. Math evaluation moved to its own 1-second timer
Pre-fix used `if (*mp == 'm' && !sb_counter)` — relied on a global byte
counter being zero, which it usually isn't. Math expressions in
descriptors fired sporadically. Now `lastmath` global + `math_run` flag
set once per second at top of `SML_Decode`. Math runs exactly once per
second per pattern, deterministically.
### A10. `sb_counter` overflow protection
Reset to 0 when > 10. Was used by the old math gate (#A9); could grow
unbounded across long runtime. Harmless but cleaner with a cap.
## Group B — scripter-free operation
The driver historically required the Tasmota Scripter for descriptor
parsing. With `USE_UFILESYS`, a standalone `/sml_meter.def` file can
now drive the whole pipeline — useful for builds that ship without
scripter.
Changes are guarded so existing `USE_SCRIPT` builds get identical
behaviour:
- `SML_REPLACE_VARS` now requires `USE_SCRIPT` (var substitution only
makes sense with scripter)
- `SML_Init`: file-md path is the fallback when `meter_script != 99`,
OR the only path when `USE_SCRIPT` is undefined
- `SML_getlinelen` moved out of `#ifdef SML_REPLACE_VARS` so the
non-scripter path can use it
- Removed `#ifdef USE_SCRIPT` wrapper around `SML_Send_Seq` and
`SML_Check_Send` (TX dispatch is universal)
- Provide `SCRIPT_EOL` fallback (= `'\n'`) when scripter isn't
compiled (was an exported symbol from `xdrv_10_scripter.ino`)
- `SML_Init` robustness: filesystem availability check before `open()`,
`special_malloc` return check with clear log instead of crash,
null-terminate file content after read, close file handle on error
paths, prefix all error messages with `SML:`
## Group C — `USE_BAT_CTRL` (ESP32 only)
Originally added for an SMA SunnyBoy battery-control descriptor where
the coexistence of FC03 reads, FC16 writes, and a tight TCP-session
policy stressed the driver. The underlying fixes are useful for any
Modbus-write-heavy descriptor, so I'm exposing them as a feature flag
rather than burying them. Auto-defined for ESP32 (off for ESP8266
where there's no TCP meter use case worth supporting); a one-line
override in `user_config_override.h` opts out.
### C1. Two-slot write queue
`SML_Write()` for type `m`/`M`/`k` now queues into `sml_write_buf[0..1]`
instead of calling `SML_Send_Seq` directly. `SML_Check_Send()` picks up
queued writes between read requests. Prevents read/write collision when
an MQTT command arrives mid-cycle. OBIS (`o`) and other timing-critical
types bypass the queue — IEC 62056-21 mode-A handshake needs deterministic
timing that a 100-ms queue hop would break.
### C2. Meter-switch cooldown (500 ms)
When `sml_desc_cnt` advances to a different meter, hold off transmission
for 5 × 100 ms. Several Modbus slaves (notably SMA-class inverters) need
~200-500 ms between session-end and next-session-start; without it,
back-to-back requests land on a slave still tearing down the previous
response and return CRC errors.
### C3. Modbus-TCP MBAP `SIZE` field made dynamic
Pre-fix, `tcph.SIZE = sml_swap(6)` was hardcoded for FC03 reads (PDU =
6 bytes: `addr + fc + reg_hi + reg_lo + cnt_hi + cnt_lo`). FC16 multi-
register write has a longer PDU (7 + 2*N bytes, up to 26+), and TCP
servers reject frames whose MBAP header `SIZE` doesn't match the actual
PDU length. Now: `sml_swap(slen - 2)`. Also bumped
`MODBUS_TCP_HEADER.payload[8]` → `[48]` for the larger PDU.
### C4. `SML_Clean_Meters()` — force TCP RST before restart
Wired into `FUNC_SAVE_BEFORE_RESTART`. Necessary for slaves that allow
only one TCP session (notably SMA Tripower 10.0SE): without forcing RST,
the slave's session table holds the dead connection in `ESTABLISHED`
state for several minutes after our reboot, rejecting reconnects from
the rebooted ESP. Uses `SO_LINGER {1, 0}` to send RST instead of FIN.
ESP32 only — ESP8266's `WiFiClient` has no `setSocketOption()`; plain
`stop()` is the best it can do there.
### C5. `reset_sml_vars()` forces TCP cleanup on script reload
Same `SO_LINGER` trick as C4 — pre-fix, a script reload would leak the
active TCP connection until natural keepalive timeout (often >30 s).
### C6. Cleanup of write-queue state on script reload
`head`/`tail`/`buf` reset so a re-init can't pick up stale queued writes.
## Footprint
```
diff --stat:
tasmota/tasmota_xsns_sensor/xsns_53_sml.ino | 315 ++++++++++++++++++++++---
1 file changed, 288 insertions(+), 27 deletions(-)
```
No new files, no new headers, no new dependencies, no other files touched.
## Validation
- ESP32-S3 devkit + emulated Modbus slave covering FC01/FC02/FC03/FC04/
FC05/FC15. Every fix in Group A has a documented repro that passes
after the change.
- Group C has been in production on the fork's SMA installations for
~18 months across ~50 devices.
## For the maintainer
If you'd prefer 3 PRs (one per group), I can split this without further
work — the commits are already organised that way locally. Let me know.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
* MIELHVAC New features and bug fixes
## Changelog
### Bug Fixes
**Memory leak in `miel_hvac_pre_init()`** — `goto del` jumped past the `free(sc)` label, leaking the allocated struct on serial init failure. Replaced with explicit `delete`/`free`/`return`.
**`remotetemp_clear` semantics inverted** — variable was `true` on boot causing a CLR frame to be sent before any sensor registered. Renamed to `remotetemp_active`, initialised `false`.
**`HVACSetPurify` used wrong map** — `airdirection_map` was passed instead of `purifier_map`.
**`0x08` Set Run State flags wrong byte order** — flags are little-endian on the wire (ref: muart-group/muart-group.github.io#17). Removed `htons()` from all `0x08` flag assignments.
**`widevane_isee` false positives** — values like `0x84`/`0x85`/`0x8c` (ISEE bit + position nibble) incorrectly reported `AirDirection:"even"`. Reverted to exact-match for `0x80`, `0x28`, `0xaa`.
---
### New Features
**`0x42` HVAC Options polling** — added `miel_hvac_data_hvac_options` struct and `MIEL_HVAC_REQUEST_HVAC_OPTIONS` request. The unit is polled for Purifier, NightMode and EconoCool state. Requires short request form (len=1). Results stored in `sc_hvac_options` and published in SENSOR and HVACSETTINGS when `cap_run_state=true`.
**Run State commands (`0x41 0x08`)** — new commands `HVACSetPurify`, `HVACSetNightMode`, `HVACSetEconoCool` and `HVACSetAirDirection` sent via `0x08` on units that report `cap_run_state=true`. Optimistic update to `sc_hvac_options` applied before confirmation.
**EconoCool** — `0x08` byte 14, flag `0x10`, COOL mode only. Command `HVACSetEconoCool on|off`.
**Base Capabilities (`0x5B 0xC9`)** — queried once after connecting. Parsed into `miel_hvac_capabilities`. Results published flat inside `MiElHVAC` as `*Supported` fields. Temperature ranges published as °C. Raw packet in `CapabilitiesHex`.
**Capabilities-aware command validation** — commands blocked when unit reports feature unavailable: modes (heat/dry/fan), temperature range from capabilities, fan speeds, run state commands return `NotSupported` or `ControlNotSupported`.
**`0x42` polling skip** — skipped on units with `cap_run_state=false`, eliminating recurring timeouts.
**`miel_hvac_append_settings_json()`** — shared helper replacing ~80 lines of duplicated JSON code between SENSOR and HVACSETTINGS topics.
---
### JSON Changes
- `Power` (W) and `Energy` (kWh) published inside `MiElHVAC` and also in a separate `ENERGY{}` object outside `MiElHVAC` for Home Assistant auto-discovery
- `Purifier`, `NightMode`, `EconoCool` shown in SENSOR and HVACSETTINGS when `cap_run_state=true`; otherwise omitted
- `AirDirection` always shows current state from `0x62 0x02` regardless of control support
- `*Supported` capability fields published flat inside `MiElHVAC`: `HeatSupported`, `DrySupported`, `FanSupported`, `VaneVSupported`, `SwingSupported`, `AutoFanSupported`, `OutdoorTempSupported`, `AirDirectionSupported`, `PurifierSupported`, `NightModeSupported`, `EconoCoolSupported`
---
### Tested on
MSZ-LN25VG2W — `cap_run_state=false`, temp ranges 16–31 °C cool/auto, 10–31 °C heat, 5 fan speeds.
* Extend AirDirection capability, added web ui energy sensor, cleanup
### AirDirection — respect i-See sensor presence
`AirDirection` now requires three conditions instead of just the vertical vane capability:
- `cap_vane_v` — unit has a vertical vane
- `sc_has_isee` — i-See sensor observed at runtime (widevane ever seen with bit `0x80`, or value `0x28`/`0xaa`)
- `cap_run_state` — unit supports `0x08` for control
Resulting behaviour:
| `cap_vane_v` | `sc_has_isee` | `cap_run_state` | `AirDirection` visible | `AirDirectionSupported` |
|:-:|:-:|:-:|:-:|:-:|
| ❌ | — | — | hidden | `not_supported` |
| ✅ | ❌ | — | hidden | `not_supported` |
| ✅ | ✅ | ❌ | shown | `control_not_supported` |
| ✅ | ✅ | ✅ | shown | `on` |
Units with vertical + horizontal vanes but no i-See sensor now correctly report `AirDirectionSupported:"not_supported"` and omit `AirDirection` from both `MiElHVAC` and `HVACSettings` JSON.
### Capability field renames
For naming consistency across the `*Supported` fields:
- `cap_heat` → `cap_mode_heat`, `HeatSupported` → `ModeHeatSupported`
- `cap_dry` → `cap_mode_dry`, `DrySupported` → `ModeDrySupported`
- `cap_fan_mode` → `cap_mode_fan`, `FanSupported` → `ModeFanSupported`
- `cap_auto_fan` → `cap_fan_auto`, `AutoFanSupported` → `FanAutoSupported`
- `OutdoorTempSupported` → `OutdoorTemperatureSupported`
- `TempCool` / `TempHeat` / `TempAuto` → `SetTemperatureCoolMinMax` / `SetTemperatureHeatMinMax` / `SetTemperatureAutoMinMax`
### Energy values
- `Power` (W) and `Energy` (kWh) published both inside `MiElHVAC` and in a standard Tasmota `ENERGY{}` sub-object (`Power`/`Total`) for Home Assistant auto-discovery.
- Added Web UI rows via `FUNC_WEB_SENSOR` showing instantaneous power (W) and cumulative total (kWh) on the Tasmota main page.
### Runtime i-See detection
New `sc_has_isee` flag in `miel_hvac_softc`, set in `miel_hvac_input_settings` on the first widevane value indicating i-See state. Once set, stays set for the session.
**Practical note:** after boot, the flag starts `false`. Units with i-See that has never been activated since Tasmota started will show `AirDirection` as hidden until i-See is first used (via IR remote or `HVACSetAirDirection`). Safe default — prevents showing misleading values on units without i-See.
* MiElHVAC Full support of AirDirection control
* MIELHVAC New features and bug fixes
## Changelog
### Bug Fixes
**Memory leak in `miel_hvac_pre_init()`** — `goto del` jumped past the `free(sc)` label, leaking the allocated struct on serial init failure. Replaced with explicit `delete`/`free`/`return`.
**`remotetemp_clear` semantics inverted** — variable was `true` on boot causing a CLR frame to be sent before any sensor registered. Renamed to `remotetemp_active`, initialised `false`.
**`HVACSetPurify` used wrong map** — `airdirection_map` was passed instead of `purifier_map`.
**`0x08` Set Run State flags wrong byte order** — flags are little-endian on the wire (ref: muart-group/muart-group.github.io#17). Removed `htons()` from all `0x08` flag assignments.
**`widevane_isee` false positives** — values like `0x84`/`0x85`/`0x8c` (ISEE bit + position nibble) incorrectly reported `AirDirection:"even"`. Reverted to exact-match for `0x80`, `0x28`, `0xaa`.
---
### New Features
**`0x42` HVAC Options polling** — added `miel_hvac_data_hvac_options` struct and `MIEL_HVAC_REQUEST_HVAC_OPTIONS` request. The unit is polled for Purifier, NightMode and EconoCool state. Requires short request form (len=1). Results stored in `sc_hvac_options` and published in SENSOR and HVACSETTINGS when `cap_run_state=true`.
**Run State commands (`0x41 0x08`)** — new commands `HVACSetPurify`, `HVACSetNightMode`, `HVACSetEconoCool` and `HVACSetAirDirection` sent via `0x08` on units that report `cap_run_state=true`. Optimistic update to `sc_hvac_options` applied before confirmation.
**EconoCool** — `0x08` byte 14, flag `0x10`, COOL mode only. Command `HVACSetEconoCool on|off`.
**Base Capabilities (`0x5B 0xC9`)** — queried once after connecting. Parsed into `miel_hvac_capabilities`. Results published flat inside `MiElHVAC` as `*Supported` fields. Temperature ranges published as °C. Raw packet in `CapabilitiesHex`.
**Capabilities-aware command validation** — commands blocked when unit reports feature unavailable: modes (heat/dry/fan), temperature range from capabilities, fan speeds, run state commands return `NotSupported` or `ControlNotSupported`.
**`0x42` polling skip** — skipped on units with `cap_run_state=false`, eliminating recurring timeouts.
**`miel_hvac_append_settings_json()`** — shared helper replacing ~80 lines of duplicated JSON code between SENSOR and HVACSETTINGS topics.
---
### JSON Changes
- `Power` (W) and `Energy` (kWh) published inside `MiElHVAC` and also in a separate `ENERGY{}` object outside `MiElHVAC` for Home Assistant auto-discovery
- `Purifier`, `NightMode`, `EconoCool` shown in SENSOR and HVACSETTINGS when `cap_run_state=true`; otherwise omitted
- `AirDirection` always shows current state from `0x62 0x02` regardless of control support
- `*Supported` capability fields published flat inside `MiElHVAC`: `HeatSupported`, `DrySupported`, `FanSupported`, `VaneVSupported`, `SwingSupported`, `AutoFanSupported`, `OutdoorTempSupported`, `AirDirectionSupported`, `PurifierSupported`, `NightModeSupported`, `EconoCoolSupported`
---
### Tested on
MSZ-LN25VG2W — `cap_run_state=false`, temp ranges 16–31 °C cool/auto, 10–31 °C heat, 5 fan speeds.
* Extend AirDirection capability, added web ui energy sensor, cleanup
### AirDirection — respect i-See sensor presence
`AirDirection` now requires three conditions instead of just the vertical vane capability:
- `cap_vane_v` — unit has a vertical vane
- `sc_has_isee` — i-See sensor observed at runtime (widevane ever seen with bit `0x80`, or value `0x28`/`0xaa`)
- `cap_run_state` — unit supports `0x08` for control
Resulting behaviour:
| `cap_vane_v` | `sc_has_isee` | `cap_run_state` | `AirDirection` visible | `AirDirectionSupported` |
|:-:|:-:|:-:|:-:|:-:|
| ❌ | — | — | hidden | `not_supported` |
| ✅ | ❌ | — | hidden | `not_supported` |
| ✅ | ✅ | ❌ | shown | `control_not_supported` |
| ✅ | ✅ | ✅ | shown | `on` |
Units with vertical + horizontal vanes but no i-See sensor now correctly report `AirDirectionSupported:"not_supported"` and omit `AirDirection` from both `MiElHVAC` and `HVACSettings` JSON.
### Capability field renames
For naming consistency across the `*Supported` fields:
- `cap_heat` → `cap_mode_heat`, `HeatSupported` → `ModeHeatSupported`
- `cap_dry` → `cap_mode_dry`, `DrySupported` → `ModeDrySupported`
- `cap_fan_mode` → `cap_mode_fan`, `FanSupported` → `ModeFanSupported`
- `cap_auto_fan` → `cap_fan_auto`, `AutoFanSupported` → `FanAutoSupported`
- `OutdoorTempSupported` → `OutdoorTemperatureSupported`
- `TempCool` / `TempHeat` / `TempAuto` → `SetTemperatureCoolMinMax` / `SetTemperatureHeatMinMax` / `SetTemperatureAutoMinMax`
### Energy values
- `Power` (W) and `Energy` (kWh) published both inside `MiElHVAC` and in a standard Tasmota `ENERGY{}` sub-object (`Power`/`Total`) for Home Assistant auto-discovery.
- Added Web UI rows via `FUNC_WEB_SENSOR` showing instantaneous power (W) and cumulative total (kWh) on the Tasmota main page.
### Runtime i-See detection
New `sc_has_isee` flag in `miel_hvac_softc`, set in `miel_hvac_input_settings` on the first widevane value indicating i-See state. Once set, stays set for the session.
**Practical note:** after boot, the flag starts `false`. Units with i-See that has never been activated since Tasmota started will show `AirDirection` as hidden until i-See is first used (via IR remote or `HVACSetAirDirection`). Safe default — prevents showing misleading values on units without i-See.
Added correct DC-DC initialisation for SH1106. Seee datasheet page 22.
Command 0x8D is unknown to SH1106 resulting in unstable DC-DC operation and no display operation.