Skip to content

Commit 5a8dcc3

Browse files
Matter Switch: Improve battery profiling
This implements a few changes to the way that devices supporting batteries are profiled: * subscribe to PowerSource.AttributeList rather than reading this attribute, to help prevent failed reads from causing issues with device profiling * update the profile within match_profile rather than power_source_attribute_list_handler * use existing structure for handling waiting for profiling data before attempting a profile update
1 parent 2192441 commit 5a8dcc3

File tree

8 files changed

+689
-719
lines changed

8 files changed

+689
-719
lines changed

drivers/SmartThings/matter-switch/src/init.lua

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ function SwitchLifecycleHandlers.device_added(driver, device)
4141
switch_utils.handle_electrical_sensor_info(device)
4242
end
4343

44+
-- For devices supporting BATTERY, add the PowerSource AttributeList to the list of subscribed
45+
-- attributes in order to determine whether to use the battery or batteryLevel capability. Note
46+
-- that this is only needed one time, since after the profile is updated the subscription will
47+
-- be added if a battery capability is present.
48+
if #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0 then
49+
device:add_subscribed_attribute(clusters.PowerSource.attributes.AttributeList)
50+
else
51+
device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.NO_BATTERY, {persist = true})
52+
end
53+
4454
-- call device init in case init is not called after added due to device caching
4555
SwitchLifecycleHandlers.device_init(driver, device)
4656
end
@@ -94,7 +104,7 @@ function SwitchLifecycleHandlers.device_init(driver, device)
94104
device:extend_device("subscribe", switch_utils.subscribe)
95105
device:subscribe()
96106

97-
-- device energy reporting must be handled cumulatively, periodically, or by both simulatanously.
107+
-- device energy reporting must be handled cumulatively, periodically, or by both simultaneously.
98108
-- To ensure a single source of truth, we only handle a device's periodic reporting if cumulative reporting is not supported.
99109
if #embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID,
100110
{feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY}) > 0 then
@@ -194,9 +204,11 @@ local matter_driver_template = {
194204
},
195205
subscribed_attributes = {
196206
[capabilities.battery.ID] = {
207+
clusters.PowerSource.attributes.AttributeList,
197208
clusters.PowerSource.attributes.BatPercentRemaining,
198209
},
199210
[capabilities.batteryLevel.ID] = {
211+
clusters.PowerSource.attributes.AttributeList,
200212
clusters.PowerSource.attributes.BatChargeLevel,
201213
},
202214
[capabilities.colorControl.ID] = {

drivers/SmartThings/matter-switch/src/switch_handlers/attribute_handlers.lua

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,10 @@ function AttributeHandlers.available_endpoints_handler(driver, device, ib, respo
319319
local set_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS)
320320
for i, set_ep_info in pairs(set_topology_eps or {}) do
321321
if ib.endpoint_id == set_ep_info.endpoint_id then
322-
-- since EP reponse is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table
322+
-- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table
323323
switch_utils.remove_field_index(device, fields.ELECTRICAL_SENSOR_EPS, i)
324324
local available_endpoints_ids = {}
325-
for _, element in pairs(ib.data.elements) do
325+
for _, element in pairs(ib.data.elements or {}) do
326326
table.insert(available_endpoints_ids, element.value)
327327
end
328328
-- set the required profile elements ("-power", etc.) to one of these EP IDs for later profiling.
@@ -344,10 +344,10 @@ function AttributeHandlers.parts_list_handler(driver, device, ib, response)
344344
local tree_topology_eps = device:get_field(fields.ELECTRICAL_SENSOR_EPS)
345345
for i, tree_ep_info in pairs(tree_topology_eps or {}) do
346346
if ib.endpoint_id == tree_ep_info.endpoint_id then
347-
-- since EP reponse is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table
347+
-- since EP response is being handled here, remove it from the ELECTRICAL_SENSOR_EPS table
348348
switch_utils.remove_field_index(device, fields.ELECTRICAL_SENSOR_EPS, i)
349349
local associated_endpoints_ids = {}
350-
for _, element in pairs(ib.data.elements) do
350+
for _, element in pairs(ib.data.elements or {}) do
351351
table.insert(associated_endpoints_ids, element.value)
352352
end
353353
-- set the required profile elements ("-power", etc.) to one of these EP IDs for later profiling.
@@ -382,30 +382,32 @@ function AttributeHandlers.bat_charge_level_handler(driver, device, ib, response
382382
end
383383

384384
function AttributeHandlers.power_source_attribute_list_handler(driver, device, ib, response)
385-
local profile_name = ""
386-
387-
local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
385+
local previous_battery_support = device:get_field(fields.profiling_data.BATTERY_SUPPORT) or fields.battery_support.NO_BATTERY
388386
for _, attr in ipairs(ib.data.elements) do
389-
-- Re-profile the device if BatPercentRemaining (Attribute ID 0x0C) or
390-
-- BatChargeLevel (Attribute ID 0x0E) is present.
391-
if attr.value == 0x0C then
392-
profile_name = "button-battery"
393-
break
394-
elseif attr.value == 0x0E then
395-
profile_name = "button-batteryLevel"
387+
if attr.value == clusters.PowerSource.attributes.BatPercentRemaining.ID then
388+
device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_PERCENTAGE, {persist=true})
396389
break
390+
elseif attr.value == clusters.PowerSource.attributes.BatChargeLevel.ID then
391+
local battery_support = device:get_field(fields.profiling_data.BATTERY_SUPPORT) or fields.battery_support.NO_BATTERY
392+
if battery_support ~= fields.battery_support.BATTERY_PERCENTAGE then -- don't overwrite if percentage support is already detected
393+
device:set_field(fields.profiling_data.BATTERY_SUPPORT, fields.battery_support.BATTERY_LEVEL, {persist=true})
394+
end
397395
end
398396
end
399-
if profile_name ~= "" then
400-
if #button_eps > 1 then
401-
profile_name = string.format("%d-", #button_eps) .. profile_name
402-
end
403-
404-
if switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then
405-
profile_name = profile_name .. "-temperature-humidity"
406-
end
407-
device:try_update_metadata({ profile = profile_name })
397+
local momentary_switch_ep_ids = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
398+
if #momentary_switch_ep_ids > 0 and switch_utils.get_product_override_field(device, "is_climate_sensor_w100") then
399+
local default_endpoint_id = switch_utils.find_default_endpoint(device)
400+
local button_cfg = require("switch_utils.device_configuration").ButtonCfg
401+
button_cfg.update_button_component_map(device, default_endpoint_id, momentary_switch_ep_ids)
402+
button_cfg.configure_buttons(device, momentary_switch_ep_ids)
403+
device:try_update_metadata({ profile = "3-button-battery-temperature-humidity" }, true)
404+
return
405+
end
406+
device:set_field(fields.profiling_data.POWER_TOPOLOGY, clusters.PowerTopology.types.Feature.SET_TOPOLOGY, {persist=true})
407+
if (device:get_field(fields.profiling_data.BATTERY_SUPPORT) or fields.battery_support.NO_BATTERY) == previous_battery_support then
408+
return
408409
end
410+
device_cfg.match_profile(driver, device)
409411
end
410412

411413

drivers/SmartThings/matter-switch/src/switch_utils/device_configuration.lua

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,13 @@ function ButtonDeviceConfiguration.update_button_profile(device, default_endpoin
123123
if #motion_eps > 0 and (num_button_eps == 3 or num_button_eps == 6) then -- only these two devices are handled
124124
profile_name = profile_name .. "-motion"
125125
end
126-
local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0
127-
if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler
128-
device:send(clusters.PowerSource.attributes.AttributeList:read(device))
129-
else
130-
device:try_update_metadata({profile = profile_name})
126+
local battery_support = device:get_field(fields.profiling_data.BATTERY_SUPPORT) or fields.battery_support.NO_BATTERY
127+
if battery_support == fields.battery_support.BATTERY_PERCENTAGE then
128+
profile_name = profile_name .. "-battery"
129+
elseif battery_support == fields.battery_support.BATTERY_LEVEL then
130+
profile_name = profile_name .. "-batteryLevel"
131131
end
132+
device:try_update_metadata({profile = profile_name})
132133
end
133134

134135
function ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, button_eps)
@@ -238,12 +239,12 @@ function DeviceConfiguration.match_profile(driver, device)
238239
end
239240

240241
-- initialize the main device card with buttons if applicable
241-
local momemtary_switch_ep_ids = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
242-
if switch_utils.tbl_contains(fields.STATIC_BUTTON_PROFILE_SUPPORTED, #momemtary_switch_ep_ids) then
243-
ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, #momemtary_switch_ep_ids)
242+
local momentary_switch_ep_ids = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.SwitchFeature.MOMENTARY_SWITCH})
243+
if switch_utils.tbl_contains(fields.STATIC_BUTTON_PROFILE_SUPPORTED, #momentary_switch_ep_ids) then
244+
ButtonDeviceConfiguration.update_button_profile(device, default_endpoint_id, #momentary_switch_ep_ids)
244245
-- All button endpoints found will be added as additional components in the profile containing the default_endpoint_id.
245-
ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, momemtary_switch_ep_ids)
246-
ButtonDeviceConfiguration.configure_buttons(device, momemtary_switch_ep_ids)
246+
ButtonDeviceConfiguration.update_button_component_map(device, default_endpoint_id, momentary_switch_ep_ids)
247+
ButtonDeviceConfiguration.configure_buttons(device, momentary_switch_ep_ids)
247248
return
248249
end
249250

drivers/SmartThings/matter-switch/src/switch_utils/fields.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,20 @@ SwitchFields.switch_category_vendor_overrides = {
135135
SwitchFields.ELECTRICAL_SENSOR_EPS = "__electrical_sensor_eps"
136136

137137
--- used in tandem with an EP ID. Stores the required electrical tags "-power", "-energy-powerConsumption", etc.
138-
--- for an Electrical Sensor EP with a "primary" endpoint, used during device profling.
138+
--- for an Electrical Sensor EP with a "primary" endpoint, used during device profiling.
139139
SwitchFields.ELECTRICAL_TAGS = "__electrical_tags"
140140

141141
SwitchFields.MODULAR_PROFILE_UPDATED = "__modular_profile_updated"
142142

143143
SwitchFields.profiling_data = {
144144
POWER_TOPOLOGY = "__power_topology",
145+
BATTERY_SUPPORT = "__battery_support",
146+
}
147+
148+
SwitchFields.battery_support = {
149+
NO_BATTERY = "NO_BATTERY",
150+
BATTERY_LEVEL = "BATTERY_LEVEL",
151+
BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE",
145152
}
146153

147154
SwitchFields.ENERGY_METER_OFFSET = "__energy_meter_offset"

drivers/SmartThings/matter-switch/src/test/test_aqara_climate_sensor_w100.lua

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ local function test_init()
113113
test.disable_startup_messages()
114114
test.mock_device.add_test_device(aqara_mock_device)
115115
local cluster_subscribe_list = {
116+
clusters.PowerSource.server.attributes.AttributeList,
116117
clusters.PowerSource.server.attributes.BatPercentRemaining,
117118
clusters.TemperatureMeasurement.attributes.MeasuredValue,
118119
clusters.TemperatureMeasurement.attributes.MinMeasuredValue,
@@ -138,23 +139,16 @@ local function test_init()
138139
test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request})
139140

140141
test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "doConfigure" })
141-
local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read()
142-
test.socket.matter:__expect_send({aqara_mock_device.id, read_attribute_list})
143-
configure_buttons()
144142
aqara_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
145-
146-
local device_info_copy = utils.deep_copy(aqara_mock_device.raw_st_data)
147-
device_info_copy.profile.id = "3-button-battery-temperature-humidity"
148-
local device_info_json = dkjson.encode(device_info_copy)
149-
test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "infoChanged", device_info_json })
150-
test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request})
151-
configure_buttons()
152143
end
153144

154145
test.set_test_init_function(test_init)
155146

156147
local function update_profile()
157-
test.socket.matter:__queue_receive({aqara_mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data(aqara_mock_device, 6, {uint32(0x0C)})})
148+
test.socket.matter:__queue_receive({aqara_mock_device.id, clusters.PowerSource.attributes.AttributeList:build_test_report_data(
149+
aqara_mock_device, 6, {uint32(clusters.PowerSource.attributes.BatPercentRemaining.ID)}
150+
)})
151+
configure_buttons()
158152
aqara_mock_device:expect_metadata_update({ profile = "3-button-battery-temperature-humidity" })
159153
end
160154

@@ -466,4 +460,3 @@ test.register_coroutine_test(
466460
)
467461

468462
test.run_registered_tests()
469-

drivers/SmartThings/matter-switch/src/test/test_aqara_cube.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ local mock_device_exhausted = test.mock_device.build_test_matter_device(
141141

142142
-- add device for each mock device
143143
local CLUSTER_SUBSCRIBE_LIST ={
144+
clusters.PowerSource.server.attributes.AttributeList,
144145
clusters.PowerSource.server.attributes.BatPercentRemaining,
145146
clusters.Switch.server.events.InitialPress,
146147
clusters.Switch.server.events.LongPress,

0 commit comments

Comments
 (0)