@@ -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
6069local subscribed_attributes = {
6170 [capabilities .lock .ID ] = {
@@ -148,15 +157,64 @@ local function device_init(driver, device)
148157
149158local 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
151166end
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 })
186245end
187246
188247local 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
209268end
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)
212298local function check_busy_state (device )
213299 local c_time = os.time ()
@@ -397,27 +483,23 @@ end
397483-- Power Source Attribute List --
398484---- -----------------------------
399485local 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 )
421503end
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