Changelog
v2.0.6
- [Improvement] Entity rename auto-follow - if the managed device entity is renamed in the entity registry, Time Off now migrates the persisted expiry to the new entity ID and schedules a reload, keeping state consistent without manual intervention.
- [Improvement]
Trigger Onlystate read from entity object reference - previously read from the HA state machine via string lookup; now reads directly from the registered switch entity object, eliminating a class of subtle timing failures during startup recovery. - [Improvement]
Off Afterdefault read from entity object reference - same improvement as above for theTime Offnumber entity. - [Improvement] Ghost entity cleanup on setup - stale entities from a previous load that are no longer in the current target list are removed from the entity registry automatically on
async_setup_entry. - [Improvement]
CONFIG_SCHEMAadded - registerscv.config_entry_only_config_schemaso HA correctly reports that this integration does not support YAML configuration. - [Improvement] Device trigger strings added -
strings.jsonnow includes thedevice_automation.trigger_typesection required for the UI device trigger label ("Time Off: When timer expires") to render correctly. - [Improvement] Service descriptions added to
strings.json- all three services now have name, description, and field metadata, enabling documentation in the HA Services UI. - [Fix] Device triggers never fired - the
time_off_timer_expireddevice trigger automation option was silently broken in rare cases. The event config was passed to HA's event trigger engine as a plain string rather than a validated schema object, causing the event type to be iterated character by character. Fixed by running the trigger config throughevent_trigger.TRIGGER_SCHEMAbefore passing it toasync_attach_trigger. - [Fix] Reload after unload orphaned the device - cancelling timers during
async_unload_entryincorrectly triggered the loop's cleanup guard (which clears the persisted expiry), so after a reload the device had no saved state and appeared as if it had never had a timer. Fixed by popping tasks from the active-timers dict before cancelling, matching the behaviour of the normal stop-timer path. - [Fix] Stale "Timer Active" after device turned off during shutdown - if a device was turned off while HA was down, the saved expiry was still present on next boot, leaving
Timer Activestuck on with no countdown running. Fixed: recovery now explicitly clears stale expiries for devices that are off at startup. - [Fix] Recovery task not cancelled on unload - the startup recovery background task was not stored or cancelled when an entry was unloaded, leaving a dangling task that could act on a stale entry. Fixed by storing the task handle in entry data and cancelling it in
async_unload_entry. - [Fix]
Off Afterrestore incorrectly set a non-zero value on a fresh boot - theOff Aftersensor restored its previous value even when no timer was actually running (no matching entry in the persisted expiry store). It now only restores a non-zero value if a saved expiry exists for that device. - [Fix]
Trigger Onlyswitch restore used a hardcoded string - the on/off restore comparison used a literal"on"string instead of theSTATE_ONconstant, making it fragile against HA internals. Fixed. - [Fix] Binary sensor interval handle was not initialised before
async_added_to_hass- a missingself._unsub_interval = Nonein__init__meant anAttributeErrorcould occur ifasync_will_remove_from_hassran before the entity was fully added. Fixed. - [Fix] Binary sensor
_updatescheduled a coroutine per tick - the interval callback was anasync def, causing HA to allocate a task on every polling tick. Converted to a synchronous@callbacksince the method only calls synchronous HA helpers. - [Fix] Binary sensor did not recover its polling interval on restart - if a timer was already active when the binary sensor entity loaded (e.g. after a reload), the
remainingattribute stayed stale. Fixed by starting the update interval at the end ofasync_added_to_hasswhen the timer is already active. - [Fix]
Time Offnumber entity used a redundant state backing field -number.pymaintained a private_statefield alongside_attr_native_value, causing a double-write on every value change. Simplified to use_attr_native_valuedirectly. - [Fix] Services registered per config entry instead of once at integration load - services were re-registered on every
async_setup_entrycall and only conditionally removed, leading to duplicate registration warnings and broken removal logic. All three services are now registered once inasync_setup. - [Fix] Config entry title was overwritten on every reload -
async_setup_entryunconditionally calledasync_update_entrywith a freshly computed title, overwriting any name the user had set. Removed; the title is now set once at creation and never touched again. - [Fix]
already_configuredabort used wrongstrings.jsonkey - the abort reason key was nested under"error"instead of"abort", so the duplicate-device message was never shown. Fixed. - [Doc] Universal Functionality table - added Climate and Input Boolean to the supported device list.
- [Doc] Restart recovery description - clarified that the 30-second wait is a fixed sleep (not adaptive), that it only runs when timers were active at shutdown, and documented the
Trigger Onlyrecovery path (fires event + resumes loop if device is still on). - [Doc] Various grammar and wording fixes.