Skip to content

Matter Switch: Add greater Energy profiling logic for Switches #2199

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

Open
wants to merge 13 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
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
name: light-level-power-energy-powerConsumption
components:
- id: main
capabilities:
- id: switch
version: 1
- id: switchLevel
version: 1
config:
values:
- key: "level.value"
range: [1, 100]
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: powerConsumptionReport
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Light
- id: main
capabilities:
- id: switch
version: 1
- id: switchLevel
version: 1
config:
values:
- key: "level.value"
range: [1, 100]
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: powerConsumptionReport
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Light
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name: light-power-energy-powerConsumption
components:
- id: main
capabilities:
- id: switch
version: 1
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: powerConsumptionReport
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Light
- id: main
capabilities:
- id: switch
version: 1
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: powerConsumptionReport
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Light
332 changes: 214 additions & 118 deletions drivers/SmartThings/matter-switch/src/init.lua

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ local function test_init()
end

test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request})
aqara_mock_device:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
Copy link
Contributor

@nickolas-deboom nickolas-deboom Jul 1, 2025

Choose a reason for hiding this comment

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

The Aqara subdriver overrides device_added so this field would not be set in the current implementation and so the subdriver may need to be updated to set it. The other subdrivers may also need to account for this

Copy link
Contributor Author

@hcarter-775 hcarter-775 Jul 1, 2025

Choose a reason for hiding this comment

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

this test doesn't handle a device that's using a subdriver.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, my bad, I saw Aqara and thought this was the Aqara cube test file

test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "doConfigure" })
local read_attribute_list = clusters.PowerSource.attributes.AttributeList:read()
test.socket.matter:__expect_send({aqara_mock_device.id, read_attribute_list})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ local aqara_mock_device = test.mock_device.build_test_matter_device({
clusters = {
{cluster_id = clusters.Basic.ID, cluster_type = "SERVER"},
{cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 2 },
{cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 5 }
{cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 5 },
{cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 1 } -- NODE_TOPOLOGY
},
device_types = {
{device_type_id = 0x0016, device_type_revision = 1}, -- RootNode
Expand Down Expand Up @@ -177,10 +178,12 @@ local function test_init()
end
end
test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request})

-- Test added -> doConfigure logic
test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "added" })
test.socket.matter:__expect_send({aqara_mock_device.id, subscribe_request})
test.socket.device_lifecycle:__queue_receive({ aqara_mock_device.id, "doConfigure" })
test.mock_devices_api._expected_device_updates[aqara_mock_device.device_id] = "00000000-1111-2222-3333-000000000001"
test.mock_devices_api._expected_device_updates[1] = {device_id = "00000000-1111-2222-3333-000000000001"}
test.mock_devices_api._expected_device_updates[1].metadata = {deviceId="00000000-1111-2222-3333-000000000001", profileReference="4-button"}
aqara_mock_device:expect_metadata_update({ profile = "4-button" })
aqara_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.mock_device.add_test_device(aqara_mock_device)
-- to test powerConsumptionReport
Expand Down Expand Up @@ -277,7 +280,7 @@ test.register_coroutine_test(
{
-- don't use "aqara_mock_children[aqara_child1_ep].id,"
-- because energy management is at the root endpoint.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment can be removed now 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, is this the correct behavior? If I remember right this device has it's energy management cluster on the root endpoint, so why is it now testing aqara_child1_ep?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is, this update actually makes the whole aqara child system work better now we use emit_component_event(), side stepping the original issues we faced having the energy management cluster on the root endpoint.

aqara_mock_device.id,
aqara_mock_children[aqara_child1_ep].id,
clusters.ElectricalPowerMeasurement.attributes.ActivePower:build_test_report_data(aqara_mock_device, 1, 17000)
}
)
Expand All @@ -289,7 +292,7 @@ test.register_coroutine_test(

test.socket.matter:__queue_receive(
{
aqara_mock_device.id,
aqara_mock_children[aqara_child1_ep].id,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 1, cumulative_report_val_19)
}
)
Expand All @@ -302,7 +305,7 @@ test.register_coroutine_test(
-- This is because related variable settings are required in set_poll_report_timer_and_schedule().
test.socket.matter:__queue_receive(
{
aqara_mock_device.id,
aqara_mock_children[aqara_child1_ep].id,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 1, cumulative_report_val_29)
}
)
Expand All @@ -313,10 +316,8 @@ test.register_coroutine_test(

test.socket.matter:__queue_receive(
{
aqara_mock_device.id,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(
aqara_mock_device, 1, cumulative_report_val_39
)
aqara_mock_children[aqara_child1_ep].id,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:build_test_report_data(aqara_mock_device, 1, cumulative_report_val_39)
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
local test = require "integration_test"
local capabilities = require "st.capabilities"
local t_utils = require "integration_test.utils"
local uint32 = require "st.matter.data_types.Uint32"

local clusters = require "st.matter.clusters"

Expand Down Expand Up @@ -42,6 +43,7 @@ local mock_device = test.mock_device.build_test_matter_device({
clusters = {
{ cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, },
{ cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", feature_map = 0, },
{ cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, }, -- SET_TOPOLOGY
},
device_types = {
{ device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor
Expand All @@ -51,10 +53,30 @@ local mock_device = test.mock_device.build_test_matter_device({
endpoint_id = 2,
clusters = {
{ cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, },
{cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}
{ cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2},
},
device_types = {
{ device_type_id = 0x010A, device_type_revision = 1 } -- OnOff Plug
{ device_type_id = 0x010B, device_type_revision = 1 }, -- OnOff Dimmable Plug
}
},
{
endpoint_id = 3,
clusters = {
{ cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, },
{ cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, }, -- SET_TOPOLOGY
},
device_types = {
{ device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor
}
},
{
endpoint_id = 4,
clusters = {
{ cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, },
{ cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2},
},
device_types = {
{ device_type_id = 0x010B, device_type_revision = 1 }, -- OnOff Dimmable Plug
}
},
},
Expand All @@ -80,16 +102,20 @@ local mock_device_periodic = test.mock_device.build_test_matter_device({
{
endpoint_id = 1,
clusters = {
{ cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, },
{ cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 10, },
{ cluster_id = clusters.PowerTopology.ID, cluster_type = "SERVER", feature_map = 4, } -- SET_TOPOLOGY
},
device_types = {
{ device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor
{ device_type_id = 0x010A, device_type_revision = 1 }, -- OnOff Plug
{ device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor
}
},
},
})

local subscribed_attributes_periodic = {
clusters.OnOff.attributes.OnOff,
clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported,
}
Expand Down Expand Up @@ -643,19 +669,52 @@ test.register_coroutine_test(
test.register_coroutine_test(
"Test profile change on init for Electrical Sensor device type",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
local read_req = clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device.id, 1)
read_req:merge(clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device.id, 3))
test.socket.matter:__expect_send({ mock_device.id, read_req })
local subscribe_request = subscribed_attributes[1]:subscribe(mock_device)
for i, cluster in ipairs(subscribed_attributes) 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.id, "doConfigure" })
mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 1, {uint32(2)})})
test.socket.matter:__queue_receive({ mock_device.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device, 3, {uint32(4)})})
mock_device:expect_metadata_update({ profile = "plug-level-power-energy-powerConsumption" })
mock_device:expect_device_create({
type = "EDGE_CHILD",
label = "nil 2",
profile = "plug-level-energy-powerConsumption",
parent_device_id = mock_device.id,
parent_assigned_child_key = string.format("%d", 4)
})
end,
{ test_init = test_init }
)

test.register_coroutine_test(
"Test profile change on init for only Periodic Electrical Sensor device type",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "added" })
local read_req = clusters.PowerTopology.attributes.AvailableEndpoints:read(mock_device_periodic.id, 1)
test.socket.matter:__expect_send({ mock_device_periodic.id, read_req })
local subscribe_request = subscribed_attributes_periodic[1]:subscribe(mock_device_periodic)
for i, cluster in ipairs(subscribed_attributes_periodic) do
if i > 1 then
subscribe_request:merge(cluster:subscribe(mock_device_periodic))
end
end
test.socket.matter:__expect_send({ mock_device_periodic.id, subscribe_request })
test.socket.device_lifecycle:__queue_receive({ mock_device_periodic.id, "doConfigure" })
mock_device_periodic:expect_metadata_update({ profile = "plug-energy-powerConsumption" })
mock_device_periodic:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.wait_for_events()
test.socket.matter:__queue_receive({ mock_device_periodic.id, clusters.PowerTopology.attributes.AvailableEndpoints:build_test_report_data(mock_device_periodic, 1, {uint32(1)})})
mock_device_periodic:expect_metadata_update({ profile = "plug-energy-powerConsumption" })
end,
{ test_init = test_init_periodic }
)
Expand Down Expand Up @@ -692,7 +751,7 @@ test.register_message_test(
direction = "receive",
message = {
mock_device.id,
clusters.LevelControl.server.commands.MoveToLevelWithOnOff:build_test_command_response(mock_device, 2)
clusters.LevelControl.server.commands.MoveToLevelWithOnOff:build_test_command_response(mock_device, 1)
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ local function test_init()
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
mock_device:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.mock_device.add_test_device(mock_device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ local function test_init()
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
test.mock_device.add_test_device(mock_device)
mock_device:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
mock_device:expect_metadata_update({ profile = "light-color-level-fan" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ local function test_init()
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
mock_device:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
test.mock_device.add_test_device(mock_device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ local function test_init()
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
end
test.socket.matter:__expect_send({mock_device.id, subscribe_request})
mock_device:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
mock_device:expect_metadata_update({ profile = "light-level-3-button" })
mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" })
Expand Down Expand Up @@ -236,6 +237,7 @@ local function test_init_mcd_unsupported_switch_device_type()
end
end
test.socket.matter:__expect_send({mock_device_mcd_unsupported_switch_device_type.id, subscribe_request})
mock_device_mcd_unsupported_switch_device_type:set_field("__ELECTRICAL_TOPOLOGY", {topology = false, tags_on_ep = {}}, {persist = false}) -- since we're assuming this would have happened during device_added in this case.
test.socket.device_lifecycle:__queue_receive({ mock_device_mcd_unsupported_switch_device_type.id, "doConfigure" })
mock_device_mcd_unsupported_switch_device_type:expect_metadata_update({ profile = "2-button" })
mock_device_mcd_unsupported_switch_device_type:expect_metadata_update({ provisioning_state = "PROVISIONED" })
Expand Down
Loading
Loading