Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/matter remove cluster element list usage #370

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 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,18 @@ 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:set_field("profile_name", new_profile)
device.log.info(string.format("Updating device profile to %s.", new_profile))
device:try_update_metadata({ profile = new_profile })
cjswedes marked this conversation as resolved.
Show resolved Hide resolved
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 +428,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what triggers a read here? the subscription?

},
[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