Skip to content

Commit fc79578

Browse files
authored
Merge branch 'main' into abb/scu200_insite_introduction
2 parents 08c1616 + c707fe7 commit fc79578

35 files changed

+1990
-374
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: lock-modular-embedded-unlatch
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: lock
6+
version: 1
7+
- id: lockAlarm
8+
version: 1
9+
- id: remoteControlStatus
10+
version: 1
11+
- id: lockUsers
12+
version: 1
13+
optional: true
14+
- id: lockCredentials
15+
version: 1
16+
optional: true
17+
- id: lockSchedules
18+
version: 1
19+
optional: true
20+
- id: battery
21+
version: 1
22+
optional: true
23+
- id: batteryLevel
24+
version: 1
25+
optional: true
26+
- id: firmwareUpdate
27+
version: 1
28+
- id: refresh
29+
version: 1
30+
categories:
31+
- name: SmartLock
32+
deviceConfig:
33+
dashboard:
34+
states:
35+
- component: main
36+
capability: lock
37+
version: 1
38+
actions:
39+
- component: main
40+
capability: lock
41+
version: 1
42+
visibleCondition: {
43+
"capability": "lock",
44+
"version": "1",
45+
"component": "main",
46+
"value": "lock.value",
47+
"operator": "DOES_NOT_EQUAL",
48+
"operand": "unlatched"
49+
}
50+
detailView:
51+
- component: main
52+
capability: lock
53+
version: 1
54+
values:
55+
- key: lock.value
56+
alternatives:
57+
- key: locked
58+
type: inactive
59+
value: '{{i18n.attributes.lock.i18n.value.locked.label}}'
60+
- key: unlocked
61+
value: '{{i18n.attributes.lock.i18n.value.unlocked.label}}'
62+
- key: unlatched
63+
value: '{{i18n.attributes.lock.i18n.value.unlatched.label}}'
64+
- key: not fully locked
65+
value: '{{i18n.attributes.lock.i18n.value.not fully locked.label}}'
66+
patch:
67+
- op: add
68+
path: /1
69+
value:
70+
capability: lock
71+
version: 1
72+
component: main
73+
label: '{{i18n.commands.unlatch.label}}'
74+
displayType: pushButton
75+
pushButton:
76+
command: unlatch
77+
- component: main
78+
capability: lockAlarm
79+
version: 1
80+
- component: main
81+
capability: remoteControlStatus
82+
version: 1
83+
- component: main
84+
capability: battery
85+
version: 1
86+
- component: main
87+
capability: batteryLevel
88+
version: 1
89+
automation:
90+
conditions:
91+
- component: main
92+
capability: lock
93+
version: 1
94+
values:
95+
- key: lock.value
96+
alternatives:
97+
- key: locked
98+
type: inactive
99+
value: '{{i18n.attributes.lock.i18n.value.locked.label}}'
100+
- key: unlocked
101+
value: '{{i18n.attributes.lock.i18n.value.unlocked.label}}'
102+
- key: unlatched
103+
value: '{{i18n.attributes.lock.i18n.value.unlatched.label}}'
104+
- key: not fully locked
105+
value: '{{i18n.attributes.lock.i18n.value.not fully locked.label}}'
106+
- component: main
107+
capability: lockAlarm
108+
version: 1
109+
- component: main
110+
capability: remoteControlStatus
111+
version: 1
112+
- component: main
113+
capability: battery
114+
version: 1
115+
- component: main
116+
capability: batteryLevel
117+
version: 1
118+
actions:
119+
- component: main
120+
capability: lock
121+
version: 1
122+
values:
123+
- key: '{{enumCommands}}'
124+
alternatives:
125+
- key: lock
126+
type: inactive
127+
value: '{{i18n.commands.lock.label}}'
128+
- key: unlock
129+
value: '{{i18n.commands.unlock.label}}'
130+
- key: unlatch
131+
value: '{{i18n.commands.unlatch.label}}'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: lock-modular
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: lock
6+
version: 1
7+
- id: lockAlarm
8+
version: 1
9+
- id: remoteControlStatus
10+
version: 1
11+
- id: lockUsers
12+
version: 1
13+
optional: true
14+
- id: lockCredentials
15+
version: 1
16+
optional: true
17+
- id: lockSchedules
18+
version: 1
19+
optional: true
20+
- id: battery
21+
version: 1
22+
optional: true
23+
- id: batteryLevel
24+
version: 1
25+
optional: true
26+
- id: firmwareUpdate
27+
version: 1
28+
- id: refresh
29+
version: 1
30+
categories:
31+
- name: SmartLock

drivers/SmartThings/matter-lock/src/new-matter-lock/init.lua

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ local NEW_MATTER_LOCK_PRODUCTS = {
3838
{0x115f, 0x2801}, -- AQARA, U300
3939
{0x115f, 0x2807}, -- AQARA, U200 Lite
4040
{0x147F, 0x0001}, -- U-tec
41+
{0x147F, 0x0008}, -- Ultraloq, Bolt Smart Matter Door Lock
4142
{0x144F, 0x4002}, -- Yale, Linus Smart Lock L2
4243
{0x101D, 0x8110}, -- Yale, New Lock
4344
{0x1533, 0x0001}, -- eufy, E31
@@ -55,7 +56,15 @@ local NEW_MATTER_LOCK_PRODUCTS = {
5556
{0x10E1, 0x2002} -- VDA
5657
}
5758

58-
local PROFILE_BASE_NAME = "__profile_base_name"
59+
local battery_support = {
60+
NO_BATTERY = "NO_BATTERY",
61+
BATTERY_LEVEL = "BATTERY_LEVEL",
62+
BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE"
63+
}
64+
65+
local profiling_data = {
66+
BATTERY_SUPPORT = "__BATTERY_SUPPORT",
67+
}
5968

6069
local subscribed_attributes = {
6170
[capabilities.lock.ID] = {
@@ -148,15 +157,64 @@ local function device_init(driver, device)
148157

149158
local function device_added(driver, device)
150159
device:emit_event(capabilities.lockAlarm.alarm.clear({state_change = true}))
160+
local battery_feature_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY})
161+
if #battery_feature_eps > 0 then
162+
device:send(clusters.PowerSource.attributes.AttributeList:read(device))
163+
else
164+
device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true })
165+
end
151166
end
152167

153-
local function do_configure(driver, device)
168+
local function match_profile_modular(driver, device)
169+
local enabled_optional_component_capability_pairs = {}
170+
local main_component_capabilities = {}
171+
local modular_profile_name = "lock-modular"
172+
for _, device_ep in pairs(device.endpoints) do
173+
for _, ep_cluster in pairs(device_ep.clusters) do
174+
if ep_cluster.cluster_id == DoorLock.ID then
175+
local clus_has_feature = function(feature_bitmap)
176+
return DoorLock.are_features_supported(feature_bitmap, ep_cluster.feature_map)
177+
end
178+
if clus_has_feature(DoorLock.types.Feature.USER) then
179+
table.insert(main_component_capabilities, capabilities.lockUsers.ID)
180+
end
181+
if clus_has_feature(DoorLock.types.Feature.PIN_CREDENTIAL) then
182+
table.insert(main_component_capabilities, capabilities.lockCredentials.ID)
183+
end
184+
if clus_has_feature(DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES) or
185+
clus_has_feature(DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES) then
186+
table.insert(main_component_capabilities, capabilities.lockSchedules.ID)
187+
end
188+
if clus_has_feature(DoorLock.types.Feature.UNBOLT) then
189+
device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "unlatched", "not fully locked"}, {visibility = {displayed = false}}))
190+
device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock", "unlatch"}, {visibility = {displayed = false}}))
191+
modular_profile_name = "lock-modular-embedded-unlatch" -- use the embedded config specified in this profile for devices supporting "unlatch"
192+
else
193+
device:emit_event(capabilities.lock.supportedLockValues({"locked", "unlocked", "not fully locked"}, {visibility = {displayed = false}}))
194+
device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}}))
195+
end
196+
break
197+
end
198+
end
199+
end
200+
201+
local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT)
202+
if supported_battery_type == battery_support.BATTERY_LEVEL then
203+
table.insert(main_component_capabilities, capabilities.batteryLevel.ID)
204+
elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then
205+
table.insert(main_component_capabilities, capabilities.battery.ID)
206+
end
207+
208+
table.insert(enabled_optional_component_capability_pairs, {"main", main_component_capabilities})
209+
device:try_update_metadata({profile = modular_profile_name, optional_component_capabilities = enabled_optional_component_capability_pairs})
210+
end
211+
212+
local function match_profile_switch(driver, device)
154213
local user_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.USER})
155214
local pin_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.PIN_CREDENTIAL})
156215
local week_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.WEEK_DAY_ACCESS_SCHEDULES})
157216
local year_schedule_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.YEAR_DAY_ACCESS_SCHEDULES})
158217
local unbolt_eps = device:get_endpoints(DoorLock.ID, {feature_bitmap = DoorLock.types.Feature.UNBOLT})
159-
local battery_eps = device:get_endpoints(PowerSource.ID, {feature_bitmap = PowerSource.types.PowerSourceFeature.BATTERY})
160218

161219
local profile_name = "lock"
162220
if #user_eps > 0 then
@@ -174,15 +232,16 @@ local function do_configure(driver, device)
174232
else
175233
device:emit_event(capabilities.lock.supportedLockCommands({"lock", "unlock"}, {visibility = {displayed = false}}))
176234
end
177-
if #battery_eps > 0 then
178-
device:set_field(PROFILE_BASE_NAME, profile_name, {persist = true})
179-
local req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {})
180-
req:merge(clusters.PowerSource.attributes.AttributeList:read())
181-
device:send(req)
182-
else
183-
device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name))
184-
device:try_update_metadata({profile = profile_name})
235+
236+
local supported_battery_type = device:get_field(profiling_data.BATTERY_SUPPORT)
237+
if supported_battery_type == battery_support.BATTERY_LEVEL then
238+
profile_name = profile_name .. "-batteryLevel"
239+
elseif supported_battery_type == battery_support.BATTERY_PERCENTAGE then
240+
profile_name = profile_name .. "-battery"
185241
end
242+
243+
device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name))
244+
device:try_update_metadata({profile = profile_name})
186245
end
187246

188247
local function info_changed(driver, device, event, args)
@@ -208,6 +267,33 @@ local function info_changed(driver, device, event, args)
208267
device:emit_event(capabilities.lockAlarm.supportedAlarmValues({"unableToLockTheDoor"}, {visibility = {displayed = false}})) -- lockJammed is madatory
209268
end
210269

270+
local function profiling_data_still_required(device)
271+
for _, field in pairs(profiling_data) do
272+
if device:get_field(field) == nil then
273+
return true -- data still required if a field is nil
274+
end
275+
end
276+
return false
277+
end
278+
279+
local function match_profile(driver, device)
280+
if profiling_data_still_required(device) then return end
281+
282+
if version.api >= 15 and version.rpc >= 9 then
283+
match_profile_modular(driver, device)
284+
else
285+
match_profile_switch(driver, device)
286+
end
287+
end
288+
289+
local function do_configure(driver, device)
290+
match_profile(driver, device)
291+
end
292+
293+
local function driver_switched(driver, device)
294+
match_profile(driver, device)
295+
end
296+
211297
-- This function check busy_state and if busy_state is false, set it to true(current time)
212298
local function check_busy_state(device)
213299
local c_time = os.time()
@@ -397,27 +483,23 @@ end
397483
-- Power Source Attribute List --
398484
---------------------------------
399485
local function handle_power_source_attribute_list(driver, device, ib, response)
400-
local support_battery_percentage = false
401-
local support_battery_level = false
402486
for _, attr in ipairs(ib.data.elements) do
403-
-- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) is present.
487+
-- mark if the device if BatPercentRemaining (Attribute ID 0x0C) or
488+
-- BatChargeLevel (Attribute ID 0x0E) is present and try profiling.
404489
if attr.value == 0x0C then
405-
support_battery_percentage = true
406-
end
407-
if attr.value == 0x0E then
408-
support_battery_level = true
409-
end
410-
end
411-
local profile_name = device:get_field(PROFILE_BASE_NAME)
412-
if profile_name ~= nil then
413-
if support_battery_percentage then
414-
profile_name = profile_name .. "-battery"
415-
elseif support_battery_level then
416-
profile_name = profile_name .. "-batteryLevel"
490+
device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_PERCENTAGE, { persist = true })
491+
match_profile(driver, device)
492+
return
493+
elseif attr.value == 0x0E then
494+
device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.BATTERY_LEVEL, { persist = true })
495+
match_profile(driver, device)
496+
return
417497
end
418-
device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name))
419-
device:try_update_metadata({profile = profile_name})
420498
end
499+
500+
-- neither BatChargeLevel nor BatPercentRemaining were found. Re-profiling without battery.
501+
device:set_field(profiling_data.BATTERY_SUPPORT, battery_support.NO_BATTERY, { persist = true })
502+
match_profile(driver, device)
421503
end
422504

423505
-------------------------------
@@ -2085,6 +2167,7 @@ local new_matter_lock_handler = {
20852167
added = device_added,
20862168
doConfigure = do_configure,
20872169
infoChanged = info_changed,
2170+
driverSwitched = driver_switched
20882171
},
20892172
matter_handlers = {
20902173
attr = {

0 commit comments

Comments
 (0)