Added Door State to Matter lock driver

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. :face_with_spiral_eyes:

8 Likes

Hi, @Andreas_Roedl
I found this in the Matter Cluster Specification. Is that the “door state” you’re discussing here?

In the case of your device, would that be connected through a manufacturer-specific or a generic fingerprint?
I’m asking because I’m not sure if every device supports this cluster and if yours is part of the certified ones I can present the case for the engineering team.
Having the model and brand would also help to have a better reference.

Yes, that’s the correct spec. It’s a Nuki Ultra lock and so far the only Matter Lock that has this Matter feature. I’m in touch with someone from Aqara to support the DoorState in the U200 Lite - sounds like they simply forgot that there’s something like that in the Matter specs.

The Nuki fingerprints are in the “new-matter-lock” as you know.

What’s really missing is a fitting capability. Otherwise I would have submitted a PR to at least raise some awareness. As you can see, it’s just an additional profile and a few lines added, not deleted or modified.

Interestingly, the Nuki Ultra uses a Bluetooth contact sensor that is connected to the lock and the U200 Lite uses an internal gyroscope.

Yes, but that’s using an alternative capability, I was going to check if we could make a request to create one that has those possible Enums. (this can take longer)
But just to be clear, you’re request for now is to “contactSensor” instead, correct?

I’ve only used the contactSensor, because there is no doorState capability yet. And I wouldn’t recommend the contactSensor, because it is a binary state and every other state would be missing.

Implementing the DoorState without a matching enum capability would be a bad idea. You can discuss it internally how such a capability should look like.

So my request is to create a DoorState capability and the implementation of the Matter DoorState feature in the Matter lock driver.

ah ok, I got confused because you mentioned a few times, it was only a few changes and creating a new capability is a complex process (if the request gets approved).
So I just want to make sure that we’re in the same understanding.

1 Like

BTW: thank you so much for responding. It would be awesome if this gets implemented.

1 Like

OHallo Andreas,

Ich habe deinen Treiber installiert (ich hoffe ich hab es richtig gemacht).

Jedoch sieht meine Ansicht vom Smartlock in Smartthings anders aus als deine.

Der Kontaktsensor wird gar nicht angezeigt und '“Sperren” , “Aufschließen” , “Entriegeln” wird bei mir gar nicht angezeigt, weder beim Stock Treiber noch bei deinem.

Hab ich was falsch gemacht?

Solved (via PM): device must be re-added after switching the driver.

Just wanted to let you know that your driver works perfectly. I was having issues with the ST official matter lock driver and my aqara U100, I tryed everything but the lock didn’t report it state to ST.

I changed to your driver and it worked first try. Thank you very much.

1 Like

I am using this for an ultraloq matter lock. Thank you very much I love how it reports the built-in contact sensor status. However the tile for contact sensor replaced the tile for manage users. It’s weird this matter lock does not report to smart lock like my old Z-Wave, and has a built-in user management system just for specifically that lock. I don’t really like it because I’d love to use this lock to replace a bunch, but I don’t feel like programming 20 codes in each fuck individually. Off the tangent of Smart Lock access, is there a way that the user management tile can be a part of your drive r too that shows contact status? I’m trying to learn the edge drivers developer thing but not having much luck.

I was looking into this just today. This driver is based on a version of the stock driver from before they introduced modular profiles. If I find the time this week, I’ll add the door state feature to the current modular driver. In the meantime, you should use the stock driver, if you want user management.


Taking notes here… ultraloq profile:

name: lock-user-pin-battery
components:
- id: main
  capabilities:
  - id: lock
    version: 1
    config:
      values:
      - key: "lock.value"
        enabledValues:
        - locked
        - unlocked
        - not fully locked
  - id: lockAlarm
    version: 1
  - id: remoteControlStatus
    version: 1
  - id: lockUsers
    version: 1
  - id: lockCredentials
    version: 1
  - id: battery
    version: 1
  - id: firmwareUpdate
    version: 1
  - id: refresh
    version: 1
  categories:
  - name: SmartLock

Thank you very much!

I was trying to read up about the Smart Lock guest access applet and it seems it might not work everywhere, however it works in my area. Do you have an idea what it would take to get this lock added to that applet?

The Smart Lock Guest Access smart app is region locked. As far as I know it’s only available in the US and some asian countries.

Can you please provide a screenshot from the device card with the user management? I’d also need the manufacturer/model IDs of your lock. You can find them in the information screen (three dots).

Certainly!

(attachments)



1 Like

Here’s something that is also cookie dukes, is that in the ultraloq phone app you can add users and codes, but they do not show up in smart things, and vice versa that smart things codes do not appear in the ultra lock app. This lock has the ability to accept fingerprints and that is the only reason I am keeping the phone app for now because it’s the only way you can add users by fingerprint, but this lock can exclusively work on smart things but does not have fingerprint adding ability through smart things.

This is what Aliro is for. The driver I’m currently working on is based on a driver that already has Aliro support. And that’s the reason why I had a look at the driver today: my Nuki Ultra lock supposedly supports Aliro with the beta firmware released yesterday.

I don’t know if the current app can handle these capabilities, though. We’ll see…

One more thing:

Can you please log in here, select your lock and tell me what profile it uses?

According to the Aliro page, it’s supported. I’m happy with the locks capabilities and functionality, just weird how it’s supposed to work but it doesn’t but it does. I wish it was like the Z-Wave locks where everything worked easily with ST. I’m hoping all the functionalities will show up in time.

Changed

  • Rebased driver on the latest stock SmartThings Matter Lock Edge Driver that uses modular profiles.
    • Keeps full compatibility with the official driver’s structure (modular profile selection, Aliro support, embedded profiles, etc.).
  • Pulled in upstream changes from
    SmartThingsCommunity/SmartThingsEdgeDrivers PR #2494
    (updates to the modular lock profiles and embedded/unlatch handling - still not perfect.).

Added – DoorState support in modular profiles

  • Added DoorState → contactSensor mapping for the DoorLock cluster:
    • Subscribes to DoorLock.attributes.DoorState when appropriate.
    • Maps the Matter enum values to contactSensor events:
      • DOOR_OPEN, DOOR_AJAR, DOOR_JAMMED, DOOR_FORCED_OPEN, DOOR_UNSPECIFIED_ERRORcontact: open
      • DOOR_CLOSEDcontact: closed.

Improved – Modular profile selection & support for unusual locks

  • Ensured that locks exposing the UNBOLT feature use the modular profile
    lock-modular-embedded-unlatch and get:
    • supportedLockValues = [locked, unlocked, unlatched, not fully locked]
    • supportedLockCommands = [lock, unlock, unlatch].
  • Verified that Nuki Smart Lock Ultra now:
    • uses a modular profile instead of a static one,
    • exposes DoorState as a contact sensor (after first DoorState report),
    • retains unlatch support via UNBOLT.

Added – Extensive logging around modular profiles & DoorState

  • Added detailed logging for match_profile_modular:
    • Logs start of matching with device name, VID, PID, API and RPC versions.
    • Logs inspected endpoints and the DoorLock feature map.
    • Logs the evaluation of key features:
      • DOOR_STATE
      • UNBOLT
      • battery support (percentage vs level)
      • Aliro-related features (where applicable).
    • Logs chosen modular profile name and which optional capabilities were enabled.
  • Enhanced door_state_handler logging:
    • Logs the first time DoorState is seen and when contactSensor support is forced.
    • Logs unexpected or unsupported DoorState values for easier debugging.
1 Like