* DALI: bind DALI-2 push button events to control gear targets
Add command DaliBind<1..8> <device>,<instance>,<target>,<mode> to link
push button events (IEC 62386-301) from an input device instance to a
control gear target. Modes are Toggle, On, Off, Up and Down; Up and Down
start a fade on button press and stop it on release. The first matching
binding is executed. DaliBind<x> 0 removes a binding and DaliBind alone
lists them all.
Bindings are stored in the DALI settings file, packed as one uint32 per
slot, so they survive a restart.
* Describe how start-up brightness stored differently per short addres vs dali groups
* DALI: decode and publish DALI-2 input device event messages
Input devices such as push buttons (IEC 62386-301), occupancy sensors
(303) and light sensors (304) communicate with 24-bit event messages.
Tasmota logged these frames at log level 4 and discarded them, so it
could not react to any DALI-2 switch or sensor on the bus.
Decode the 24-bit frame according to the five IEC 62386-103 addressing
schemes and publish the result for rules and MQTT, for example
{"DALI":{"Event":"0x82840B","Scheme":"Instance","Type":1,"Instance":1,
"Info":11,"Name":"LongPressRepeat"}}
Push button events carry the part 301 event name, occupancy events the
Movement/Occupied/Repeat/Sensor flags and light sensor events the
Illuminance value. 24-bit frames with bit 16 set are commands from
another application controller and are ignored.
Tested with a zencontrol zc-switch rotary on an ESP32-S3-Pico with the
Waveshare Pico-DALI2, driving one channel of a DA4-T DT6 driver via
rules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* DALI: commission DALI-2 control devices and query their instances
Control devices (IEC 62386-103) such as push buttons and sensors are
addressed with 24-bit frames and have their own short address space,
separate from control gear. Until now they could only be commissioned
by hand with raw DaliSend255 frames.
Add DaliDeviceScan 1|2[,<max_count>] which assigns short addresses using
the 24-bit INITIALISE / RANDOMISE / COMPARE / PROGRAM SHORT ADDRESS
sequence. The existing binary search used by DaliScan is shared through
a device flag so control gear commissioning is unchanged. Mode 2 probes
the bus first so addresses already in use are never reassigned.
Add DaliDevice which lists addressed control devices, reports one
device's version, status and instances (type, enabled, event scheme),
and sets the event scheme of all its instances, for example
{"DaliDevice":{"Device":1,"Version":"2.0","Status":0,"Instances":[
{"Instance":0,"Type":1,"Name":"PushButton","Enabled":1,"Scheme":"Instance"}]}}
Input devices default to the Instance event scheme which carries no
short address, so DaliDevice <device>,2 switches them to DeviceInstance
events that identify the sending device.
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* 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`.
* MiElHVAC auto-enable i-See widevane when setting AirDirection
### Problem
`HVACSetAirDirection` (`indirect` / `direct` / `even`) sets the i-See airflow
direction via a `0x41 0x08` runstate packet (flag `0x2000`). The unit only
honors that value once i-See airflow control is enabled, which requires
`widevane=0x80` sent in a `0x01` settings packet.
Until now the user had to manually run `HVACSetWideVane isee` **before**
`HVACSetAirDirection`, otherwise the direction change was silently ignored.
### Change
`HVACSetAirDirection` now sets the widevane to
`MIEL_HVAC_SETTINGS_WIDEVANE_ISEE` (`0x80`) in the same call.
The dispatcher always sends the settings (`0x01`) packet before the runstate
(`0x41`) packet, so the unit receives the i-See enable first and the direction
value on the following tick — no manual ordering required.
### Notes
- `off` is unchanged (still expressed as `widevane=0x8c` via `0x01`).
- The existing capability guard (`cap_vane_v` + `sc_has_isee`) is unchanged.
* Fix GPIO name table offset from duplicate MODBUSRELAY entry
kSensorNames had the four D_MODBUSRELAY_* tokens twice while the
UserSelectablePins enum lists GPIO_MODBUSRELAY_* once, shifting every
following name by four - the GPIO dropdown showed MiEl HVAC MB Tx/Rx/DE
and CC1101 CS as 'MbsRelay ...'. Drop the duplicate row.
* MiEL HVAC accept fan_only as alias for fan mode
Home Assistant's climate integration uses 'fan_only'; map it to the same
value as 'fan' so HVACSetMode/HVACSetHAMode accept it. Reporting is
unchanged (miel_hvac_map_byval still returns 'fan').
Replace the on/off-only main-page presence with a full climate panel and
a live read-only state table, styled to a fixed dark palette scoped
under #hvacp so it is independent of the Tasmota theme.
- FUNC_WEB_ADD_MAIN_BUTTON renders the panel once: mode segment
(Auto/Heat/Cool/Dry/Fan/Off), target-temperature stepper + slider, and
Fan speed / Vane vertical / Vane horizontal / Air direction selects;
which controls appear is gated by the 0x7B 0xC9 Base Capabilities
(cap_mode_*, cap_vane_v, cap_vane_swing, cap_fan_auto, cap_run_state)
and miel_hvac_get_fan_count()
- controls call la('&<key>=<value>'); FUNC_WEB_GET_ARG turns each key
into the matching HVACSet* command (mode via HVACSetHAMode so a mode
button also powers the unit), reusing the existing validated setters
- FUNC_WEB_SENSOR refreshes the panel in place every web_refresh via the
<img onerror> exec trick: state-table cells, mode-button highlight and
every select value, skipped while a change is still in flight
(update_settings/runstate_pending) or the control is focused
- state table: Room/Set temp, Mode, Fan, Vane V/H, Air Direction,
Operation, Compressor, Power/Energy; locale decimal separator applied
(cherry picked from commit 6e013484f168f3748f6d388f18ef69f8313fab97)
Co-authored-by: Theo Arends <11044339+arendst@users.noreply.github.com>
* Fix GPIO name list falling out of sync without USE_MODBUS_RELAY
The enum in kGpioNiceList guards the four MODBUSRELAY entries with
#ifdef USE_MODBUS_RELAY, the matching name list does not. In any build
without that define - which is the default - the two run four entries
apart, and every GPIO role added after that point inherits a name that
belongs to something else.
Nothing visible went wrong so far because no role after the Modbus block
is currently offered in the selection list. The next one added would have
shown up as "MbsRelay TX".
* Add support for TFA Marbella pool thermometer
Receives the TFA Dostmann Marbella (30.3066.01) floating pool thermometer
on 868 MHz with a CC1101 and reports water temperature, sensor id, rolling
counter and battery state as sensor telemetry.
Packet layout, checksum algorithm and temperature formula are taken from
the rtl_433 project (src/devices/tfa_marbella.c and the reflected reverse
Galois digest in src/bit_util.c), Copyright (C) 2021 Benjamin Larsson,
GPL-2.0-or-later.
Wiring is the shared SPI bus plus two pins that belong to the module:
SCK / MISO / MOSI -> SPI CLK / SPI MISO / SPI MOSI
CSN -> CC1101 CS
GDO0 -> CC1101 GDO0
CC1101 CS is added as a role of its own, like every other SPI device has
one (LORA_CS, RC522_CS, MCP2515_CS and so on). The three bus lines stay
generic because they are shared - a display and this receiver hang on the
same wires, and only chip select tells them apart. The name string for it
already existed in the language files, unused.
Built on RadioLib, which already ships with Tasmota and drives the CC1101
on both ESP8266 and ESP32. The older lib/lib_rf/cc1101 declares
"platforms": "espressif8266" for a reason: on ESP32 it collides with the
CC1101 class RadioLib itself declares.
Three settings are not obvious and were established against hardware:
- The bit rate is 9.5238 kBit/s, not 9.6. rtl_433 documents a symbol
width of 105 us, and the 0.8 percent difference accumulates to about
0.7 bit over an 88 bit frame - enough that the first bytes arrive
intact and the last ones come out shifted.
- The sync word is 0xAAD2, although the frame itself begins with
AA 2D D4. The radio matches in hardware and strips what it matched,
so the two values describe the same transition from different sides.
- disableAddressFiltering() is required. RadioLib leaves address
checking on, and the chip then discards every frame whose first byte
does not match the node address - which for this sensor is always
0xAA. Without it reception runs and nothing ever arrives.
SPI is set up by the driver rather than left to RadioLib, whose ArduinoHal
calls SPI.begin() without arguments - on ESP32 that picks the default bus
pins and ignores the GPIO template, the same reason the LoRa drivers do it
themselves.
Reception follows the split the LoRa drivers use: the interrupt handler
sets a flag and nothing else, the packet is read in the main loop. The
flag is cleared after reception is restarted, not before, because
startReceive() puts GDO0 back to its idle level and that is an edge in
itself - clearing first makes the driver re-trigger on its own restart and
read the same stale buffer thousands of times per second. Both edges are
used: the pin does not reliably return to idle after a packet.
The driver binds to the first sensor with a valid checksum and ignores
every other one afterwards, so a neighbour's identical sensor cannot feed
readings into the same value. Marbella <id> rebinds, Marbella 0 relearns;
anything that is not a valid 24 bit id is refused rather than silently
turned into 0, which would look like a successful bind while clearing the
binding.
After TFA_MARBELLA_TIMEOUT seconds without a packet the reading disappears
from both the JSON payload and the web interface. A stale value looks
exactly like a measurement that simply does not change, which is the one
failure mode this driver must not produce.
No plausibility range is applied to the temperature. A range check of
0..50 degrees, as sometimes used in place of a checksum, would wrongly
discard a real reading of 51 degrees.
Verified against the sensor: frames decode with matching checksums, a
disturbed frame is rejected by the checksum, and the rolling counter
advances without gaps over consecutive receptions.
* Add SetOption166 to publish TFA Marbella readings on reception
Off by default, so readings go out with the regular TelePeriod telemetry
like those of every other sensor - a driver should not quietly behave
differently from the rest.
Worth turning on for this sensor: it transmits about once a minute, so
with the default TelePeriod of 300 seconds four out of five readings never
leave the device, and the one that does can be almost five minutes old.
With the option set, every reading becomes an MQTT message - useful for
rules, and more traffic on a busy broker, which is why it is a choice
rather than the default.
The bit is taken from the spare range of the SetOption146..177 block. If
the number collides with something merged in the meantime, it can be moved
without touching the driver.
* Add TFA Marbella build configuration
USE_TFA_MARBELLA is off by default and adds 12k6 of code on ESP8266 and
5k7 on ESP32, most of it RadioLib where it is not already in the build.
It sits with the other SPI peripherals because it needs USE_SPI: without
that, the CC1101 CS role cannot be assigned at all and the driver would be
silently dead.
USE_TFA_MARBELLA and USE_KEELOQ cannot be combined: both drive the same
CC1101, one transmitting on 433 MHz and one receiving on 868 MHz.
Whichever configured the chip last would win silently, so the combination
fails at compile time instead.
* Show the age of the last TFA Marbella reading in the web UI
The reading is still dropped from JSON after TFA_MARBELLA_TIMEOUT, so a
stale value never reaches MQTT. The web interface, however, showed nothing
at all in that case - and an empty page cannot be told apart from a driver
that is not running, a radio that never starts, or a sensor whose battery
died. Those need different answers.
The web interface now always shows a line: the age of the last packet
("12 sec ago"), or "no signal, last 4 Min ago" once the reading expired,
or "no signal yet" when nothing was ever received.
The age is shown rather than a timestamp: the sensor sends once a minute,
so what matters is whether the last packet is seconds or hours old - and a
clock time would be wrong anyway while the device has no NTP yet.
* Use SetOption147 for TFA Marbella and correct the radio parameters
Review feedback: drop the newly added SetOption166 and share the existing
SetOption147 instead. Readings are now published as they arrive, which is
what the sensor needs - it transmits once a minute while TelePeriod defaults
to 300 seconds - and SetOption147 suppresses that, the same handle the serial
bridge, IR, LoRaWAN and WizMote drivers already use. flag6 bit 20 goes back
to being spare.
Two radio parameters were wrong. Both came from the rtl_433 device
description and were never verified against a real sensor:
- Bit rate 9.4 kBit/s, not the 9523.8 Bd that rtl_433's 105 us per symbol
implies. Measured from three SDR recordings by decoding each at every
sampling phase and letting the sensor's own checksum decide: all three
peak at 9400 Bd, and none yields a single valid frame at 9523.8 Bd. Over
the 112 bits of a transmission a 1.3 % rate error drifts past a full bit,
so the tail of every frame arrives shifted - which is why 9.6 kBit/s
delivered nothing at all and 9.5238 only worked occasionally.
- Deviation 34.5 kHz, not 30, from the same recordings.
New setting: "Marbella <MHz>" tunes the receiver and is stored in
Settings->marbella_frequency (int16, 100 Hz steps, taken from the filler
after marbella_serial). The CC1101 derives its frequency from a 26 MHz
crystal, and one module measured here sat 27 kHz below what it was told.
That fits inside the 135 kHz receive filter but spends the margin the signal
needs, and it is a property of the board rather than of the driver - so it
is a setting instead of a constant, and nobody needs a private build for it.
A decimal point tells a frequency from a sensor id; ids now have to be the
full six hex digits, so "Marbella 868" is refused rather than silently
binding to sensor 000868.
Also documented at the sync word why 0xAAD2 is the right value: the sensor
sends an alternating preamble, then a single 0xD2, then the frame - so the
16 bits before AA 2D D4 read AA D2, which is exactly what the radio matches.
That was previously noted as unexplained.
Claude-Session: https://claude.ai/code/session_01EmAyStmKbyqFx9X7eTH7Qg
* Update tasmota_types.h
Use union
---------
Co-authored-by: Theo Arends <11044339+arendst@users.noreply.github.com>
* 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`.
* MiElHVAC auto-enable i-See widevane when setting AirDirection
### Problem
`HVACSetAirDirection` (`indirect` / `direct` / `even`) sets the i-See airflow
direction via a `0x41 0x08` runstate packet (flag `0x2000`). The unit only
honors that value once i-See airflow control is enabled, which requires
`widevane=0x80` sent in a `0x01` settings packet.
Until now the user had to manually run `HVACSetWideVane isee` **before**
`HVACSetAirDirection`, otherwise the direction change was silently ignored.
### Change
`HVACSetAirDirection` now sets the widevane to
`MIEL_HVAC_SETTINGS_WIDEVANE_ISEE` (`0x80`) in the same call.
The dispatcher always sends the settings (`0x01`) packet before the runstate
(`0x41`) packet, so the unit receives the i-See enable first and the direction
value on the following tick — no manual ordering required.
### Notes
- `off` is unchanged (still expressed as `widevane=0x8c` via `0x01`).
- The existing capability guard (`cap_vane_v` + `sc_has_isee`) is unchanged.
* Add Modbus RTU slave to MiEL HVAC driver (xdrv_44)
Optional second RS485 serial port that exposes every MiEL HVAC state as
Modbus read registers and maps every driver function to write
registers/coils, so the unit can be driven directly from a PLC.
- ESP32 only, opt-in via USE_MIEL_HVAC_MODBUS_SLAVE
- new GPIOs: MiEl HVAC MB Tx / Rx / DE (RS485 direction, optional)
- persisted config (address, baudrate, serial config) reclaimed from
Settings free_eb0 + SBitfield1 spare bit; no settings version bump
- function codes 0x01-0x06, 0x0F, 0x10 with CRC-16, RTU inter-frame
gap framing in FUNC_LOOP, and Modbus exception replies
- input registers (FC04) mirror settings, room/outdoor temp, timers,
status/energy, stage, capabilities; holding registers/coils drive
power, mode, temp, fan, vane, widevane, airdirection, prohibit,
purifier, night mode, econocool, remote temp and clear time
- writes reuse new miel_hvac_apply_* typed setters (same capability
gating as the console commands, which are unchanged)
- commands HVACModbus / HVACModbusAddress / HVACModbusBaudrate /
HVACModbusConfig (all live), plus a "Modbus" block in SENSOR JSON
* Update tasmota_template.h
Fix duplicate GPIOs
* Update tasmota_types.h
Use union for parameters
---------
Co-authored-by: Theo Arends <11044339+arendst@users.noreply.github.com>
* Add Shelly emulation for ESP32 (EMUL_SHELLY)
Presents Tasmota as a generation 2 Shelly Pro 2PM to controllers
speaking the Shelly Gen2 RPC protocol, such as the Sigenergy
mySigen application.
The emulation follows Belkin WeMo and Hue Bridge: compiled in
with USE_EMULATION_SHELLY (ESP32 only, as it needs the IDF mDNS
component) and selected at runtime with `Emulation 3` or on the
Configure Other web page, where it appears as "Shelly single
device".
When selected, the driver advertises _shelly._tcp over mDNS with
the IPv4 and IPv6 addresses of all active interfaces and serves
/shelly and /rpc. Up to two switch, two input and two metering
channels are mapped onto Tasmota relays, switches and the energy
driver. Channels beyond the relays configured keep their state
in NVS so a controller still sees two working outputs.
Shelly controllers discard mDNS replies that do not carry the
RFC 6762 TTL of 255, which Espressif's mDNS component only sets
on multicast, so the driver raises the TTL on the mDNS sockets
after registration.
* Make Shelly emulation opt-in and renumber driver