diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml index 3d9e68b44c..253374212b 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular-embedded-unlatch.yml @@ -6,6 +6,9 @@ components: version: 1 - id: lockAlarm version: 1 + - id: doorState + version: 1 + optional: true - id: remoteControlStatus version: 1 - id: lockUsers diff --git a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml index 3a8a53bf70..7a664767dd 100644 --- a/drivers/SmartThings/matter-lock/profiles/lock-modular.yml +++ b/drivers/SmartThings/matter-lock/profiles/lock-modular.yml @@ -4,6 +4,9 @@ components: capabilities: - id: lock version: 1 + - id: doorState + version: 1 + optional: true - id: lockAlarm version: 1 - id: remoteControlStatus diff --git a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua index 11aa2b0884..d3519333d3 100644 --- a/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua +++ b/drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua @@ -64,6 +64,9 @@ local subscribed_attributes = { [capabilities.lock.ID] = { DoorLock.attributes.LockState }, + [capabilities.doorState.ID] = { + DoorLock.attributes.DoorState + }, [capabilities.remoteControlStatus.ID] = { DoorLock.attributes.OperatingMode }, @@ -168,6 +171,12 @@ local function match_profile_modular(driver, device) 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 + 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 if clus_has_feature(DoorLock.types.Feature.USER) then table.insert(main_component_capabilities, capabilities.lockUsers.ID) end @@ -330,6 +339,30 @@ local function lock_state_handler(driver, device, ib, response) end) end +local function door_state_handler(driver, device, ib, response) + if ib.data.value == nil then return end + local DoorStateEnum = DoorLock.types.DoorStateEnum + local doorState = capabilities.doorState.doorState + local DOOR_STATE_MAP = { + [DoorStateEnum.DOOR_OPEN] = doorState.open, + [DoorStateEnum.DOOR_CLOSED] = doorState.closed, + [DoorStateEnum.DOOR_JAMMED] = doorState.jammed, + [DoorStateEnum.DOOR_FORCED_OPEN] = doorState.forcedOpen, + [DoorStateEnum.DOOR_UNSPECIFIED_ERROR] = doorState.unspecifiedError, + [DoorStateEnum.DOOR_AJAR] = doorState.ajar + } + device:emit_event(DOOR_STATE_MAP[ib.data.value]()) + + local supportedDoorStates = device:get_latest_state("main", capabilities.doorState.ID, capabilities.doorState.supportedDoorStates.NAME) or {} + for _, state in pairs(supportedDoorStates) do + if state == DOOR_STATE_MAP[ib.data.value].NAME then + return + end + end + table.insert(supportedDoorStates, DOOR_STATE_MAP[ib.data.value].NAME); + device:emit_event(capabilities.doorState.supportedDoorStates(supportedDoorStates, {visibility = {displayed = false}})) +end + --------------------- -- Operating Modes -- --------------------- @@ -2831,6 +2864,7 @@ local new_matter_lock_handler = { 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, diff --git a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua index 4388c61a4a..4adf9aeadd 100644 --- a/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua +++ b/drivers/SmartThings/matter-lock/src/test/test_new_matter_lock.lua @@ -11,8 +11,20 @@ local DoorLock = clusters.DoorLock local types = DoorLock.types local lock_utils = require "lock_utils" +local enabled_optional_component_capability_pairs = {{ + "main", + { + capabilities.doorState.ID, + capabilities.lockUsers.ID, + capabilities.lockCredentials.ID, + capabilities.lockSchedules.ID + } +}} local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("lock-user-pin-schedule.yml"), + profile = t_utils.get_profile_definition( + "lock-modular.yml", + {enabled_optional_capabilities = enabled_optional_component_capability_pairs} + ), manufacturer_info = { vendor_id = 0x115f, product_id = 0x2802, @@ -34,7 +46,7 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_id = DoorLock.ID, cluster_type = "SERVER", cluster_revision = 1, - feature_map = 0x0181, -- PIN & USR & COTA + feature_map = 0x01A1, -- PIN & DPS & USR & COTA } }, device_types = { @@ -48,6 +60,7 @@ local function test_init() test.disable_startup_messages() -- subscribe request local subscribe_request = DoorLock.attributes.LockState:subscribe(mock_device) + subscribe_request:merge(DoorLock.attributes.DoorState:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.OperatingMode:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfTotalUsersSupported:subscribe(mock_device)) subscribe_request:merge(DoorLock.attributes.NumberOfPINUsersSupported:subscribe(mock_device)) @@ -606,6 +619,126 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Handle received DoorState.DOOR_CLOSED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_CLOSED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"closed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_JAMMED from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_JAMMED + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.jammed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"jammed"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_FORCED_OPEN from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_FORCED_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.forcedOpen()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"forcedOpen"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_UNSPECIFIED_ERROR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_UNSPECIFIED_ERROR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.unspecifiedError()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"unspecifiedError"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_AJAR from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_AJAR + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.ajar()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"ajar"}, {visibility={displayed=false}})) + ) + end +) + +test.register_coroutine_test( + "Handle received DoorState.DOOR_OPEN from Matter device.", + function() + test.socket.matter:__queue_receive( + { + mock_device.id, + DoorLock.attributes.DoorState:build_test_report_data( + mock_device, 1, DoorLock.attributes.DoorState.DOOR_OPEN + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.doorState.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.doorState.supportedDoorStates({"open"}, {visibility={displayed=false}})) + ) + end +) + local function refresh_commands(dev) local req = DoorLock.attributes.LockState:read(dev) return req