The current stock Matter lock driver doesn’t support the door state, so I just added it. I’m using the contactSensor
capability until there’s a better alternative in the production capabilities. Only the DOOR_CLOSED
state is considered as closed, every other state is considered as open.
This is what it looks like:
Driver can be found in this channel: SmartThings. Add a little smartness to your things.
And here’s the patch if you want to apply it yourself:
diff '--color=auto' -Nur matter-lock.orig/config.yml matter-lock/config.yml
--- matter-lock.orig/config.yml 2025-04-25 11:49:48.054721221 +0200
+++ matter-lock/config.yml 2025-04-17 12:54:09.365940200 +0200
@@ -1,5 +1,5 @@
-name: 'Matter Lock'
-packageKey: 'matter-lock'
+name: 'Matter Lock (AR)'
+packageKey: 'matter-lock-ar'
permissions:
matter: {}
description: "SmartThings driver for Matter lock devices"
diff '--color=auto' -Nur matter-lock.orig/profiles/lock-unlatch-contact-battery.yml matter-lock/profiles/lock-unlatch-contact-battery.yml
--- matter-lock.orig/profiles/lock-unlatch-contact-battery.yml 1970-01-01 01:00:00.000000000 +0100
+++ matter-lock/profiles/lock-unlatch-contact-battery.yml 2025-04-17 12:53:52.825637118 +0200
@@ -0,0 +1,131 @@
+name: lock-unlatch-contact-battery
+components:
+- id: main
+ capabilities:
+ - id: lock
+ version: 1
+ config:
+ values:
+ - key: "lock.value"
+ enabledValues:
+ - locked
+ - unlocked
+ - unlatched
+ - not fully locked
+ - id: lockAlarm
+ version: 1
+ - id: remoteControlStatus
+ version: 1
+ - id: contactSensor
+ version: 1
+ - id: battery
+ version: 1
+ - id: firmwareUpdate
+ version: 1
+ - id: refresh
+ version: 1
+ categories:
+ - name: SmartLock
+deviceConfig:
+ dashboard:
+ states:
+ - component: main
+ capability: lock
+ version: 1
+ - component: main
+ capability: contactSensor
+ version: 1
+ actions:
+ - component: main
+ capability: lock
+ version: 1
+ visibleCondition: {
+ "capability": "lock",
+ "version": "1",
+ "component": "main",
+ "value": "lock.value",
+ "operator": "DOES_NOT_EQUAL",
+ "operand": "unlatched"
+ }
+ detailView:
+ - component: main
+ capability: lock
+ version: 1
+ values:
+ - key: lock.value
+ alternatives:
+ - key: locked
+ type: inactive
+ value: '{{i18n.attributes.lock.i18n.value.locked.label}}'
+ - key: unlocked
+ value: '{{i18n.attributes.lock.i18n.value.unlocked.label}}'
+ - key: unlatched
+ value: '{{i18n.attributes.lock.i18n.value.unlatched.label}}'
+ - key: not fully locked
+ value: '{{i18n.attributes.lock.i18n.value.not fully locked.label}}'
+ patch:
+ - op: add
+ path: /1
+ value:
+ capability: lock
+ version: 1
+ component: main
+ label: '{{i18n.commands.unlatch.label}}'
+ displayType: pushButton
+ pushButton:
+ command: unlatch
+ - component: main
+ capability: lockAlarm
+ version: 1
+ - component: main
+ capability: remoteControlStatus
+ version: 1
+ - component: main
+ capability: contactSensor
+ version: 1
+ - component: main
+ capability: battery
+ version: 1
+ automation:
+ conditions:
+ - component: main
+ capability: lock
+ version: 1
+ values:
+ - key: lock.value
+ alternatives:
+ - key: locked
+ type: inactive
+ value: '{{i18n.attributes.lock.i18n.value.locked.label}}'
+ - key: unlocked
+ value: '{{i18n.attributes.lock.i18n.value.unlocked.label}}'
+ - key: unlatched
+ value: '{{i18n.attributes.lock.i18n.value.unlatched.label}}'
+ - key: not fully locked
+ value: '{{i18n.attributes.lock.i18n.value.not fully locked.label}}'
+ - component: main
+ capability: lockAlarm
+ version: 1
+ - component: main
+ capability: remoteControlStatus
+ version: 1
+ - component: main
+ capability: contactSensor
+ version: 1
+ - component: main
+ capability: battery
+ version: 1
+ actions:
+ - component: main
+ capability: lock
+ version: 1
+ values:
+ - key: '{{enumCommands}}'
+ alternatives:
+ - key: lock
+ type: inactive
+ value: '{{i18n.commands.lock.label}}'
+ - key: unlock
+ value: '{{i18n.commands.unlock.label}}'
+ - key: unlatch
+ value: '{{i18n.commands.unlatch.label}}'
diff '--color=auto' -Nur matter-lock.orig/src/new-matter-lock/init.lua matter-lock/src/new-matter-lock/init.lua
--- matter-lock.orig/src/new-matter-lock/init.lua 2025-04-25 11:49:48.063722631 +0200
+++ matter-lock/src/new-matter-lock/init.lua 2025-04-25 12:38:49.783969510 +0200
@@ -71,6 +71,9 @@
DoorLock.attributes.NumberOfWeekDaySchedulesSupportedPerUser,
DoorLock.attributes.NumberOfYearDaySchedulesSupportedPerUser
},
+ [capabilities.contactSensor.ID] = {
+ DoorLock.attributes.DoorState
+ },
[capabilities.battery.ID] = {
PowerSource.attributes.BatPercentRemaining
},
@@ -150,6 +153,7 @@
local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES})
local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES})
local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT})
+ local doorstate_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.DOOR_STATE})
local battery_eps = device:get_endpoints(PowerSource.ID, {feature_bitmap = PowerSource.types.PowerSourceFeature.BATTERY})
local profile_name = "lock"
@@ -168,6 +172,10 @@
else
device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}}))
end
+ if #doorstate_eps > 0 then
+ profile_name = profile_name .. "-contact"
+ device:add_subscribed_attribute(DoorLock.attributes.DoorState)
+ end
if #battery_eps > 0 then
device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true})
local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {})
@@ -238,6 +246,27 @@
end)
end
+-- NEW: DoorState handler
+local function door_state_handler(driver, device, ib, response)
+ local DoorState = DoorLock.types.DoorStateEnum
+ local attr = capabilities.contactSensor.contact
+
+ local DOOR_STATE_MAP = {
+ [DoorState.DOOR_OPEN] = attr.open(),
+ [DoorState.DOOR_CLOSED] = attr.closed(),
+ [DoorState.DOOR_JAMMED] = attr.open(), -- Optional mapping
+ [DoorState.DOOR_FORCED_OPEN] = attr.open(), -- Optional mapping
+ [DoorState.DOOR_UNSPECIFIED_ERROR] = attr.open(), -- Optional mapping
+ [DoorState.DOOR_AJAR] = attr.open(), -- Optional mapping
+ }
+
+ if ib.data.value ~= nil then
+ device:emit_event(DOOR_STATE_MAP[ib.data.value])
+ else
+ device.log.warn("Door State is nil")
+ end
+end
+
---------------------
-- Operating Modes --
---------------------
@@ -1743,6 +1772,7 @@
attr = {
[DoorLock.ID] = {
[DoorLock.attributes.LockState.ID] = lock_state_handler,
+ [DoorLock.attributes.DoorState.ID] = door_state_handler,
[DoorLock.attributes.OperatingMode.ID] = operating_modes_handler,
[DoorLock.attributes.NumberOfTotalUsersSupported.ID] = total_users_supported_handler,
[DoorLock.attributes.NumberOfPINUsersSupported.ID] = pin_users_supported_handler,
@@ -1809,6 +1839,7 @@
capabilities.lockUsers,
capabilities.lockCredentials,
capabilities.lockSchedules,
+ capabilities.contactSensor,
capabilities.battery,
capabilities.batteryLevel
},
TODO: Custom capability, GitHub PR.