Releases: Ctrlable/rfwc5_controller
v1.4.3 — Device card entity picker UX
What's new
Device card improvements
- Action type select now shows friendly labels ("Stateful Scene", "HA Scene", "Cover Cycle (Up / Stop / Down)", etc.) instead of raw internal keys
- Action entity is now a domain-filtered select entity (replaces the plain text input) — shows all matching entities as "Friendly Name (entity_id)" options
- Stateful Scene → switch entities
- HA Scene → scene entities
- Automation → automation entities
- Script → script entities
- Entity Toggle → light / switch / fan / input_boolean / media_player / climate
- Cover Cycle / None → unavailable (configure multi-cover via Reconfigure)
- Changing the action type automatically clears the linked entity
- Button action sensor
native_valuenow shows entity friendly names; rawaction_typeandaction_entityexposed asextra_state_attributes
Breaking change
The text.*_entity entities are removed. HA will mark them as unavailable/orphaned; the new select.*_entity entities replace them.
UX: friendly action names, entity autocomplete, reconfigure pre-population
Three Config Flow UX Fixes
Fix 1 — Action types show friendly names
The action type dropdown previously showed raw internal keys (stateful_scene, cover_cycle, etc.). It now uses HA's SelectSelector with a labelled list:
| Value | Now shows as |
|---|---|
stateful_scene |
Stateful Scene |
ha_scene |
HA Scene |
automation |
Automation |
script |
Script |
entity_toggle |
Entity Toggle (On/Off) |
cover_cycle |
Cover Cycle (Up / Stop / Down) |
none |
None (LED only) |
Fix 2 — Entity fields use HA's built-in entity picker
Entity selection previously used either a static dropdown (limited to entities visible at setup time) or a plain text input. All entity fields now use EntitySelector with domain filtering:
| Action type | Domain filter |
|---|---|
| Stateful Scene | switch |
| HA Scene | scene |
| Automation | automation |
| Script | script |
| Entity Toggle | switch, light, input_boolean, fan, cover, climate, media_player, lock |
| Cover Cycle | cover — multi-select (select multiple covers natively; stored as CSV) |
| Indicator entity | sensor, number |
| Basic sensor | sensor |
Cover Cycle now uses multiple=True on the EntitySelector — the user selects covers from a proper multi-select picker instead of typing a comma-separated string.
Fix 3 — Reconfigure pre-populates all fields
Re-opening Configure previously showed blank fields. All steps now pre-fill with current values:
OptionsFlow.__init__mergesentry.optionsoverentry.dataintoself._data, so the most recent reconfigure values always take priorityConfigFlow.async_step_userseedsself._datafrom the existing entry when launched from a reconfigure context- Every step (MQTT, buttons, entities, indicator, basic sensor) uses the merged data as field defaults
Files Changed
const.py—ACTION_TYPE_LABELSdictconfig_flow.py— complete rewrite withSelectSelector,EntitySelector, mergedself._data,_current()helper; removed stale entity-list helpersstrings.json/translations/en.json— removed{cover_hint}placeholder (no longer needed with EntitySelector)manifest.json— bumped to1.4.2
Fix: cover cycle now responds to every button press using internal state machine
What Was Broken
The cover cycle only worked on the first press. Every subsequent press read HA cover state, which still reflected the pre-command position (HA state lags by seconds after a cover command is issued). So every press after the first saw the same state and repeated the same command.
Fix: Internal State Machine
CoverCycleController now maintains its own CoverMovementState that updates instantly on button press, independent of HA cover state.
State Machine
OPEN / STOPPED_OPEN → close_cover → CLOSING
CLOSING → stop_cover → STOPPED_CLOSE
STOPPED_CLOSE → open_cover → OPENING
OPENING → stop_cover → STOPPED_OPEN
CLOSED → open_cover → OPENING
UNKNOWN → seed from HA, then apply transition
State is stored at class level in _states dict (keyed by entry_id_button_idx) so it survives object recreation on integration reload.
HA State Is Still Used — But Only For
- Initial seeding — on first press, HA state is read once to set the starting
CoverMovementState - Terminal confirmation —
sync_from_ha_state()advancesOPENING → OPENorCLOSING → CLOSEDonly when HA confirms position ≥ 95% or ≤ 5%
Other Improvements
- Instant state update —
async_cycle()setsnext_statebefore the service call, so a second press 300ms later sees the correct new state - Correct revert on failure —
prev_statecaptured before update; reverted on service call exception - Accurate log messages — logs
prev_state → service → next_state - 400ms debounce — rapid double-taps dropped cleanly
Files Changed
cover_controller.py— complete rewrite withCoverMovementStateenum and internal state machine__init__.py— cover entity state change handler callssync_from_ha_state()thenget_led_state()manifest.json— bumped to1.4.1
Feature: cover cycle action with position-safe Up/Stop/Down
New Feature: Cover Cycle Action Type
Adds cover_cycle as a new button action type, enabling Lutron-style Up / Stop / Down cycling over one or more cover entities without ever forcing an absolute position.
Cycle Logic
| Current state | Button press issues |
|---|---|
| Any cover moving (opening/closing) | stop_cover — stops all covers |
| All covers at position ≤ 5% (or closed) | open_cover — opens all covers |
| All covers at position ≥ 95% (or open) | close_cover — closes all covers |
| Stopped mid-travel | Resumes in last direction (open or close) |
Position thresholds (5% / 95%) handle covers that never reach exact 0 or 100. Last-direction memory persists across button presses and survives integration reloads.
Multiple Covers
Enter a comma-separated list of cover entity IDs in the entity field:
cover.living_room_shade, cover.office_shade
All covers in the list are controlled together. LED state reflects the group: ON when any cover is open/opening (position > 5%), OFF when all are closed/closing.
LED Behavior
- ON = any cover position > 5% or state is
open/opening - OFF = all covers position ≤ 5% or state is
closed/closing
The LED syncs automatically when covers are moved by other automations or by hand.
direction_key Scoping
Each button on each keypad gets its own direction memory key ({entry_id}_{button_index}), so multiple keypads sharing the same HA instance don't interfere.
Other Changes
async_execute_actiongains**kwargs(backwards-compatible); all call sites passdirection_keyfor proper scopingaction_executor.pyis now the single authoritative executor — removed the stale duplicate that was in__init__.pyget_tracked_state()returns correct initial LED state for cover_cycle buttons on HA startup
Files Changed
cover_controller.py(new) — CoverCycleController with cycle logic, direction memory, LED state computationaction_executor.py— cover_cycle branch, **kwargs, updated get_tracked_state__init__.py— remove local executor duplicate, cover_cycle entity tracking, direction_key passingswitch.py— cover_cycle short-circuit in _apply_stateconfig_flow.py— free-text entity field for cover_cycle buttonsconst.py— ACTION_TYPE_COVER_CYCLE, cover state/service constantsstrings.json/translations/en.json— cover_hint placeholder in entities stepmanifest.json— bumped to1.4.0
Fix: coalescing write queue prevents Z-Wave indicator race condition on simultaneous button state changes
What's Fixed
Root cause
Physical button press → action fires → linked entity state changes → _on_linked_entity_state_change fires → schedules another write → two writes race each other within milliseconds, overwhelming the RFWC5's 300-series Z-Wave chip.
Coalescing write queue (led_manager.py)
- Write guard (
_write_in_progress):_async_write_nowreturns immediately if a write is already executing — no concurrent Z-Wave writes ever. - Pending flag (
_write_pending): if state changes arrive while a write is executing,_write_pendingis set. Thefinallyblock schedules a follow-up write after the coalesce window so the latest state is always sent. - Coalesce timer (
_coalesce_window = 0.5s): rapid back-to-back_schedule_write()calls cancel and restart the timer, so only the final state after 500ms of quiet is written. Replaces the old 1s debounce. async_sync_allupdated to clear_write_pendingand respect the in-progress guard.
Suppression window (led_manager.py + __init__.py)
- New
suppress_external_writes(duration)method sets a monotonic deadline. - After each button press action fires (ON and OFF paths),
manager.suppress_external_writes(2.0)is called. _on_linked_entity_state_changecheckstime.monotonic() > manager._suppress_untilbefore calling_schedule_write(). If within the suppression window, the LED state is still updated in memory but no write is scheduled — the button press write already sent the correct state.
Constants Changed (const.py)
| Old | New |
|---|---|
LED_WRITE_DEBOUNCE_S = 1.0 |
LED_COALESCE_WINDOW_S = 0.5 |
| (new) | LED_SUPPRESS_WINDOW_S = 2.0 |
Files Changed
led_manager.py— coalescing write queue, write guard, suppression windowconst.py— new timing constants__init__.py— suppress calls after action fires, suppression check in state change handlermanifest.json— bumped to1.3.2
v1.3.1 — Options/data merge fix + auto-reload on reconfigure
What's Fixed
Options/data merge (entry.options vs entry.data)
HA's OptionsFlow saves reconfigured values to entry.options, not entry.data. Previously, async_setup_entry read only entry.data, so changes made via Configure (basic sensor, MQTT settings, button assignments, etc.) were silently ignored until a full HA restart wiped entry.options back into entry.data.
Fix: a _get_config helper now checks entry.options first, falling back to entry.data. The same merge is applied in MQTTProvisioner.__init__ so MQTT settings updated via reconfigure are used on the next provisioning run.
Auto-reload on reconfigure
After saving new options, HA did not automatically reload the integration — the new settings had no effect until the user manually restarted HA or reloaded the integration.
Fix: entry.add_update_listener now triggers an automatic reload whenever the config entry is updated, so reconfigure changes take effect immediately.
Files Changed
__init__.py—_get_confighelper,group_levelsfix, update listener registrationprovisioner.py—MQTTProvisioner.__init__options-over-data mergemanifest.json— bumped to1.3.1
Feature: use Basic sensor entity for button press detection
What's new in v1.3.0
Button presses now detected via Basic sensor entity state changes
Replaces the zwave_js_value_updated bus listener with direct HA state change tracking on the Basic CC sensor entity.
- Basic sensor value 10/20/30/40/50 → button press → LED updated + action fired
- Basic sensor value 0 → button release → indicator refreshed → OFF actions fired for buttons that turned off
New config step: Select Basic Sensor
Pick the Basic CC sensor entity from your Z-Wave device in the setup and reconfigure flows. Disabled entities show with a [disabled] tag and are auto-enabled by the integration.
async_execute_action dispatcher
| Action Type | ON | OFF |
|---|---|---|
| ha_scene | scene.turn_on | — |
| stateful_scene | switch.turn_on | switch.turn_off |
| automation | automation.trigger | — |
| script | script.turn_on | script.turn_off |
| entity_toggle | homeassistant.turn_on | homeassistant.turn_off |
Auto-migration to schema v4
Existing entries are automatically migrated. No action required.
Fix: node_id migration, reprovision button, clean up log levels
What's new in v1.2.1
Auto-migration for existing entries
Existing config entries created before v1.2.0 are automatically migrated to schema version 2 with MQTT defaults. No action required after upgrading.
If `node_id` is not yet set, the provisioner logs a clear WARNING with step-by-step instructions to reconfigure, and the integration stays operational (LED control still works).
Run Provisioning button on device card
A new Run Provisioning button (`mdi:cog-sync`) appears on the device card. Press it to run MQTT provisioning without going to Developer Tools.
Log level cleanup
Diagnostic trace messages moved from WARNING → DEBUG. Production logs will no longer be flooded.
| Message | Old | New |
|---|---|---|
| set_button called | WARNING | DEBUG |
| refresh_and_read | WARNING | DEBUG |
| ingest_indicator | WARNING | DEBUG |
| PRE-WRITE / POST-WRITE | WARNING | DEBUG |
| tracking entities | WARNING | DEBUG |
| linked entity changed | WARNING | DEBUG |
Feature: automatic MQTT-based Z-Wave group provisioning
What's new in v1.2.0
MQTT-based Z-Wave provisioning (replaces broken zwave_js service approach)
The integration now provisions your RFWC5 keypad automatically on first setup via the Z-Wave JS UI MQTT API — no manual Z-Wave configuration required.
New config step: Z-Wave JS UI MQTT Settings
A new step in the setup flow (and reconfigure flow) collects:
| Field | Default | Description |
|---|---|---|
| MQTT Prefix | `zwave-js-ui` | The MQTT topic prefix for Z-Wave JS UI |
| Gateway Name | `zwave-js-ui` | The gateway name in Z-Wave JS UI |
| MQTT Broker Host | `localhost` | Your MQTT broker |
| MQTT Broker Port | `1883` | Your MQTT broker port |
| RFWC5 Node ID | (required) | Z-Wave node ID of the RFWC5 keypad |
| Controller Node ID | `1` | Z-Wave controller node ID |
Find your node ID in Z-Wave JS UI → Devices → your RFWC5 → Node ID.
What gets provisioned
Group Associations — Groups 1–5 (one per button) and group 255 (lifeline) are associated to the Z-Wave controller. Without these, button presses are never forwarded to Home Assistant.
Group Level Parameters — Configuration CC 112, parameters 1–5 are set to values 10 / 20 / 30 / 40 / 50. These are the Basic CC values the keypad sends when each button is pressed.
Provisioning runs once only
After the first successful run, `provisioned: true` is stored in the config entry. Subsequent HA restarts skip provisioning entirely. Use the `rfwc5_controller.reprovision` service to re-run if needed (e.g. after a device reset or Z-Wave network change).
Multi-keypad support
Each keypad config entry has its own MQTT settings — supports multiple RFWC5 keypads across different Z-Wave networks.
Feature: automatic Z-Wave group provisioning on setup
What's new in v1.1.0
Automatic Z-Wave provisioning
Every time the integration loads, it now runs a provisioning sequence before the LED manager starts. This ensures the keypad is correctly wired to the Z-Wave controller before any button presses are processed.
Part A — Group Associations (Association CC)
Groups 1–5 (one per button) and group 255 (lifeline) are associated to the Z-Wave controller node. Button presses are sent via Basic CC to these groups — without the associations they are never forwarded to HA.
Part B — Group Level Configuration (CC 112)
Configuration parameters 1–5 are set to values 10 / 20 / 30 / 40 / 50. These are the Basic CC values the keypad sends when each button is pressed. Each value is read back after writing to verify the change took effect.
Provisioning status sensor
A new sensor.<keypad>_Provisioning_Status entity appears on the device card:
- State:
ok/incomplete/unknown - Extra attributes: full report showing per-group association and level results
Manual reprovision service
rfwc5_controller.reprovision — re-runs the full provisioning sequence from Developer Tools without having to delete and re-add the integration. Accepts an optional entry_id field to target a specific keypad.