Retain last state change data of a sensor after reboot

I can understand this approach for sensors, but why input_boolean are updated? Those could be updated only manually via HA…

There are ways to overcome async problem:

  • Use a “rehydration phase” after startup
  • Introduce a lightweight “last state cache” for startup
  • Hybrid model

The async objection is valid for blocking operations - but nothing prevents scheduling a coroutine post-startup.

In fact, many HA components (e.g., Recorder purge tasks, entity restoration) already use hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, callback) to do post-start adjustments.

Anyway, since it’s not likely to be implemented, are there any entities/helpers that do not update last_changed attribute on HA start-up?

By all means, if you want to tackle this make an architecture proposal with all your plans.

As an FYI, the state machine is built and populated before EVENT_HOMEASSISTANT_STARTED fires, which is the problem.

No. This is built into the state machine. When a new state is added to the state machine, last_update or last_changed is resolved.

Deep architecture proposal — concise, technical, and implementable

TL;DR: add a tiny, opt-in Last State Cache (LSC) + a small, safe core hook so the state machine can apply cached last_changed at initial population (no Recorder required), then asynchronously reconcile with Recorder later. Minimal, low-risk core change; non-blocking; crash-safe.


Goals & constraints

  • Must not block async startup.
  • Must work before Recorder is available (state machine is populated early).
  • Preserve last_changed only when state truly unchanged .
  • Minimal surface area / opt-in / easy to audit.

Architecture (high level)

  1. LSC writer (shutdown): on EVENT_HOMEASSISTANT_STOP dump {entity_id, state, last_changed} for selected entities to an atomic JSON file in config (.ha_last_state.json). Use temp + os.replace() for atomicity. Default: only entities that use restore_state/are not transient (configurable whitelist/blacklist).
  2. Early LSC loader (very early, before StateMachine population): tiny core hook reads the JSON synchronously into hass.data[“lsc_cache”] during bootstrap before entities are created. Reading a few KB synchronously is acceptable and deterministic.
  3. StateMachine initial population hook (small core patch): when the StateMachine creates the initial in-memory State objects (the code path that sets state for the first time), consult hass.data[“lsc_cache”]:
  • If cached_state exists and cached_state[“state”] == new_state, then create the State object with last_changed = cached_state[“last_changed”] (instead of now()).
  • Otherwise proceed as today.This requires adding an optional initial_last_changed argument or a pre-check in the initial-setting code path — very small, localized change.
  1. Deferred reconciliation (async): once Recorder is available, spawn an async task to reconcile: compare LSC vs Recorder DB and correct in-memory last_changed where Recorder proves a different historical timestamp. This is non-blocking and runs in background.

Data model & rules

  • File format: {“version”:1,“generated”:“2025-10-06T…”,“entities”:{“sensor.x”:{“state”:“1”,“last_changed”:“2025-10-05T12:03:00Z”}}}
  • Only store entity_id, state, last_changed. Do not store attributes.
  • Apply cached last_changed only if cached.state == reported.state. (Exact match; no guessing.)
  • Optional policies:
    • Exclude entities tagged transient (sensors, last_reset-based, one-shot switches).
    • Opt-in config: preserve_last_changed: true|false and allow whitelist/blacklist.

Safety, robustness, and edge cases

  • Atomic writes prevent partial files on crash.
  • Graceful failure: if file missing/corrupt, ignore and continue.
  • Race conditions: because change is applied during initial population, there’s no window where other subsystems will see a wrong last_changed (they get the preserved value immediately). Deferred reconciliation may still adjust timestamps but runs after Recorder is ready.
  • Minimal core risk: patch is small — read-only cache load + small conditional when creating initial State objects. No Recorder or DB calls at bootstrap.

Why this solves the problem

  • No Recorder dependency at bootstrap. LSC is file-backed and sync-read during bootstrap.
  • No async blocking. State machine population remains synchronous and deterministic.
  • Minimal core change. One early read + small conditional path on initial state creation.
  • Safe and reversible. Opt-in config, limited scope, covered by tests.
1 Like

To make architecture changes, you post in architecture discussions. Understand that if you post there, you are expected to make the changes you’re proposing.

I’d also suggest providing an actual technical proposal instead of AI slop as well.

I really don’t get it - Nabu Casa is busy adding things like “AI image generation,” which have nothing to do with home automation, yet they refuse to implement a basic must-have feature: preserving the state of unchangeable entities (like input_boolean, input_select, input_*) while HA is off. Their state can’t even be changed until Home Assistant starts again - how does that make any sense?

5 Likes

There’s really nothing to understand here. HA is still mostly volunteer developed. All anyone needs here core approval for the work.

If you’re expecting core to make the change, you need to lower these expectations. There is a roadmap that has already been developed and it’s being followed. The core team develops what’s on the roadmap, which is mostly automation changes and UI at the moment.

Hi,
I just shared a good work around for this here:

1 Like