Skip to content

Commit

Permalink
Remove usages of cluster element data from drivers
Browse files Browse the repository at this point in the history
  • Loading branch information
cjswedes committed Dec 20, 2022
1 parent bf8a759 commit 3df0388
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ local mock_device = test.mock_device.build_test_matter_device({
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
attributes = nil, -- attribute id list
server_commands = nil, --server cmd id list
client_commands = nil, --client cmd id list
events = nil, --event id list
},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ local mock_device = test.mock_device.build_test_matter_device({
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
attributes = nil, -- attribute id list
server_commands = nil, --server cmd id list
client_commands = nil, --client cmd id list
events = nil, --event id list
},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"},
{cluster_id = clusters.MediaPlayback.ID, cluster_type = "SERVER", feature_map = 0x0},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ local mock_device = test.mock_device.build_test_matter_device({
cluster_type = "SERVER",
cluster_revision = 1,
feature_map = 0, --u32 bitmap
attributes = nil, -- attribute id list
server_commands = nil, --server cmd id list
client_commands = nil, --client cmd id list
events = nil, --event id list
},
{cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 31},
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"}
Expand Down
119 changes: 119 additions & 0 deletions drivers/SmartThings/matter-thermostat/src/AttributeList.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
-- Copyright 2022 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator.


--Note this file is present in the driver package in case it is not available in the lua libs on
-- the hub. It will be required for any API version <= 2. It will be removed by SmartThings once
-- all hub platforms running the driver support an API version greater than 2
local cluster_base = require "st.matter.cluster_base"
local data_types = require "st.matter.data_types"
local TLVParser = require "st.matter.TLV.TLVParser"

--- @class st.matter.clusters.Thermostat.AttributeList
--- @alias AttributeList
---
--- @field public ID number 0xFFFB the ID of this attribute
--- @field public NAME string "AttributeList" the name of this attribute
--- @field public data_type st.matter.data_types.Array the data type of this attribute

local AttributeList = {
ID = 0xFFFB,
NAME = "AttributeList",
base_type = data_types.Array,
}
--- Create a Array object of this attribute with any additional features provided for the attribute
--- This is also usable with the AttributeList(...) syntax
---
--- @vararg vararg the values needed to construct a Array
--- @return st.matter.data_types.Array
function AttributeList:new_value(...)
local o = self.base_type(table.unpack({...}))

return o
end

--- Constructs an st.matter.interaction_model.InteractionRequest to read
--- this attribute from a device
--- @param device st.matter.Device
--- @param endpoint_id number|nil
--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request
function AttributeList:read(device, endpoint_id)
return cluster_base.read(
device,
endpoint_id,
self._cluster.ID,
self.ID,
nil --event_id
)
end


--- Reporting policy: AttributeList => true => mandatory

--- Sets up a Subscribe Interaction
---
--- @param device any
--- @param endpoint_id number|nil
--- @return any
function AttributeList:subscribe(device, endpoint_id)
return cluster_base.subscribe(
device,
endpoint_id,
self._cluster.ID,
self.ID,
nil --event_id
)
end

function AttributeList:set_parent_cluster(cluster)
self._cluster = cluster
return self
end

--- Builds an AttributeList test attribute reponse for the driver integration testing framework
---
--- @param device st.matter.Device the device to build this message for
--- @param endpoint_id number|nil
--- @param value any
--- @param status string Interaction status associated with the path
--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA
function AttributeList:build_test_report_data(
device,
endpoint_id,
value,
status
)
local data = data_types.validate_or_build_type(value, self.base_type)

return cluster_base.build_test_report_data(
device,
endpoint_id,
self._cluster.ID,
self.ID,
data,
status
)
end

function AttributeList:deserialize(tlv_buf)
local data = TLVParser.decode_tlv(tlv_buf)

return data
end

setmetatable(AttributeList, {__call = AttributeList.new_value})
return AttributeList

30 changes: 27 additions & 3 deletions drivers/SmartThings/matter-thermostat/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@ local log = require "log"
local clusters = require "st.matter.clusters"
local im = require "st.matter.interaction_model"

local version = require "version"
local MatterDriver = require "st.matter.driver"
local utils = require "st.utils"

--TODO should we remove this or not? With or without it is blocked on hub FW 0.45
local ThermostatClusterAttributeList
if version.api < 3 then
ThermostatClusterAttributeList = require "AttributeList"
ThermostatClusterAttributeList:set_parent_cluster(clusters.Thermostat)
else
ThermostatClusterAttributeList = clusters.Thermostat.attributes.AttributeList
end


local THERMOSTAT_MODE_MAP = {
[clusters.Thermostat.types.ThermostatSystemMode.OFF] = capabilities.thermostatMode.thermostatMode.off,
[clusters.Thermostat.types.ThermostatSystemMode.AUTO] = capabilities.thermostatMode.thermostatMode.auto,
Expand Down Expand Up @@ -55,6 +66,7 @@ local function do_configure(driver, device)
local fan_eps = device:get_endpoints(clusters.FanControl.ID)
local humidity_eps = device:get_endpoints(clusters.RelativeHumidityMeasurement.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 @@ -74,10 +86,9 @@ 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
profile_name = profile_name .. "-nostate"

device:set_field("profile_name", profile_name)
device:send(ThermostatClusterAttributeList: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 @@ -142,6 +153,18 @@ local function system_mode_handler(driver, device, ib, response)
end
end

local function attr_list_handler(driver, device, ib, response)
local utils = require "st.utils"
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 @@ -346,6 +369,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,
[ThermostatClusterAttributeList.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 @@ -16,7 +16,7 @@ local test = require "integration_test"
local capabilities = require "st.capabilities"
local t_utils = require "integration_test.utils"
local utils = require "st.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 All @@ -32,26 +32,9 @@ local mock_device = test.mock_device.build_test_matter_device({
{cluster_id = clusters.FanControl.ID, cluster_type = "SERVER"},
{
cluster_id = clusters.Thermostat.ID,
attributes={
0,
18,
26,
27,
28,
65528,
65529,
65531,
65532,
65533,
},
client_commands={
0,
},
cluster_revision=5,
cluster_type="SERVER",
events={},
feature_map=1, -- Heat feature only.
server_commands={},
},
{cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"},
{cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}
Expand All @@ -72,26 +55,10 @@ local mock_device_simple = test.mock_device.build_test_matter_device({
clusters = {
{
cluster_id = clusters.Thermostat.ID,
attributes={
0,
18,
26,
27,
28,
65528,
65529,
65531,
65532,
65533,
},
client_commands={
0,
},
cluster_revision=5,
cluster_type="SERVER",
events={},
feature_map=2, -- Cool feature only.
server_commands={},
},
{cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"},
}
Expand Down Expand Up @@ -144,28 +111,63 @@ 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()
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(0x1)}),
})
end
)

test.register_coroutine_test(
"Profile change due to Thermostat attribute list",
function()
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
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,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
Loading

0 comments on commit 3df0388

Please sign in to comment.