Skip to content

Releases: dasimon135/ha-rf-fan

v1.10.3 — the add form keeps your answers, and unnamed keys speak your language

Choose a tag to compare

@dasimon135 dasimon135 released this 24 Sep 06:43
1470790

Fixed

  • Adding a fan under a name already used on that gateway is now an error on the
    name field. It aborted the whole form, and every answer on it was lost. A missing
    gateway no longer resets the other answers either, and the gateway already picked
    stays picked when the form comes back with an error.
  • A free-form key left unnamed takes its translated name ("Touche supplémentaire
    1" in French). It was stored as the English "Extra key 1", so the translation
    shipped in fr.json was never used. Keys named before this keep their stored name.
  • Walks on the light: three moves in quick succession could leave two walks
    pressing keys at once, and reloading the entry (a reconfigure does) left a walk in
    flight pressing keys against runtime data nothing read any more.
  • Translations: the explanation of the extra-key count was defined twice and
    never shown, it is now the field's help text; the reconfigure form labels that
    field; a second setup of the same fan has a message; and the recovery screen no
    longer says "no RF signal" under a duplicate-code error.
  • Blueprint: the temperature thresholds accept Fahrenheit (they were capped at
    40 and labelled °C, which made the blueprint unusable on a °F sensor).
  • Card: Enter or Space on the focused fan picture toggles the fan, a rename shows
    without waiting for the next state change, a timer whose hours cannot be read is
    labelled with its name instead of a bare "h", the visual editor offers tile_tap
    and calibrate_entity, and the compact layout reports its smaller size.

Validated

On the maintainer's own instance before this release: loaded without a line in the log,
and on the real gateway the wall-switch script started the fan at its lowest speed,
the light went on and off, and the card rendered.

What was not exercised on hardware: the add-form and translation changes (no fan
was added), the walk fixes (this lamp has no +/- keys), the blueprint, and the card's
keyboard and editor changes. Those rest on their tests, each seen failing first.

Nothing to do after updating. A free-form key named before this keeps the name that
was stored for it.

v1.10.2 — the speed services move one notch, and the shown preset follows the fan

Choose a tag to compare

@dasimon135 dasimon135 released this 24 Sep 05:43
556e843

Fixed

  • fan.increase_speed and fan.decrease_speed never changed the speed. Called
    without a percentage_step, which is how scripts, automations and voice assistants
    call them, Home Assistant plans the next speed from the entity's speed_count. The
    fan never set it, so it read the default of 100 and every call re-sent the speed the
    fan was already at. A wall switch wired to "one notch faster" did nothing. Both
    services now move exactly one speed, and one below the lowest turns the fan off, as
    they do for any fan. The stored percentages do not change, so an automation that
    compares against one keeps working.
  • A dedicated airflow preset stayed shown after the fan had left it, on three
    paths. A direction change on a per_speed remote re-sends a speed code, which is
    what leaves the preset. The remote's ON key starts a stopped fan in normal airflow,
    and it also left a preset asked for meanwhile armed, to be pressed right after the
    next speed key. And a restart kept the shown preset but lost the press it was
    waiting for, after which choosing it again did nothing.
  • Switching the light off stops a brightness or colour walk in flight. It went on
    pressing the +/- keys for a lamp that was off, and counting them.
  • Entries created before 1.5.0 now get a unique id (config entry 6.2), so the same
    fan can no longer be added twice on the same gateway. If two entries already are the
    same fan, the second is left alone and named in the log.
  • Free-form keys are shown by the name you gave them on the learning screen and on
    the manual codes form, which only said extra_1.
  • Card: it no longer freezes in the dashboard editor after the first edit, a long
    press no longer swallows the next tap, and on another integration's fan it no longer
    guesses which of that device's buttons is its own (a restart button could become
    "recalibrate").

Documentation

  • A raw-timings gateway wants a repeat count of 1, and the README only ever said
    to raise it
    (#78). idle: 12ms
    captures a press whole, so a remote that repeats itself within one press puts every
    one of those frames inside the learned code: on a Cecotec ceiling fan, one code is
    241 timings, four frames 6 ms apart. The repeat count then multiplies presses rather
    than frames, and at its default of 3 the fan beeps three times while a toggled light
    blinks before it settles. Relearning does not help, because the code is right.
  • The repeat count is on Configure, not Reconfigure. It lives in the options
    flow, titled RF Transmission Options; Reconfigure is the menu that relearns codes
    and has never carried it. The troubleshooting section sent readers to the wrong screen.

Validated

On the maintainer's own instance, on a Cecotec fan whose remote has no fan_on key,
before this release: the wall-switch scripts that call fan.increase_speed and
fan.decrease_speed with no step moved the fan from 33 % to 50 % and back, where
they had always re-sent the current speed. The config entry, created before 1.5.0,
migrated to 6.2 and received its unique id. The light was switched on and off, the
card rendered, and nothing from rf_fan reached the log.

What was not exercised on hardware: the dedicated airflow preset (this fan's
airflow key toggles) and a brightness walk (this lamp has no +/- keys). Those fixes
rest on their tests, each of which was seen failing before its fix.

Nothing to do after updating: the entry moves to 6.2 on its own at the next restart,
no action key changes name, and nothing is relearned.

v1.10.1 — the state Home Assistant shows matches the fan

Choose a tag to compare

@dasimon135 dasimon135 released this 19 Sep 23:15
780a375

Fixed

Found by a full read of the code rather than reported. Each of them left Home
Assistant showing a state the hardware was not in, and each now has a test that
was seen failing first.

  • A second gateway in range could swallow a press of the physical remote. Two
    gateways running the same YAML report the same code string for the same press.
    The receive de-bounce is keyed on that string and was consulted before the
    frame's origin, so a neighbour's copy arriving first made ours look like a
    repeat, and the press was dropped.
  • fan.turn_on on a running fan dropped it to speed 1 while the state kept the
    old percentage.
    On a remote with no fan_on key the fallback is a speed key;
    it is now the speed the fan is showing, and it goes through the same path as any
    other speed, so it also ends a dedicated airflow preset and settles a
    per_speed direction.
  • An airflow preset asked for while the fan was stopped is now pressed by
    whichever service starts the fan. The bundled card's speed segments and the
    more-info slider call fan.set_percentage, which ignored it. It then stayed
    armed behind a state that read normal and went on the air at the next
    fan.turn_on, unasked; it is dropped when a speed key leaves the preset.
  • The brightness resynchronisation button cancels a brightness move still in
    flight, and is cancelled by the next one, like any other move on that control.
    It used to press its way down beside a walk still climbing, then declare the
    bottom reached.
  • Relearning by pasting a code now refuses one that a kept action already
    owns, as capturing one always has.
  • A refused rename no longer resets the airflow levels and the extra-key count
    on the form it sends back.
  • Diagnostics label the colour position the way the select does (1…N unless
    the lamp has the three named positions), and carry the brightness position.
  • Card: a preset name is escaped before it goes into the markup. The card
    accepts any fan.* entity, so that name is not always this integration's.

Changed

  • The card walks the entity registry once per registry instead of once per
    state change.
    Home Assistant hands a card a new hass on every state change
    anywhere in the house, and each one used to filter every entity of the install
    to find the fan's siblings, twice when it led to a render.
  • Removed three constants nothing referenced (LIGHT_LEVEL_STEPS,
    COLOR_TEMP_OPTIONS, KELVIN_STEP_GAP_SEC) and a transmit helper the
    resynchronisation fix made unused.

Documentation

  • The replay troubleshooting section gives every rc_switch protocol's inline
    block, and the gateway YAMLs show protocol 1's

    (#78). The comment at the
    protocol: line used to show the Hampton Bay block from #59 as its example, which
    is protocol 6: sync: [31, 1], inverted: true. In a file whose rc_protocol
    defaults to "1", it read as a template to fill in, and a protocol 1 remote given
    that block with its own unit sends a frame the fan does not recognise. The README
    now tabulates all eight protocols from ESPHome's own table, says to change only
    pulse_length, and warns that an inline block's omitted keys fall back to
    protocol 1's values.

Validated

This build has run on the maintainer's own instance since 20 September, through two
restarts: config entry loaded, states restored, nothing from rf_fan in the log.
fan.set_percentage, fan.turn_on on the running fan and fan.turn_off were
called against the real gateway, on a remote with no fan_on key, so turn_on
went through the new fallback; the bundled card was rendered in its tile layout and
follows the fan and the lamp.

What was not observed on hardware: which speed the fan physically held across
that turn_on — nobody was watching it, and the test is what pins it — and
anything about a dedicated airflow preset or a second gateway, neither of which
exists here. Those three fixes rest on their tests, each of which was seen failing
before its fix.

Nothing to migrate: config entries stay at version 6, no action key changes name,
and nothing is relearned.

v1.10.0 — natural airflow in levels

Choose a tag to compare

@dasimon135 dasimon135 released this 09 Sep 21:31
75073be

Added

  • Natural airflow can come in levels. A remote whose airflow key offers
    Breeze 1 / 2 / 3 now declares how many levels it has, and each becomes a preset
    mode on the fan entity: natural 1natural N beside normal. That gives the
    level a home it never had — fan.set_preset_mode for automations, preset_mode
    in the recorder, and one chip per level on the bundled card — without a single
    new entity. Reported by @Ltek on #59, designed on #61.
  • One learned code per level, and per direction on a remote that has no direction
    key: fan_natural_1fan_natural_N, plus fan_natural_1_reverse … . Same
    rule the speeds already follow.

Changed

  • The capability form asks for the number of airflow levels beside the airflow
    selector. Zero is the default and means the shape that shipped: one airflow
    key, one natural preset, nothing renamed and nothing relearned.
  • Levels are refused on an airflow key that only flips. A level is a value and a
    toggle carries none, so the form says so instead of silently dropping half the
    answer.

Why the model is what it is

The behaviour that decides it — does a speed press leave the airflow preset —
was never measured on @Ltek's fan. His own capture document answered it instead.
Splitting his 26-bit codes into a 16-bit prefix, a 4-bit value and a 6-bit key
field shows Breeze carrying the same key field as a forward speed, with the
level in the same four bits that hold the speed number: speeds 0-9, Breeze 1/2/3
as 11/12/13. One field holds one value, so from Breeze 2 the Speed 5 key emits the
code already stored as Speed 5, byte for byte, and the fan cannot still be in
Breeze afterwards. That is exactly what natural_control: dedicated has meant
since #34.

Documentation

  • Troubleshooting → "The light key changes the fan speed". On some remotes the
    light and dim keys have no frames of their own: each frame carries the current
    speed
    in a field of its own, and the physical remote fills that field with
    whatever is running so its light command never disturbs the fan. This integration
    replays a code exactly as it was learned, that speed included — capture the light
    key with the fan off and every light toggle from Home Assistant sends the stop
    command. The fix needs nothing from the code: a light code written by hand,
    carrying a value the fan has no speed for, toggles the light and leaves the blades
    alone. Found on his own hardware by @Ltek (#59), with his frames as the worked
    example and how to locate the fields on an undecoded remote.
  • Which extra keys may be exposed to voice assistants and scenes. A key that does
    one thing — a store, an ioniser, a beep — changes nothing this integration tracks
    and is safe there. A key that walks the fan through states it never reports back
    leaves every assumption wrong at once, with nothing in the press to say so. The
    section documented the drift; it now states the rule that follows from it.
    Promised to @elmr91 on #18.

Validated

This build has run on the maintainer's own instance since 6 September: config entry
loaded, migrated to version 6, natural_levels at zero, all sixteen learned codes
intact, nothing from rf_fan in the error log.

The levelled path itself has been actuated on no hardware. Nobody here owns a
remote with airflow levels; the model comes from reading @Ltek's capture document,
not from a measurement on a fan. #61 is where to say what a real one does.

Migration

Config entries move to version 6 on the first restart. Every existing entry
declares zero levels, which is the single-level shape it was set up as, so no
action key changes name and nothing is relearned.

v1.10.0b1 — natural airflow in levels, waiting on the one fan that has them

Choose a tag to compare

@dasimon135 dasimon135 released this 06 Sep 10:49
8fef56b

First prerelease of 1.10.0, cut because @Ltek is the only person who can measure the one thing that matters here: whether the levelled airflow preset actually behaves on his hardware the way the frames say it should.

Install through HACS → ⋮ → Redownload → "Need a different version?" → v1.10.0b1, then restart Home Assistant.

Natural airflow can come in levels

#61: a remote whose airflow key offers Breeze 1 / 2 / 3 declares how many levels it has, and each becomes a preset mode on the fan entity — natural 1natural N beside normal — with one learned code per level, and per direction on a remote that has no direction key. That gives the level a home it never had: fan.set_preset_mode for automations, preset_mode in the recorder, one chip per level on the bundled card.

The model behind it was never measured on a fan. It came from splitting @Ltek's own captured codes into a 16-bit prefix, a 4-bit value and a 6-bit key field: Breeze carries the same key field as a forward speed, with the level in the same four bits as the speed number. One field, one value, so a speed press should leave the preset the same way it already does on a single-level dedicated remote. That deduction has not touched real hardware yet — this prerelease is that test.

Zero levels is the default and is the shape that shipped before this release: one airflow key, one natural preset. Every existing entry keeps that, unchanged and nothing relearned.

Full changelog: CHANGELOG.md

v1.9.1 - Tile height matches the grid row

Choose a tag to compare

@dasimon135 dasimon135 released this 06 Sep 10:06
d355ab4

The tile layout is one grid row tall

The tile layout came out 62 px tall: a 42 px icon dot, and a name/state pair with no explicit line-height standing ~38 px. A Home Assistant section grid row is 56 px — 10 px padding + 36 px content + 10 px — which is exactly what the native tile card and Mushroom occupy, so this card stood proud of every neighbour in a column.

Both halves are fixed: the dot is 36 px, and the text carries Home Assistant's own metrics (14 px on a 20 px line, 12 px on a 16 px line, no gap), which stack to exactly 36 px. Measured on a live installation before and after.

The one visible side effect: the name is a hair smaller than before, at the 14 px the native tile card and Mushroom use.

Published as a pre-release for a visual pass on a live installation first; it becomes the regular release once that is done.

v1.9.0 - The card can be themed

Choose a tag to compare

@dasimon135 dasimon135 released this 05 Sep 21:53
be550e5

Added

  • The bundled card can be themed. Every colour of its own is now the fallback of a CSS variable a theme can set — --rf-fan-accent (active controls, hover borders; falls back to --primary-color), --rf-fan-light (the light button and the amber chip) and --rf-fan-tint-low / --rf-fan-tint-high (the first and last segment of a multi-colour light). Nothing changes when a theme sets none of them. A test scans the shipped file so a literal colour cannot creep back in. Card 1.9.0.

Published as a pre-release for a visual pass on a live installation first; it becomes the regular release once that is done. (#64)

v1.8.1 — the betas, promoted

Choose a tag to compare

@dasimon135 dasimon135 released this 05 Sep 11:48

Four pre-releases between 29 August and 5 September, promoted unchanged. A pre-release only reaches installations that opted into beta versions, so everything below has been sitting where almost nobody could get it.

Both halves were measured on real remotes by the people who reported them.

The card stopped loading

#44, reported and narrowed by @elmr91, who lost the card on about one hard reload in three, and on every load in the Android companion app. Each of his measurements removed a suspect: not the cache, not the ?v= cache-busting, not his reverse proxy, not the card failing on a half-loaded Home Assistant.

What was left was the loader, and the difference there is structural: Lovelace loads its own resources and waits for them before rendering a card, while nothing waits for a frontend module URL. The card is now delivered as a Lovelace resource, the way HACS delivers every custom card. An existing entry on the same path is adopted rather than duplicated, and a YAML-mode configuration keeps the old mechanism, because that resource list is the user's file and not this integration's to write to.

The same release fixes a second registration appearing on every restart, and holds the card to surviving a partially loaded Home Assistant.

The repeat count sent one frame, not three

#59, measured by @Ltek over four days on one remote. The default of 2 was wrong twice over, and the second way was worse: transmit_repeat_count rounds an even count down to odd for toggle actions, so every light, sound, reverse, natural and free-form key was sent exactly once — the lone frame that the same function's docstring says some receivers drop outright. The default is now 3.

The same work brought direction-aware off, so stopping a reversed fan no longer leaves the receiver storing the wrong direction, and made the timer entities individually optional.

If you are updating

The integration's config entries migrate to schema 5. Home Assistant refuses to load an entry that is newer than the running build, so do not roll back to v1.8.0 afterwards — restore a backup instead if you need to go back.

Conversely, if you are running one of the 1.8.1 betas today, this is the release to take: it is that code, with nothing added.


242 tests, ruff clean.

v1.8.1b4 — the repeat count the only measured fan needed

Choose a tag to compare

@dasimon135 dasimon135 released this 05 Sep 07:33
22fe1e5

Everything in this pre-release was measured by @Ltek on a
Hampton Bay 9-speed DC fan, on hardware the maintainer does not own
(#59).

The default RF repeat count is now 3

If you have never touched the repeat setting, this is the change that matters. Toggle
actions round an even repeat count down to odd, so the old default of 2 gave every
light, sound, reverse, natural and free-form key exactly one frame — the lone frame
that some receivers drop outright. Speeds that "did nothing" with no error in the log
were very often this.

An entry that stores a repeat count explicitly keeps it. Only entries that never set
one move to 3.

Direction-aware off

A per_speed fan can declare an optional fan_off_reverse. Remotes that put the
direction in every frame were stopping a reversed fan with the forward off code, which
left the receiver storing "forward" — and the next speed code started it the wrong way
round. An entry without the new code falls back to fan_off and behaves exactly as
before.

Sleep timers you actually have

has_timers demanded all four of 1/2/4/8 h. It is now a multi-select: tick the durations
your remote has, get one button per tick. An optional timer_off cancel key can be
declared beside them.

Config entries migrate to version 5 automatically. has_timers: true becomes all four
durations, which is what it meant. No action key changes name, so nothing is relearned.

Documentation

inverted: is now documented, along with the trap behind it: the ESPHome receiver runs at
tolerance: 50% and will happily decode a remote whose timing is well off nominal, while
the fan has no such tolerance. Receiving a code proves nothing about transmitting it.
Both shipped gateway YAMLs say so at the protocol: line, with @Ltek's protocol 6 /
335 µs case as the worked example.


Pre-release. 242 tests green; the config-entry migration was validated on a live install
(v2 → v5 in one restart, codes and entity IDs intact). The repeat-count evidence is
@Ltek's hardware, not the maintainer's.

🤖 Generated with Claude Code

v1.8.1b3 — one registration, not one per restart

Choose a tag to compare

@dasimon135 dasimon135 released this 30 Aug 12:56
0dff9bc

Third prerelease of 1.8.1, fixing something v1.8.1b1 introduced and @elmr91 found by looking at his resource list: the card registered twice, identically.

Install through HACS → ⋮ → Redownload → "Need a different version?" → v1.8.1b3, then restart Home Assistant.

If you ran b1 or b2, this cleans up after itself — the duplicates are removed on startup, with a line in the log saying what went and why. There is nothing to delete by hand.

What went wrong

Lovelace reads its resource list lazily, and the two calls the integration used disagreed about that: asking what is registered does not read the stored list, while registering does. So on a start where Lovelace had not read it yet, the integration was told "nothing is registered", and the registration that followed — which reads it first — added a second copy of what was already there. One more per restart.

That is not cosmetic. Two copies of a card race to define the same element, the loser cannot be replaced, and the older build is the one that keeps rendering. It is precisely the failure that once hid three card fixes from @elmr91 for a month, reintroduced by the change meant to end it.

The list is now read before the question is asked, and any extra copies are removed.

Everything else from b1 and b2 stands

The card registering itself (#44 — no load failures reported since), the "Assumed light state" select (#45 — used in anger already, to repair a desync caused by a memory key), and free-form keys (#18), which since b2 can be added to a fan you already own.

Full changelog: CHANGELOG.md