Skip to content

Support DPS feature#2750

Open
HunsupJung wants to merge 4 commits intomainfrom
feature/doorlock-dps-feature
Open

Support DPS feature#2750
HunsupJung wants to merge 4 commits intomainfrom
feature/doorlock-dps-feature

Conversation

@HunsupJung
Copy link
Collaborator

@HunsupJung HunsupJung commented Feb 3, 2026

Type of Change

  • WWST Certification Request
    • If this is your first time contributing code:
      • I have reviewed the README.md file
      • I have reviewed the CODE_OF_CONDUCT.md file
      • I have signed the CLA
    • I plan on entering a WWST Certification Request or have entered a request through the WWST Certification console at developer.smartthings.com
  • Bug fix
  • New feature
  • Refactor

Checklist

  • I have performed a self-review of my code
  • I have commented my code in hard-to-understand areas
  • I have verified my changes by testing with a device or have communicated a plan for testing
  • I am adding new behavior, such as adding a sub-driver, and have added and run new unit tests to cover the new behavior

Description of Change

Summary of Completed Tests

Signed-off-by: Hunsup Jung <hunsup.jung@samsung.com>
@github-actions
Copy link

github-actions bot commented Feb 3, 2026

Duplicate profile check: Warning - duplicate profiles detected.
lock-modular-embedded-unlatch.yml == lock-modular.yml

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

Test Results

   72 files    489 suites   0s ⏱️
2 681 tests 2 680 ✅ 0 💤 1 ❌
4 529 runs  4 528 ✅ 0 💤 1 ❌

For more details on these failures, see this check.

Results for commit 8843cef.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Feb 3, 2026

File Coverage
All files 72%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-lock/src/init.lua 92%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-lock/src/lazy_load_subdriver.lua 57%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-lock/src/lock_utils.lua 98%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-lock/src/new-matter-lock/can_handle.lua 90%
/home/runner/work/SmartThingsEdgeDrivers/SmartThingsEdgeDrivers/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua 64%

Minimum allowed coverage is 90%

Generated by 🐒 cobertura-action against 8843cef

Signed-off-by: Hunsup Jung <hunsup.jung@samsung.com>
@tpmanley
Copy link
Contributor

tpmanley commented Feb 5, 2026

@HunsupJung can you look at the unit test failure and also add new unit tests for the DPS functionality?

[DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError,
[DoorStateEnum.DOOR_AJAR] = doorState.ajar
}
device:emit_event(DOOR_STATE_MAP[ib.data.value]())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DoorState is a nullable attribute so you should check for nil here

local clus_has_feature = function(feature_bitmap)
return DoorLock.are_features_supported(feature_bitmap, ep_cluster.feature_map)
end
if clus_has_feature(DoorLock.types.Feature.DOOR_POSITION_SENSOR) then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that at least some locks will set the DOOR_POSITION_SENSOR feature flag by default even if a door sensor is not configured. They will report NULL for the DoorState attribute until one has been configured. So I think we should check DoorState and the DPS flag and if they are non-NULL and the flag is set then we can say doorState is supported on this lock.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tpmanley
I am sorry for the late reply and thank you for the review.

Do you mean to read DoorLock cluster's AttributeList and set the flag if there is DoorState attribute?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem @HunsupJung . I mean reading the DoorState attribute to confirm it's a non-null value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tpmanley
How about setting to unspecifiedError with logging when DoorState attribute is nil?
I think It's not easy to change profile when device start sending the normal value for DoorState after continuously sending nil value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do something similar to #2828 by setting a field like profiling_data.DOOR_STATE_NON_NULL when the DoorState attribute is reported as a non-null value. When that field changes from not being set to being set, then that will trigger the re-profile.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hcarter-775 @nickolas-deboom
Could you review it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we wouldn't include the capability by default, Hunsup is right that we'd need to do a read, which would probably be fine. However, to avoid the problem of reads failing I think we could just add DoorState as a subscribed attribute in device_init with add_subscribed_attribute, and then if we get a non-null value reported back, then do what Tom suggested within door_state_handler with some logic like this to re-profile the device:

if ib.data.value ~= nil and device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) == nil then
  device:set_field(profiling_data.DOOR_STATE_NON_NULL, true)
  match_profile(driver, device)
end

Then within match_profile, check for the presence of the profiling_data.DOOR_STATE_NON_NULL as the basis for including doorState in the profile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, since it's not a mandatory attribute, maybe create a new separate field rather than putting this field within profiling_data, otherwise locks that don't support DPS would be blocked here.

Copy link
Contributor

@hcarter-775 hcarter-775 Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my recomendation:

Using my PR here as a blueprint,

we should update this table:

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

Then, in device_init:

local function device_init(driver, device)
  device:set_component_to_endpoint_fn(component_to_endpoint)
  if #device:get_endpoints(clusters.DoorLock.ID, {feature_bitmap = clusters.DoorLock.types.Feature.DOOR_POSITION_SENSOR}) == 0 and device:supports_capability(capabilities.doorState) == false then
    device:set_field(profiling_data.ENABLE_DOOR_STATE, false, {persist = true})
  else
    device:add_subscribed_attribute(clusters.DoorLock.attributes.DoorState)
  end
  ... rest of function ...
end

Next, in door_state_handler:

local function door_state_handler(driver, device, ib, response)
  if ib.data.value == nil then
    -- early return on nil data. Also, if ENABLE_DOOR_STATE is unset, set it to false and attempt profile matching.  
    if device:get_field(profiling_data.ENABLE_DOOR_STATE) == nil then
      device:set_field(profiling_data.ENABLE_DOOR_STATE, false)
      match_profile(driver, device)
    end
    return
  elseif device:supports_capability(capabilities.doorState) == false then
    -- if a non-nil report comes in and the doorState capability is unsupported, set ENABLE_DOOR_STATE to true and attempt profile matching.
    device:set_field(profiling_data.ENABLE_DOOR_STATE, true)
    match_profile(driver, device)
    return
  end
  ... rest of function ...
end

Finally, in do_configure, we can remove this section:

if clus_has_feature(DoorLock.types.Feature.DOOR_POSITION_SENSOR) then
  table.insert(main_component_capabilities, capabilities.doorState.ID)
    device.thread:call_with_delay(5, function(t)
    device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory
  end)
end

and we can replace it with the following logic:

  ... rest of code ...
  if device:get_field(profiling_data.DOOR_STATE_ENABLED) or device:supports_capability(capabilities.doorState) then
    table.insert(main_component_capabilities, capabilities.doorState.ID)
  end
  ... rest of code ...

probably over by the BATTERY_SUPPORT logic.

This ensures that whether the DOOR_POSITION_SENSOR flag is set and whether the DoorState is populated, the profiling will occur as expected. It also ensures that if the doorState capability is ever set, it will never be unset. Finally, it ensures that if the DOOR_POSITION_SENSOR is ever enabled during the device's lifetime, we will re-attempt a device profile matching (at least on driver restart).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, to ensure that any re-profiling works as expected, we should generally add gating like this for all modular capability insertions:

if clus_has_feature(DoorLock.types.Feature.USER) or device:supports_capability(capabilities.lockUsers) then
  table.insert(main_component_capabilities, capabilities.lockUsers.ID)
end

to ensure that if an optional capability is already supported, we continue to enable it.

@HunsupJung HunsupJung requested a review from tpmanley March 9, 2026 06:20
Comment on lines +229 to +231
device.thread:call_with_delay(5, function(t)
device:emit_event(capabilities.doorState.supportedDoorStates({"open", "closed"}, {visibility = {displayed = false}})) -- open and closed are mandatory
end)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should handle this in infoChanged, gated behind a profile change, since this has the possibility of failing (if a profile change does not occur in <5 seconds)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the discussion here: #2763

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants