Skip to content

Commit

Permalink
Utilize AttributeList in matter-thermo profile selection
Browse files Browse the repository at this point in the history
  • Loading branch information
cjswedes committed Oct 23, 2023
1 parent 5452581 commit 7c876f1
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 27 deletions.
21 changes: 17 additions & 4 deletions drivers/SmartThings/matter-thermostat/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ local function do_configure(driver, device)
local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.ID)
local battery_eps = device:get_endpoints(clusters.PowerSource.ID)
local profile_name = "thermostat"

--Note: we have not encountered thermostats with multiple endpoints that support the Thermostat cluster
if #thermo_eps == 1 then
if #humidity_eps > 0 and #fan_eps > 0 then
Expand All @@ -139,15 +140,15 @@ local function do_configure(driver, device)
profile_name = profile_name .. "-cooling-only"
end

-- TODO remove this in favor of reading Thermostat clusters AttributeList attribute
-- to determine support for ThermostatRunningState
-- Add nobattery profiles if updated
-- Note that Thermostat.AttributeList report will correct this if the optional cluster
-- elements are present
profile_name = profile_name .. "-nostate"

if #battery_eps == 0 then
profile_name = profile_name .. "-nobattery"
end

device:set_field("profile_name", profile_name)
device:send(clusters.Thermostat.attributes.AttributeList:read(device, thermo_eps[1]))
log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name))
device:try_update_metadata({profile = profile_name})
else
Expand Down Expand Up @@ -212,6 +213,17 @@ local function system_mode_handler(driver, device, ib, response)
end
end

local function attr_list_handler(driver, device, ib, response)
for _, attr_id in ipairs (ib.data.elements or {}) do
if attr_id.value == clusters.Thermostat.attributes.ThermostatRunningState.ID then
local new_profile = string.gsub(device:get_field("profile_name"), "-nostate", "")
device.log.info(string.format("Updating device profile to %s.", new_profile))
device:try_update_metadata({ profile = new_profile })
return
end
end
end

local function running_state_handler(driver, device, ib, response)
for mode, operating_state in pairs(THERMOSTAT_OPERATING_MODE_MAP) do
if ((ib.data.value >> mode) & 1) > 0 then
Expand Down Expand Up @@ -415,6 +427,7 @@ local matter_driver_template = {
[clusters.Thermostat.attributes.AbsMinCoolSetpointLimit.ID] = setpoint_limit_handler(setpoint_limit_device_field.MIN_COOL),
[clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit.ID] = setpoint_limit_handler(setpoint_limit_device_field.MAX_COOL),
[clusters.Thermostat.attributes.MinSetpointDeadBand.ID] = min_deadband_limit_handler,
[clusters.Thermostat.attributes.AttributeList.ID] = attr_list_handler
},
[clusters.FanControl.ID] = {
[clusters.FanControl.attributes.FanModeSequence.ID] = fan_mode_sequence_handler,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

local test = require "integration_test"
local t_utils = require "integration_test.utils"

local Uint32 = require "st.matter.data_types".Uint32
local clusters = require "st.matter.clusters"

local mock_device = test.mock_device.build_test_matter_device({
Expand Down Expand Up @@ -179,43 +179,78 @@ local function test_init()
end
test.set_test_init_function(test_init)

local function configure(device, is_heat)
test.socket.device_lifecycle:__queue_receive({ device.id, "doConfigure" })
local read_limits
if is_heat then
read_limits = clusters.Thermostat.attributes.AbsMinHeatSetpointLimit:read()
read_limits:merge(clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit:read())
else
read_limits = clusters.Thermostat.attributes.AbsMinCoolSetpointLimit:read()
read_limits:merge(clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit:read())
end
test.socket.matter:__expect_send({device.id, read_limits})
test.socket.matter:__expect_send({
device.id,
clusters.Thermostat.attributes.AttributeList:read(device, 1)
})
end

test.register_coroutine_test(
"Profile change on doConfigure lifecycle event due to cluster feature map",
"Profile change on doConfigure lifecycle event due to cluster heating feature map",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
local read_limits = clusters.Thermostat.attributes.AbsMinHeatSetpointLimit:read()
read_limits:merge(clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit:read())
test.socket.matter:__expect_send({mock_device.id, read_limits})
--TODO why does provisiong state get added in the do configure event handle, but not the refres?
configure(mock_device, true)
mock_device:expect_metadata_update({ profile = "thermostat-humidity-fan-heating-only-nostate" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device_simple.id,
clusters.Thermostat.attributes.AttributeList:build_test_report_data(mock_device_simple, 1, {Uint32(0x2)}),
})
end
)

test.register_coroutine_test(
"Profile change on doConfigure lifecycle event due to cluster feature map",
"Profile change on doConfigure lifecycle event due to cluster cooling feature map",
function()
local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device)
for i, cluster in ipairs(cluster_subscribe_list) do
if i > 1 then
subscribe_request:merge(cluster:subscribe(mock_device))
end
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})

test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({}))
configure(mock_device_simple, false)
mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" })
mock_device_simple:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device_simple.id,
clusters.Thermostat.attributes.AttributeList:build_test_report_data(mock_device_simple, 1, {Uint32(0x1)}),
})
end
)

test.register_coroutine_test(
"Profile change on doConfigure lifecycle event due to cluster feature map",
"Profile change due to Thermostat attribute list",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_simple.id, "doConfigure" })
local read_limits = clusters.Thermostat.attributes.AbsMinCoolSetpointLimit:read()
read_limits:merge(clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit:read())
test.socket.matter:__expect_send({mock_device_simple.id, read_limits})
configure(mock_device_simple, false)
mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only-nostate" })
mock_device_simple:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device_simple.id,
clusters.Thermostat.attributes.AttributeList:build_test_report_data(mock_device_simple, 1, {Uint32(0x29)}),
})
mock_device_simple:expect_metadata_update({ profile = "thermostat-cooling-only" })
end
)

test.register_coroutine_test(
"Profile change due to Thermostat attribute list no battery",
function()
configure(mock_device_no_battery, false)
mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-nobattery" })
mock_device_no_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({
mock_device_no_battery.id,
clusters.Thermostat.attributes.AttributeList:build_test_report_data(mock_device_no_battery, 1, {Uint32(0x29)}),
})
mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nobattery" })
end
)

Expand All @@ -224,6 +259,10 @@ test.register_coroutine_test(
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_no_battery.id, "doConfigure" })
local read_limits = clusters.Thermostat.attributes.AbsMinCoolSetpointLimit:read()
test.socket.matter:__expect_send({
mock_device_no_battery.id,
clusters.Thermostat.attributes.AttributeList:read(mock_device_no_battery, 1)
})
read_limits:merge(clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit:read())
test.socket.matter:__expect_send({mock_device_no_battery.id, read_limits})
mock_device_no_battery:expect_metadata_update({ profile = "thermostat-cooling-only-nostate-nobattery" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,19 @@ local cached_heating_setpoint = capabilities.thermostatHeatingSetpoint.heatingSe
local cached_cooling_setpoint = capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 26.67, unit = "C" })

local function configure(device)
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
test.socket.device_lifecycle:__queue_receive({ device.id, "doConfigure" })
local read_limits = clusters.Thermostat.attributes.AbsMinHeatSetpointLimit:read()
read_limits:merge(clusters.Thermostat.attributes.AbsMaxHeatSetpointLimit:read())
read_limits:merge(clusters.Thermostat.attributes.AbsMinCoolSetpointLimit:read())
read_limits:merge(clusters.Thermostat.attributes.AbsMaxCoolSetpointLimit:read())
read_limits:merge(clusters.Thermostat.attributes.MinSetpointDeadBand:read())
test.socket.matter:__expect_send({device.id, read_limits})
test.socket.matter:__expect_send({
device.id,
clusters.Thermostat.attributes.AttributeList:read(device, 1)
})

--Note nostate profile updates only happen when we receive and process an attribute list report
mock_device:expect_metadata_update({ profile = "thermostat-nostate" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
Expand Down Expand Up @@ -122,6 +128,7 @@ local function configure(device)
test.socket.capability:__expect_send(
device:generate_test_message("main", cached_cooling_setpoint)
)

test.wait_for_events()
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
-- limitations under the License.

local test = require "integration_test"

local capabilities = require "st.capabilities"
local t_utils = require "integration_test.utils"
local utils = require "st.utils"
Expand Down

0 comments on commit 7c876f1

Please sign in to comment.