From 1d083a6adbfdfb8fd049518d3b406197de0945f2 Mon Sep 17 00:00:00 2001 From: Rodrigo Roque Date: Fri, 7 Aug 2026 08:09:55 -0300 Subject: [PATCH] docs: align README, CODE_STYLE and CLAUDE.md with the actual codebase The README undersold the integration (three entities, no binary sensor) and announced event attributes, a flat data.py, a TtlockBleOperationEvent class and lint/tests/validate workflows that do not exist. The entity table now lists the four real entities and the real log-event attributes, the layout matches the data/ package, and the CI section describes ci.yml, codeql.yml, release.yml and auto-assign.yml. CODE_STYLE.md and CLAUDE.md drop the same stale names and a config_flow._validate helper that never existed here, and describe the local uv run pre-commit hooks. --- CLAUDE.md | 4 ++-- CODE_STYLE.md | 24 +++++++++++++----------- README.md | 29 +++++++++++++++-------------- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index af2c48b..79bc5d4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,12 +64,12 @@ coordinator.py → polls every scan_interval seconds via each connection, lock.py → LockEntity backed by the BLE connection sensor.py → BatterySensor backed by the same poll + push events binary_sensor.py → connectivity BinarySensorEntity reflecting live BLE link state -event.py → EventEntity that surfaces decoded LockEvent pushes +event.py → EventEntity that surfaces decoded LogEntry records ``` ### Entry typing -`data/` is a package, one class per file, re-exported from `data/__init__.py`. `data/__init__.py` defines `TtlockBleConfigEntry = ConfigEntry[TtlockBleData]`; `data/runtime.py` defines the `TtlockBleData(keys, virtual_keys, connections, coordinator, bluetooth_unsubs)` dataclass. State lives on `entry.runtime_data` (auto-discarded on unload), never on `hass.data`. +`data/` is a package, one class per file, re-exported from `data/__init__.py`. `data/__init__.py` defines `TtlockBleConfigEntry = ConfigEntry[TtlockBleData]`; `data/runtime.py` defines the `TtlockBleData(keys, virtual_keys, connections, coordinator, bluetooth_unsubs, first_refresh)` dataclass. State lives on `entry.runtime_data` (auto-discarded on unload), never on `hass.data`. ### Config flow surface diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 3dcce04..fd6f5f7 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -33,8 +33,8 @@ Style conventions for the `ha-ttlock-ble` project. Before committing, run `TtlockBleCoordinatorData` live in `data/__init__.py`). - **Helper functions** may live in the same file as the single class that uses them (e.g. `_classify_cloud_error` in `api.py`). -- **`__init__.py` of the integration package** wires `async_setup_entry`, - `async_unload_entry`, `async_reload_entry` and nothing else. +- **`__init__.py` of the integration package** wires the Home Assistant + config-entry lifecycle hooks and nothing else. ## Entities: encode behaviour directly, no description-callable indirection @@ -50,7 +50,7 @@ should be written: - Don't write an `Description` subclass with a `value_fn` / `action_fn` field. - Do write `` (e.g. `TtlockBleBatterySensor`, - `TtlockBleLock`, `TtlockBleOperationEvent`). + `TtlockBleLock`, `TtlockBleLogEvent`). - The reason: each entity is a discrete contract; mixing them through a generic class hides the contract behind indirection and discourages per-entity refinement (icons, state attributes, custom logic). @@ -60,8 +60,8 @@ should be written: - Public classes are prefixed with `TtlockBle` (rename to `` when forking). - Concrete platform entities end with the entity type: - `TtlockBleSensor`, `TtlockBleBinarySensor`, - `TtlockBleSwitch`. + `TtlockBleBatterySensor`, `TtlockBleConnectionBinarySensor`, + `TtlockBleLogEvent`, `TtlockBleLock`. - Exception classes end with `Error`: `TtlockBleApiClientError`, `…CommunicationError`, `…AuthenticationError`. - Private attributes / functions are prefixed with `_`. @@ -171,8 +171,8 @@ with a one-line comment explaining the deliberate narrowing — see - Format: `"Failed to : "` where `` is the exception or a short reason. Keep them short and grep-able. - Pre-validate inputs before the network call so user-facing errors point at - the bad input, not a downstream traceback (`config_flow._validate` rejects - malformed credentials before contacting the API). + the bad input, not a downstream traceback (`manual_key.async_validate` + rejects a malformed key before it is stored on the entry). - Custom exceptions get the same hierarchy: `TtlockBleApiClientError` (base) → `…CommunicationError` (timeout, connection, DNS) and `…AuthenticationError` (401/403). Wrap raw upstream @@ -215,16 +215,18 @@ with a one-line comment explaining the deliberate narrowing — see ## Pre-commit hooks `pre-commit` is a dev dependency (`pyproject.toml`) and `.pre-commit-config.yaml` -mirrors the lint commands (ruff format, ruff check). Install once per -clone: +mirrors the lint commands (ruff format, ruff check, mypy) through local +`uv run` hooks, so every commit runs the exact tool versions pinned in +`pyproject.toml`. Install once per clone: ```bash pre-commit install ``` -The hook runs the same ruff gates as CI on every commit. Skip it only on +The hooks run the same gates as CI on every commit. Skip them only on emergency `git commit --no-verify` and immediately re-run -`uv run ruff format --check .` and `uv run ruff check .`. +`uv run ruff format --check .`, `uv run ruff check .` and +`uv run mypy custom_components/ttlock_ble`. ## Conventional commits diff --git a/README.md b/README.md index 192c1ef..3956cb8 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,16 @@ Local control of TTLock smart locks over Bluetooth, for [Home Assistant](https:/ ## Entities -Each configured lock produces one HA device with three entities: +Each configured lock produces one HA device with four entities: | Entity | Domain | Purpose | |---|---|---| -| `lock.` | `lock` | Locked/unlocked state, with optimistic updates and a post-command settle window. | +| `lock.` | `lock` | Locked/unlocked state, with optimistic updates, `locking`/`unlocking` transitional states and a post-command settle window. | | `sensor._battery` | `sensor` | Battery percentage (diagnostic). | -| `event._operation` | `event` | Fires on every push from the lock, with decoded `lock_state`, `battery`, `uid`, `record_id`, `timestamp` attributes when present. | +| `binary_sensor._connection` | `binary_sensor` | Live BLE link state (connectivity, diagnostic). | +| `event._log` | `event` | Fires for every new operation-log record read from the lock. | + +The event entity classifies each record as `unlock`, `lock`, `unlock_failed`, `password_change` or `other`, and attaches `record_type` and `battery` always, plus `timestamp`, `uid`, `credential`, `key_id` and `accessory_battery` when the record carries them. `credential` is only populated for record types where the value is an identifier (card number, fingerprint id, fob MAC) — record types where it would be a working door code never expose it. ## Installation @@ -82,7 +85,7 @@ The lock's TTLock firmware aggressively closes idle BLE sessions (~5 s of silenc ## Useful commands ```bash -scripts/setup # install dependencies (requirements.txt) +scripts/setup # install dependencies (uv sync, dev + lint groups) scripts/develop # start Home Assistant in debug mode with the integration loaded # Lint and test directly (config lives in pyproject.toml): @@ -92,8 +95,6 @@ uv run mypy custom_components/ttlock_ble uv run pytest ``` -Each `scripts/*` helper auto-detects `./.venv` and prepends it to `PATH` — no `source .venv/bin/activate` needed. - HA runs with config in `config/` and `PYTHONPATH` pointing at the repo root. To reset entity/device IDs during development: ```bash @@ -104,22 +105,23 @@ rm config/.storage/core.entity_registry config/.storage/core.device_registry ``` custom_components/ttlock_ble/ -├── __init__.py # async_setup_entry / unload / reload + bluetooth callbacks +├── __init__.py # config-entry lifecycle: setup / unload / reload ├── advertisement.py # TtlockBleAdvertisementTracker: state from advertisements ├── api.py # TtlockBleApiClient: TTLockCloud wrapper (cloud bootstrap only) +├── binary_sensor.py # TtlockBleConnectionBinarySensor: live BLE link state ├── brand/ # icon / logo PNGs (local placeholder for HA brand registry) ├── config_flow.py # menu / cloud / manual / verify_code / reauth / reconfigure -├── manual_key.py # TtlockBleManualKey: key entry for cloud-less locks ├── connection.py # TtlockBleConnection: persistent BLE session per lock ├── const.py # DOMAIN, LOGGER, defaults ├── coordinator.py # DataUpdateCoordinator polling each connection -├── data.py # TypedDicts + TtlockBleData dataclass +├── data/ # one TypedDict/dataclass per file + type aliases in __init__.py ├── diagnostics.py # redacted credentials/keys ├── entity.py # base CoordinatorEntity with DeviceInfo -├── event.py # TtlockBleOperationEvent push surface +├── event.py # TtlockBleLogEvent: operation-log records as HA events ├── exceptions/ # one file per exception class ├── lock.py # TtlockBleLock: LockEntity backed by the connection ├── manifest.json +├── manual_key.py # TtlockBleManualKey: key entry for cloud-less locks ├── options_flow.py # TtlockBleOptionsFlow: scan_interval, reconnect_interval, permanent_connection ├── sensor.py # TtlockBleBatterySensor backed by polls + pushes └── translations/ @@ -137,15 +139,14 @@ Install once per clone (after `scripts/setup`): pre-commit install ``` -This wires ruff + basic file hygiene checks (`.pre-commit-config.yaml`) into every commit, mirroring the CI lint job. +This wires ruff, mypy and basic file hygiene checks (`.pre-commit-config.yaml`) into every commit, mirroring the CI lint job. ## CI -- **`lint.yml`** — ruff (check + format) and mypy (Python 3.14) -- **`tests.yml`** — pytest with the 90 % coverage gate -- **`validate.yml`** — `hassfest` + HACS validation; push/PR to `main` and a daily cron +- **`ci.yml`** — lint (ruff check + format, mypy), tests (pytest with the 90 % coverage gate) and validation (`hassfest` + HACS) via the shared reusable workflows - **`codeql.yml`** — GitHub CodeQL security scan; push/PR to `main` and a weekly cron - **`release.yml`** — release-please opens a release PR on every push to `main` based on conventional commits +- **`auto-assign.yml`** — assigns the repository owner to new issues and pull requests ## License