Summary
The new-matter-lock sub-driver can leave a stale lockAlarm.alarm value in device state when a Matter lock starts on a fallback/static profile that does not support lockAlarm, and later migrates to a modular profile that does support it.
In the observed case, a Nuki Smart Lock Ultra still showed:
"lockAlarm": {
"supportedAlarmValues": {
"value": [
"unableToLockTheDoor"
]
},
"alarm": {
"value": "unableToLockTheDoor"
}
}
even though the lock was online, working normally, and reporting lock = locked.
The stale alarm value had an old timestamp and appeared to be persisted state that was not cleared after the profile changed.
Tested device
This was observed with a real Nuki Smart Lock Ultra paired to SmartThings via Matter.
The same device also demonstrates the modular profile migration path discussed separately: after the DoorLock FeatureMap is processed, the driver can migrate the device to:
lock-modular-embedded-unlatch
That profile supports lockAlarm.
Observed behavior in logs
When the device is first handled, it starts from a fallback/static profile that does not support lockAlarm.
During the added lifecycle event, the driver attempts to emit a lockAlarm event:
received lifecycle event: added
Attempted to generate event for <device-id>.main but it does not support capability lockAlarm
This means the initial alarm.clear() event cannot be applied because the active profile does not yet include the lockAlarm capability.
Later, after the FeatureMap is read and the device migrates to the modular profile, the device can support lockAlarm. In the working reprofiling path, the log shows:
Updating device profile to lock-modular-embedded-unlatch. Enabling the optional capabilities [battery] on component 'main'
and then after infoChanged:
received lifecycle event: infoChanged
emitting event: {"attribute_id":"alarm","capability_id":"lockAlarm","component_id":"main","state":{"value":"clear"},"state_change":true}
This confirms that alarm.clear() can only be emitted successfully after the profile has changed to one that includes lockAlarm.
Current issue
The driver currently has lock-alarm initialization logic in multiple lifecycle paths, but it is not consistently guarded by whether the current profile actually supports lockAlarm.
As a result:
alarm.clear() may be emitted too early, while the fallback profile does not support lockAlarm.
- That event is rejected/ignored by the platform.
- After the profile migration, the driver may not reliably emit
alarm.clear() again in all relevant lifecycle paths.
- A stale persisted alarm such as
unableToLockTheDoor can remain visible in component status even though it is no longer current.
This is especially confusing because supportedAlarmValues and the current alarm state are different concepts:
supportedAlarmValues is capability metadata.
alarm.clear() is current state cleanup.
The driver should not only emit alarm.clear() when supportedAlarmValues is missing. A stale current alarm can exist even when supportedAlarmValues is already present.
Expected behavior
The driver should initialize/clear lockAlarm only when the active profile supports lockAlarm, and should do so after lifecycle events where the profile may have changed.
Expected behavior:
- If the current profile does not support
lockAlarm, do not attempt to emit lockAlarm events.
- Once the profile supports
lockAlarm, emit alarm.clear() to remove stale alarm state.
- Emit
supportedAlarmValues({"unableToLockTheDoor"}) only if the metadata is missing.
- Continue to handle real DoorLockAlarm events normally.
Suggested fix
Add a small helper that initializes the lockAlarm capability only when the current profile supports it:
local function initialize_lock_alarm(device)
if not device:supports_capability_by_id(capabilities.lockAlarm.ID) then
return
end
device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true}))
if device:get_latest_state("main", capabilities.lockAlarm.ID, capabilities.lockAlarm.supportedAlarmValues.NAME) == nil then
device:emit_event(capabilities.lockAlarm.supportedAlarmValues(
{"unableToLockTheDoor"},
{visibility = {displayed = false}}
)) -- lockJammed is mandatory
end
end
Then use that helper from lifecycle paths where the current profile may already support, or may have just gained, lockAlarm support:
local function device_added(driver, device)
initialize_lock_alarm(device)
end
local function info_changed(driver, device, event, args)
...
initialize_lock_alarm(device)
end
local function do_configure(driver, device)
match_profile(driver, device, false)
device.thread:call_with_delay(5, function()
initialize_lock_alarm(device)
end)
end
local function driver_switched(driver, device)
match_profile(driver, device, false)
initialize_lock_alarm(device)
end
This keeps the behavior conservative:
- It does not emit unsupported
lockAlarm events while the fallback profile is active.
- It clears stale alarm state once
lockAlarm is actually available.
- It does not change DoorLockAlarm event handling.
- It does not change supported alarm values, except to initialize them if missing.
Why this matters
A stale unableToLockTheDoor alarm can make a normally working lock appear to be in an error state. For door locks, this is particularly undesirable because users may interpret the old alarm as a current lock failure.
The issue is not that the lock is currently unable to lock. The issue is that an old persisted alarm state is not reliably cleared after the profile/capability set changes.
@hcarter-775 @ctowns Since this is somewhat of a timing issue and I can only observe it on my device, this should be replicated with other real devices.
Summary
The
new-matter-locksub-driver can leave a stalelockAlarm.alarmvalue in device state when a Matter lock starts on a fallback/static profile that does not supportlockAlarm, and later migrates to a modular profile that does support it.In the observed case, a Nuki Smart Lock Ultra still showed:
even though the lock was online, working normally, and reporting
lock = locked.The stale alarm value had an old timestamp and appeared to be persisted state that was not cleared after the profile changed.
Tested device
This was observed with a real Nuki Smart Lock Ultra paired to SmartThings via Matter.
The same device also demonstrates the modular profile migration path discussed separately: after the DoorLock FeatureMap is processed, the driver can migrate the device to:
That profile supports
lockAlarm.Observed behavior in logs
When the device is first handled, it starts from a fallback/static profile that does not support
lockAlarm.During the
addedlifecycle event, the driver attempts to emit alockAlarmevent:This means the initial
alarm.clear()event cannot be applied because the active profile does not yet include thelockAlarmcapability.Later, after the FeatureMap is read and the device migrates to the modular profile, the device can support
lockAlarm. In the working reprofiling path, the log shows:and then after
infoChanged:This confirms that
alarm.clear()can only be emitted successfully after the profile has changed to one that includeslockAlarm.Current issue
The driver currently has lock-alarm initialization logic in multiple lifecycle paths, but it is not consistently guarded by whether the current profile actually supports
lockAlarm.As a result:
alarm.clear()may be emitted too early, while the fallback profile does not supportlockAlarm.alarm.clear()again in all relevant lifecycle paths.unableToLockTheDoorcan remain visible in component status even though it is no longer current.This is especially confusing because
supportedAlarmValuesand the currentalarmstate are different concepts:supportedAlarmValuesis capability metadata.alarm.clear()is current state cleanup.The driver should not only emit
alarm.clear()whensupportedAlarmValuesis missing. A stale current alarm can exist even whensupportedAlarmValuesis already present.Expected behavior
The driver should initialize/clear
lockAlarmonly when the active profile supportslockAlarm, and should do so after lifecycle events where the profile may have changed.Expected behavior:
lockAlarm, do not attempt to emitlockAlarmevents.lockAlarm, emitalarm.clear()to remove stale alarm state.supportedAlarmValues({"unableToLockTheDoor"})only if the metadata is missing.Suggested fix
Add a small helper that initializes the
lockAlarmcapability only when the current profile supports it:Then use that helper from lifecycle paths where the current profile may already support, or may have just gained,
lockAlarmsupport:This keeps the behavior conservative:
lockAlarmevents while the fallback profile is active.lockAlarmis actually available.Why this matters
A stale
unableToLockTheDooralarm can make a normally working lock appear to be in an error state. For door locks, this is particularly undesirable because users may interpret the old alarm as a current lock failure.The issue is not that the lock is currently unable to lock. The issue is that an old persisted alarm state is not reliably cleared after the profile/capability set changes.
@hcarter-775 @ctowns Since this is somewhat of a timing issue and I can only observe it on my device, this should be replicated with other real devices.