Skip to content

Matter Lock: add DoorState support for locks with Door Position Sensor support #2947

@ldeora

Description

@ldeora

Summary

The Matter Lock driver currently does not expose the Matter Door Lock DoorState attribute through the SmartThings doorState capability.

For Matter locks that support the Door Lock DOOR_POSITION_SENSOR feature, the driver should expose the door position as a read-only doorState capability. This allows SmartThings to distinguish between the lock state and the actual door state.

Example:

Lock state: locked / unlocked / unlatched / not fully locked
Door state: open / closed / ajar / jammed / forcedOpen / unspecifiedError

These are separate concepts and both are useful for locks such as the Nuki Smart Lock Ultra.

Tested device

This was tested with a real Nuki Smart Lock Ultra paired to SmartThings via Matter.

The device reports Door Lock FeatureMap support for the relevant functionality and can provide a valid DoorState value. With a local implementation, SmartThings component status correctly reported:

"doorState": {
  "doorState": {
    "value": "closed"
  },
  "supportedDoorStates": {
    "value": [
      "open",
      "closed",
      "jammed",
      "forcedOpen",
      "unspecifiedError",
      "ajar"
    ]
  }
}

The doorState capability also became available as a Routine condition when added to the generated presentation.

Current driver behavior

The current Matter Lock driver handles LockState, OperatingMode, battery-related PowerSource attributes, lock users, lock credentials, schedules, Aliro, and lock alarms.

However, it does not currently expose DoorLock.attributes.DoorState through the doorState capability.

As a result, the device can support door-position information at the Matter layer, but SmartThings does not expose it to the user or to Routines.

Why this matters

For a door lock, lock state and door state are different and both are important.

Examples:

  • A lock can be locked while the door is physically closed.
  • A lock can be unlocked while the door is still closed.
  • A door can be open while the lock state is not sufficient to describe the physical situation.
  • Automations often need to know whether the door is actually closed before locking.
  • Users may want Routines such as “notify me if the door is open” or “only lock if the door is closed.”

For devices such as the Nuki Smart Lock Ultra, exposing DoorState makes SmartThings a better Matter controller because it can represent the full lock/door state instead of only the lock actuator state.

Expected behavior

For Matter locks whose Door Lock cluster supports DOOR_POSITION_SENSOR, the driver should:

  1. Add doorState as an optional capability to the relevant modular lock profiles.
  2. Enable the optional doorState capability only when the Matter Door Lock FeatureMap indicates Door Position Sensor support.
  3. Subscribe to DoorLock.attributes.DoorState.
  4. Map Matter DoorStateEnum values to SmartThings doorState values.
  5. Emit a fixed, safe supportedDoorStates list when the capability is active.
  6. Read DoorState during refresh when the capability is supported.
  7. Avoid using contactSensor as a workaround; this should use the standard doorState capability.

Suggested mapping

The Matter Door Lock DoorStateEnum values should map to SmartThings doorState values as follows:

Matter DoorStateEnum SmartThings doorState
DOOR_OPEN open
DOOR_CLOSED closed
DOOR_JAMMED jammed
DOOR_FORCED_OPEN forcedOpen
DOOR_UNSPECIFIED_ERROR unspecifiedError
DOOR_AJAR ajar

Unknown or unsupported values should fall back conservatively to unspecifiedError.

Important implementation note

supportedDoorStates should not be built as a history of observed door-state reports.

A safe implementation should emit a fixed list such as:

local SUPPORTED_DOOR_STATES = {
  "open",
  "closed",
  "jammed",
  "forcedOpen",
  "unspecifiedError",
  "ajar",
}

and then emit only the current state separately:

device:emit_event(capabilities.doorState.doorState.closed())

This is important because supportedDoorStates is metadata. It should not grow over time as repeated open / closed reports arrive.

Suggested implementation approach

1. Add a profiling field

local profiling_data = {
  BATTERY_SUPPORT = "__BATTERY_SUPPORT",
  ENABLE_DOOR_STATE = "__ENABLE_DOOR_STATE",
}

2. Add DoorState to subscribed attributes

[capabilities.doorState.ID] = {
  DoorLock.attributes.DoorState
},

3. Enable the optional capability only when supported

In match_profile_modular(), add doorState only when Door Position Sensor support has been detected:

if device:get_field(profiling_data.ENABLE_DOOR_STATE) then
  table.insert(main_component_capabilities, capabilities.doorState.ID)
end

The ENABLE_DOOR_STATE field can be set from the Door Lock FeatureMap:

if ib.data.value & DoorLock.types.Feature.DOOR_POSITION_SENSOR == 0 then
  device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true})
else
  device:set_field(profiling_data.ENABLE_DOOR_STATE, true, {persist = true})
end

After updating that field, the driver should run the profile matching logic so the optional capability can be enabled.

4. Add a DoorState handler

local SUPPORTED_DOOR_STATES = {
  "open",
  "closed",
  "jammed",
  "forcedOpen",
  "unspecifiedError",
  "ajar",
}

local function emit_supported_door_states_if_needed(device)
  if device:supports_capability_by_id(capabilities.doorState.ID) and
    device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then
    device:emit_event(capabilities.doorState.supportedDoorStates(
      SUPPORTED_DOOR_STATES,
      {visibility = {displayed = false}}
    ))
  end
end

local function door_state_handler(driver, device, ib, response)
  if ib.data.value == nil then
    return
  end

  if not device:supports_capability_by_id(capabilities.doorState.ID) then
    return
  end

  emit_supported_door_states_if_needed(device)

  local DoorStateEnum = DoorLock.types.DoorStateEnum
  local door_state = capabilities.doorState.doorState

  local DOOR_STATE_MAP = {
    [DoorStateEnum.DOOR_OPEN] = door_state.open,
    [DoorStateEnum.DOOR_CLOSED] = door_state.closed,
    [DoorStateEnum.DOOR_JAMMED] = door_state.jammed,
    [DoorStateEnum.DOOR_FORCED_OPEN] = door_state.forcedOpen,
    [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = door_state.unspecifiedError,
    [DoorStateEnum.DOOR_AJAR] = door_state.ajar,
  }

  local event_factory = DOOR_STATE_MAP[ib.data.value] or door_state.unspecifiedError
  device:emit_event(event_factory())
end

5. Register the attribute handler

[DoorLock.attributes.DoorState.ID] = door_state_handler,

6. Add doorState to modular profiles

The relevant modular lock profiles should declare doorState as optional:

- id: doorState
  version: 1
  optional: true

At minimum, this is needed for:

lock-modular.yml
lock-modular-embedded-unlatch.yml

7. Add DoorState to presentation / automations

For profiles with embedded deviceConfig, doorState should be included as a read-only state in detailView and as an automation condition.

Example:

detailView:
  - component: main
    capability: doorState
    version: 1
automation:
  conditions:
    - component: main
      capability: doorState
      version: 1

It should not be added as an automation action or as a dashboard action because doorState is read-only.

Local test result

With a local implementation on a real Nuki Smart Lock Ultra:

  • The device migrated to lock-modular-embedded-unlatch.
  • The doorState capability was enabled.
  • Component status reported doorState.closed.
  • supportedDoorStates remained a fixed list and did not grow.
  • doorState appeared as a Routine condition.
  • Lock/unlock/unlatch command behavior was not changed.

App UI note

The generated device presentation can include doorState in detailView and automation.conditions.

In testing, doorState appeared in Routines as a condition. The SmartThings app detail screen for the lock may still use a specialized Smart Lock UI/plugin that does not render every generated detail-view item. If so, the app detail UI may require separate handling. However, the driver-side capability state and Routine condition support work correctly once doorState is exposed.

Suggested tests

Recommended tests:

  1. Matter lock with DOOR_POSITION_SENSOR support enables optional doorState.
  2. Matter lock without DOOR_POSITION_SENSOR support does not enable doorState.
  3. DoorState reports map correctly:
    • open
    • closed
    • ajar
    • jammed
    • forcedOpen
    • unspecifiedError
  4. Repeated open/closed reports do not append duplicate values to supportedDoorStates.
  5. Refresh reads DoorState only when the device supports the doorState capability.
  6. Routines expose doorState as a condition.
  7. No changes are made to lock/unlock/unlatch command semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions