Skip to content

A failed operation-log read discards an already-successful state read, so the lock entity never refreshes #74

Description

@marianogoldman

Summary

In coordinator._async_poll, the operation-log fetch runs after the state
query and before the return. If it raises, the state and battery values that
were already read successfully are thrown away, and _async_update_data
substitutes {"locked": None, "battery_level": None}.

Reading the operation log is auxiliary — it feeds the event entity. It should
not be able to invalidate the lock's state.

async def _async_poll(self, connection):
    result = await connection.async_query_state()
    if result is None:
        return {"locked": None, "battery_level": None}
    raw_state, battery = result              # <-- state already in hand
    await connection.async_get_operation_log()   # <-- raises
    return {                                 # <-- never reached
        "locked": _parse_lock_state(raw_state),
        "battery_level": battery,
    }

Why it matters

With DEFAULT_SCAN_INTERVAL_SECONDS = 3600 this is the only mechanism that
refreshes the entity from the device. When it is broken, the entity's state
only ever changes from HA's own optimistic writes in lock.py.

Everything done outside HA — fingerprint, PIN, NFC card, the TTLock app, the
keypad long-press — becomes invisible, not for an hour but indefinitely.

Measured on my install: I unlocked from the TTLock app and HA still reported
locked 40+ minutes later, including after a forced
homeassistant.update_entity.

That is worse than an unavailable entity, because it reports a confident wrong
value. A state-based automation such as "lock the door if it isn't locked when
everyone leaves" silently does nothing: the condition reads a stale locked
and the automation aborts without acting and without warning. I hit exactly
this before tracing it back here.

Environment

ha-ttlock-ble 3.4.0
ttlock-ble 0.1.10
Home Assistant 2026.7.2 (Docker)
Lock LL609, DLock-XP V3

Suggested fix

Isolate the auxiliary read:

    raw_state, battery = result
    try:
        await connection.async_get_operation_log()
    except Exception:
        LOGGER.debug("Operation log fetch failed for %s", connection.mac, exc_info=True)
    return {
        "locked": _parse_lock_state(raw_state),
        "battery_level": battery,
    }

The lock state stays fresh; only the event entity lags.

Secondary: the failure is reported as a success

_async_update_data catches the exception and returns a None state, so the
coordinator logs:

WARNING [custom_components.ttlock_ble] Failed to poll <MAC>: The length of the provided data is not a multiple of the block length.
DEBUG   [custom_components.ttlock_ble] Finished fetching ttlock_ble data in 39.262 seconds (success: True)

success: True on a poll that produced no data hides the problem in exactly
the log a user would check. Worth flagging the update as failed so HA's own
coordinator machinery reflects it.

Related, but not duplicates

Three different paths to "HA reports the wrong lock state" may be worth
triaging together, since the consequence is the same and it's a bad one: a
confidently wrong locked silently disables any state-based automation built
on it.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions