- Unify design across slot and lock data cards, with a preference towards the slot card design.
- Add type checking to CI:
- Add type checking CI job to python-checks.yml (mypy already in pre-commit)
- Explore alternatives to mypy (Astral may have a replacement - check for "ty" or similar)
- Fix existing type errors (~49 errors as of Mar 2026)
- Test visual editor for both cards.
- Strategy UI module has unit tests in
ts/*.test.tsand Python tests for resource registration/unload intests/test_init.py; still need end-to-end UI coverage (Lovelace resource registration + reload in a real frontend). - Add provider tests when new integrations beyond Z-Wave JS and virtual are added.
- Test rate limiting and connection failure timing in live environment.
Current Issues:
tests/common.pyhas some duplicated mock setup code- Some tests may have redundant assertions
- Test fixtures could potentially be more reusable across test files
Potential Improvements:
- Consolidate duplicate test setup code into shared fixtures
- Review test naming conventions for clarity
- Consider parametrized tests where multiple similar tests exist
- Reduce test boilerplate with helper functions
Estimated Effort: Medium (4-8 hours) Priority: Medium Status: Not started
Areas to Review:
- Dual storage pattern (
data+optionsin config entries) - Can this be simplified? - Entity unique ID format - Is
{entry_id}|{slot}|{type}optimal? - Multiple coordinator instances - One per lock - could this be unified?
- Internal method wrappers -
async_internal_*methods with locks - are all necessary?
Document and standardize when to read from config_entry.data vs
config_entry.options. Current understanding: prefer options only within the
config entry update listener during options updates; elsewhere use data to
avoid mid-update inconsistencies. Add helper(s) or guidance to reduce ambiguity
and prevent regressions.
Specific Items:
- Review if all
_get_entity_state()calls can be simplified - Evaluate if
_entity_id_mapdictionary caching is worth the complexity - Consider if provider
async_internal_*wrapper methods can be simplified - Review entity base classes for potential consolidation
Estimated Effort: High (20+ hours) Priority: Low-Medium Status: Not started
Current Architecture Issues:
- In-sync binary sensor reads PIN config from text entities via
_get_entity_state() - Reads lock state from coordinator data
- Compares them and triggers sync operations (set_usercode/clear_usercode)
- Cross-entity dependencies create race conditions during startup
- Current guard uses
_attr_is_on is Noneto avoid initial sync operations
Proposed Solution: Move sync logic entirely into the coordinator:
- Coordinator already has access to both desired state (from config) and actual state (from lock)
- Coordinator performs sync operations during its
_async_update_data()cycle - Binary sensor becomes read-only, just displays coordinator's computed in-sync status
- Text/number/switch entities remain as config views
Example Implementation:
# In coordinator._async_update_data()
actual_code = await self.provider.get_usercodes()
desired_code = self.config_entry.data[slot]["pin"]
if actual_code != desired_code and slot_enabled:
await self.provider.set_usercode(slot, desired_code)
return {"in_sync": actual_code == desired_code, "actual_code": actual_code}Benefits:
- Eliminates cross-entity state reading
- Removes
_initial_state_loadedflag and startup detection logic - No race conditions during startup
- Simpler, more centralized sync logic
- Coordinator is single source of truth
Considerations:
- Major architectural change
- Would need to update binary sensor to be read-only
- Config updates still flow through text/switch entities
- Need to ensure coordinator runs sync on config changes
Estimated Effort: High (16-24 hours) Priority: Medium Status: Not started
Convert config entry data to typed dataclasses with from_dict/from_entry
class methods. Use object instances internally instead of iterating through raw
config dicts. Audit codebase for other complex dicts that would benefit from
dataclass conversion (e.g., slot data, lock state, coordinator data). This
improves type safety, IDE autocompletion, and code readability.
Why not Voluptuous? Voluptuous is for validation, not object instantiation.
Other options like dacite or Pydantic add dependencies.
Example implementation:
@dataclass
class SlotConfig:
name: str
pin: str
enabled: bool = True
calendar: str | None = None
number_of_uses: int | None = None
@classmethod
def from_dict(cls, data: dict) -> SlotConfig:
return cls(
name=data[CONF_NAME],
pin=data[CONF_PIN],
enabled=data.get(CONF_ENABLED, True),
calendar=data.get(CONF_CALENDAR),
number_of_uses=data.get(CONF_NUMBER_OF_USES),
)
@dataclass
class LCMConfig:
locks: list[str]
slots: dict[int, SlotConfig]
@classmethod
def from_entry(cls, entry: ConfigEntry) -> LCMConfig:
return cls(
locks=get_entry_data(entry, CONF_LOCKS, []),
slots={
int(k): SlotConfig.from_dict(v)
for k, v in get_entry_data(entry, CONF_SLOTS, {}).items()
},
)Places to audit for dict-to-dataclass conversion:
config_entry.data/config_entry.optionsaccess patternsget_entry_data()/get_slot_data()return values- Coordinator
self.datastructure - Lock provider internal state
Context: Z-Wave locks have userIdStatus which can be Enabled, Available,
or Disabled. Currently the coordinator only stores the code value, not the status.
Investigation Needed:
- Test what happens when LCM tries to set a user code on a slot with
userIdStatus=Disabled - Determine if we need to explicitly enable the slot before setting a code
- Check if different lock brands handle disabled slots differently
Related: See "Enhance Coordinator Data Model" below.
Estimated Effort: Medium (4-8 hours) Priority: Medium Status: Not started
Current State: Coordinator stores {slot: code} mapping only.
Proposed Change: Store {slot: {code, status}} where status is a generic
LCM enum that providers map to.
Generic Status Enum:
class SlotStatus(StrEnum):
ENABLED = "enabled" # Slot has active code
AVAILABLE = "available" # Slot can be used but is empty
DISABLED = "disabled" # Slot cannot be used (locked out)Provider Mapping:
- Z-Wave JS: Maps
userIdStatus(Enabled/Available/Disabled) →SlotStatus - Other providers: Map their equivalent states to the generic enum
Benefits:
- Frontend can distinguish between "slot is empty" vs "slot is disabled"
- Better handling of disabled slots in sync logic
- More accurate representation of lock state
- Provider-agnostic data model
Implementation:
- Define
SlotStatusenum inconst.py - Update Z-Wave JS provider to track userIdStatus and map to
SlotStatus(currently filtered out inon_value_updated) - Change coordinator data schema from
dict[int, str]todict[int, SlotData]whereSlotDataincludes code and status - Update all consumers of coordinator data (binary_sensor, sensor, websocket)
- Update frontend types and rendering
Estimated Effort: High (12-16 hours) Priority: Medium Status: Not started
Context: PR #787 consolidated multiple websocket commands into a single
get_config_entry_data command that returns {config_entry, entities, locks, slots}.
However, not all callers need all the data:
| Caller | config_entry | entities | locks | slots |
|---|---|---|---|---|
view-strategy.ts |
✅ | ❌ | ❌ | ❌ |
generate-view.ts |
❌ | ❌ | ✅ | ✅ |
slot-section-strategy.ts (legacy) |
✅ | ✅ | ❌ | ✅ |
dashboard-strategy.ts |
❌ | ❌ | ✅ | ❌ |
lock-codes-card-editor.ts |
❌ | ❌ | ✅ | ❌ |
Cost Analysis:
config_entry- Very cheap (justconfig_entry.as_json_fragment)slots- Very cheap (extracts calendar IDs from config)locks- Moderate (iterates locks, gets friendly names via state lookups)entities- Most expensive (queries entity registry, maps toas_partial_dict)
Proposed Change: Add optional include_entities and include_locks flags
(default True for backwards compatibility). Skip config_entry and slots
flags since they're essentially free.
vol.Optional("include_entities", default=True): bool,
vol.Optional("include_locks", default=True): bool,Benefits:
view-strategy.tscan skip both entities and locksdashboard-strategy.tsandlock-codes-card-editor.tscan skip entities- Legacy
slot-section-strategy.tscan skip locks
Estimated Effort: Low (2-4 hours) Priority: Low Status: Not started
Track entity registry updates and warn if LCM entities change entity IDs (reload required).
Add get_diagnostic_data() method to BaseLock for exposing provider-specific
diagnostic information. Keymaster has this pattern with get_platform_data().
Example Implementation:
# In BaseLock
def get_diagnostic_data(self) -> dict[str, Any]:
"""Return provider-specific diagnostic data."""
return {}
# In ZWaveJSLock
def get_diagnostic_data(self) -> dict[str, Any]:
return {
"node_id": self.node.node_id,
"home_id": self.node.client.driver.controller.home_id,
"client_connected": self.node.client.connected,
"config_entry_state": self.lock_config_entry.state.value,
}Benefits:
- Easier troubleshooting of provider-specific issues
- Can be exposed via diagnostics platform or websocket
Estimated Effort: Low (2-4 hours) Priority: Low Status: Not started
Add mechanism to alert users when drift detection consistently fails over extended periods (e.g., lock offline). Currently failures are logged but there is no visibility to users or entities.
Problem: Users report that managing calendars in HA is too painful for simple recurring schedules. Calendars are overkill for "weekdays 9-5" patterns.
Proposed Solution: Add alternative condition types alongside calendar:
| Condition Type | Use Case |
|---|---|
calendar |
One-time events, external calendar sync (existing) |
schedule |
Recurring weekly patterns via HA schedule helper |
entity |
Custom logic via any binary sensor/input_boolean |
Implementation:
-
Config Flow Changes:
- Add condition type selector (calendar/schedule/entity)
- Show appropriate entity selector based on type
schedule: EntitySelector filtered toscheduledomainentity: EntitySelector filtered tobinary_sensor,input_boolean
-
Binary Sensor Changes:
- Update
_get_entity_state()to handle different condition types - Schedule entities: check if current time is within schedule (state = "on")
- Entity conditions: directly use entity state
- Update
-
Slot Data Model:
- Add
condition_typefield:calendar | schedule | entity - Add
condition_entityfield (alternative tocalendar) - Maintain backward compatibility with existing
calendarfield
- Add
-
Frontend Updates:
- Update slot card to show condition type
- Show appropriate icon/label for each type
Benefits:
- Schedule helper is much simpler for recurring patterns
- Entity condition allows maximum flexibility (templates, automations)
- Calendar remains available for complex/one-time events
Estimated Effort: Medium (8-12 hours) Priority: Medium-High Status: Not started
Feature Request: Allow slot number and PIN to be configured directly in calendar event metadata, eliminating need for separate slot configuration.
Design Considerations:
Current Behavior:
- Slot configured in config flow with fixed PIN
- Calendar event (when present) only controls whether slot is active/inactive
- PIN and slot number are static configuration
Proposed Behavior:
- Calendar event contains slot number and PIN in its metadata
- User configures regex/pattern to extract slot number and PIN from:
- Event title (e.g., "Slot 3: 1234")
- Event description
- Event location
- Custom calendar properties
Implementation Requirements:
- Config Flow Changes:
- Add "advanced calendar mode" toggle per slot
- When enabled, show pattern configuration UI
- Pattern fields: slot number regex, PIN regex, which field to parse (title/description/location)
- Entity Changes:
- Calendar event listener needs to parse event metadata
- Extract slot number and PIN based on user-defined patterns
- Validate extracted values (numeric slot, PIN format)
- Binary Sensor Changes:
- Check if calendar event contains valid extracted values
- Use extracted PIN instead of configured PIN
- Handle multiple calendar events with different slots
Example Patterns:
- Title: "Guest Access: Slot 5, PIN 9876" -> Slot:
\d+, PIN:\d{4}$ - Description: "Code: 1234 for slot 3" -> Slot:
slot (\d+), PIN:Code: (\d+) - Location: "5:1234" -> Slot:
^(\d+):, PIN::(\d+)$
Challenges:
- Multiple calendar events with different slots
- Error handling for invalid patterns
- UI for testing patterns
- Backward compatibility with existing simple calendar mode
Estimated Effort: Very High (40+ hours) Priority: Medium Status: Not started
Analysis: Review Home Assistant release notes from 2024.1 through current and integrate relevant new features.
Key Features to Evaluate (2024-2025):
- Entity Category Enhancements (2024.x)
- New entity categories available
- Action: Review entity category assignments
- Selector Improvements (2024.x)
- New selector types for config flow
- Action: Review config flow UI for better selectors
- Repair Platform (2024.x)
- Notify users of configuration issues
- Action: Consider adding repairs for common misconfigurations
Already Evaluated (No Changes Needed):
- Config Entry Runtime Data:
hass.data[DOMAIN]correctly holds global cross-config-entry state (lock registry, resource flag). Per-entry data already usesruntime_data. - DataUpdateCoordinator
_async_setup(): All coordinator setup is synchronous (timer registration). No async initialization needed. - Config Entry Diagnostics: No significant internal state to expose beyond what's already visible via entities and config entries.
Estimated Effort: Low-Medium (4-8 hours) Priority: Medium Status: Partially evaluated
Current Providers:
- Z-Wave JS (
zwave_js.py) - Virtual (
virtual.py) - for testing only
Available Lock Integrations in Home Assistant Core:
Smart Home Platforms:
deconz- deCONZ (Zigbee/Z-Wave gateway)homematic- Homematic (CCU)homematicip_cloud- Homematic IP Cloudhomekit_controller- HomeKit accessoriesmatter- Matter protocol (PR open)mqtt- MQTT lockssmartthings- SmartThingszha- Zigbee Home Automationzwave_js- Z-Wave JS (already supported)zwave_me- Z-Wave.Me
Brand-Specific Integrations:
abode- Abode Securitybmw_connected_drive- BMW Connected Drivedormakaba_dkey- Dormakaba dkeyigloohome- igloohomekiwi- Kiwi (Eufy)loqed- Loqed Smart Locknuki- Nuki Smart Lockschlage- Schlage Encodesesame- Sesame Smart Lockswitchbot- SwitchBot Lockswitchbot_cloud- SwitchBot Cloudtedee- Tedee Smart Lockyolink- YoLink
Security Systems:
simplisafe- SimpliSafeverisure- Verisure
Cannot Be Supported (see README for details):
esphome- No user code API in ESPHomeaugust,yale,yalexs_ble,yale_smart_alarm- Library limitations
Vehicle Integrations:
subaru- Subaru Starlinktesla_fleet- Tesla Fleet APIteslemetry- Teslemetrytessie- Tessie (Tesla)starline- StarLine
Other Integrations:
fibaro- Fibarofreedompro- Freedomproinsteon- Insteonisy994- Universal Devices ISYkeba- KEBA EV Chargeroverkiz- Overkiz (Somfy TaHoma)surepetcare- Sure Petcareunifiprotect- UniFi Protectvera- Verawallbox- Wallbox EV Chargerxiaomi_aqara- Xiaomi Aqara
Utility/Helper Integrations:
demo- Demo platformgroup- Lock groupskitchen_sink- Testing platformswitch_as_x- Convert switches to lockstemplate- Template lockshomee- Homee gateway
Recommended Priorities for Support:
High Priority (popular, widely used):
- ZHA (Zigbee Home Automation) - Very popular, supports many lock brands
- Matter - Future-proof, industry standard (PR open)
- MQTT - Generic protocol, many custom implementations
Medium Priority (brand-specific, popular):
- Nuki - Popular in Europe
- Schlage - Popular in North America
- SwitchBot - Growing popularity
Low Priority (niche or less common):
- Vehicle locks (Tesla, BMW, Subaru) - different use case
- Security system locks - usually managed by their own systems
- Utility integrations (template, group) - may work without specific provider
Implementation Approach:
For each new provider:
- Create
providers/INTEGRATION_NAME.py - Subclass
BaseLock - Implement required methods
- Add integration-specific event listeners
- Add tests in
tests/INTEGRATION_NAME/test_provider.py - Update documentation
Estimated Effort per Provider: Medium (6-12 hours each) Priority: Medium-High Status: Not started Recommended First Addition: ZHA (Zigbee)
Ideas:
- Add visual indicator when codes are out of sync
- Bulk operations (enable/disable multiple slots)
- Import/export slot configuration
- QR code generation for PIN sharing
- History view showing when codes were used
- Slot templates for quick configuration
Estimated Effort: High (20+ hours) Priority: Medium Status: Not started
Ideas:
lock_code_manager.set_temporary_code- Create time-limited PINlock_code_manager.generate_pin- Auto-generate secure PINlock_code_manager.bulk_enable- Enable multiple slots at oncelock_code_manager.bulk_disable- Disable multiple slots at oncelock_code_manager.copy_slot- Copy configuration from one slot to another- Note:
hard_refresh_usercodesservice already exists for lock-wide refresh.
Estimated Effort: Medium (8-16 hours) Priority: Medium-Low Status: Not started
Ideas:
- Notify when PIN is used (already have event entity, could add notification action)
- Notify when code goes out of sync
- Notify when calendar event starts (PIN becomes active)
- Notify when number of uses is depleted
Estimated Effort: Medium (6-10 hours) Priority: Low Status: Not started
Ideas:
- Warn if PIN is too simple (e.g., "1234", "0000")
- Warn if multiple slots use same PIN
- Validate PIN format against lock requirements
- Check for slot conflicts across config entries
Estimated Effort: Low-Medium (4-8 hours) Priority: Medium Status: Not started
- Better out-of-sync visibility in the UI.
- Websocket commands expose lock data via
lock_code_manager/subscribe_lock_codesand slot data vialock_code_manager/subscribe_code_slot. Thelcm-lock-codesandlcm-slotfrontend cards subscribe to these respectively.
CLAUDE.mdpoints toAGENTS.md; updateAGENTS.mdafter architecture changes.- Review TODOs after completing current work, when starting new features, during
refactoring sessions, or on
/todos.