Skip to content
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
3 changes: 3 additions & 0 deletions drivers/SmartThings/matter-switch/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ local matter_driver_template = {
[capabilities.colorTemperature.ID] = {
[capabilities.colorTemperature.commands.setColorTemperature.NAME] = capability_handlers.handle_set_color_temperature,
},
[capabilities.energyMeter.ID] = {
[capabilities.energyMeter.commands.resetEnergyMeter.NAME] = capability_handlers.handle_reset_energy_meter,
},
[capabilities.fanMode.ID] = {
[capabilities.fanMode.commands.setFanMode.NAME] = capability_handlers.handle_set_fan_mode
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ function AttributeHandlers.energy_imported_factory(is_periodic_report)
device, ib, capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME
) or 0
energy_imported_wh = energy_imported_wh + energy_meter_latest_state
else
-- the field containing the offset may be associated with a child device
local field_device = switch_utils.find_child(device, ib.endpoint_id) or device
local energy_meter_offset = field_device:get_field(fields.ENERGY_METER_OFFSET) or 0.0
energy_imported_wh = energy_imported_wh - energy_meter_offset
end
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.energyMeter.energy({ value = energy_imported_wh, unit = "Wh" }))
switch_utils.report_power_consumption_to_st_energy(device, ib.endpoint_id, energy_imported_wh)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,25 @@ function CapabilityHandlers.handle_fan_speed_set_percent(driver, device, cmd)
device:send(clusters.FanControl.attributes.PercentSetting:write(device, fan_ep, speed))
end

return CapabilityHandlers

-- [[ ENERGY METER CAPABILITY COMMANDS ]] --

---
--- If a Cumulative Reporting device, this will store the most recent energy meter reading, and all subsequent reports will have this value subtracted
--- from the value reported. Matter, like Zigbee and unlike Z-Wave, does not provide a way to reset the value to zero, so this is an attempt at a workaround.
--- In the case it is a Periodic Reporting device, the reports do not need to be offset, so setting the current energy to 0.0 will do the same thing.
---
function CapabilityHandlers.handle_reset_energy_meter(driver, device, cmd)
local energy_meter_latest_state = device:get_latest_state(cmd.component, capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) or 0.0
if energy_meter_latest_state ~= 0.0 then
device:emit_component_event(device.profile.components[cmd.component], capabilities.energyMeter.energy({value = 0.0, unit = "Wh"}))
-- note: field containing cumulative reports supported is only set on the parent device
local field_device = device:get_parent_device() or device
if field_device:get_field(fields.CUMULATIVE_REPORTS_SUPPORTED) then
local current_offset = device:get_field(fields.ENERGY_METER_OFFSET) or 0.0
device:set_field(fields.ENERGY_METER_OFFSET, current_offset + energy_meter_latest_state, {persist=true})
end
end
end

return CapabilityHandlers
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ SwitchFields.profiling_data = {
POWER_TOPOLOGY = "__power_topology",
}

SwitchFields.ENERGY_METER_OFFSET = "__energy_meter_offset"
SwitchFields.CUMULATIVE_REPORTS_SUPPORTED = "__cumulative_reports_supported"
SwitchFields.LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp"
SwitchFields.MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds
Expand Down
3 changes: 2 additions & 1 deletion drivers/SmartThings/matter-switch/src/switch_utils/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ function utils.mired_to_kelvin(value, minOrMax)
end

function utils.get_product_override_field(device, override_key)
if fields.vendor_overrides[device.manufacturer_info.vendor_id]
if device.manufacturer_info
and fields.vendor_overrides[device.manufacturer_info.vendor_id]
and fields.vendor_overrides[device.manufacturer_info.vendor_id][device.manufacturer_info.product_id]
then
return fields.vendor_overrides[device.manufacturer_info.vendor_id][device.manufacturer_info.product_id][override_key]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,17 @@ local periodic_report_val_23 = {
reactive_energy = 0
}

local mock_child = test.mock_device.build_test_child_device({
profile = t_utils.get_profile_definition("plug-level-energy-powerConsumption.yml"),
device_network_id = string.format("%s:%d", mock_device.id, 4),
parent_device_id = mock_device.id,
parent_assigned_child_key = string.format("%d", 4)
})

local function test_init()
test.mock_device.add_test_device(mock_device)
test.mock_device.add_test_device(mock_child)

local subscribe_request = subscribed_attributes[1]:subscribe(mock_device)
for i, cluster in ipairs(subscribed_attributes) do
if i > 1 then
Expand Down Expand Up @@ -431,7 +440,6 @@ 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, "doConfigure" })
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 4, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
Expand Down Expand Up @@ -463,6 +471,189 @@ test.register_coroutine_test(
{ test_init = test_init_periodic }
)

test.register_coroutine_test(
"Test resetEnergyMeter command on parent and child for CumulativeEnergyImported",
function()

test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases)

-- this block needs to run to set the requisite fields. It is tested on its own elsewhere
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" })
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 2, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
test.socket.matter:__expect_send({mock_device.id, clusters.LevelControl.attributes.Options:write(mock_device, 4, clusters.LevelControl.types.OptionsBitmap.EXECUTE_IF_OFF)})
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 of block

-- Initial Parent Energy Report
test.socket.matter:__queue_receive(
{
mock_device.id,
clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data(
mock_device, 1, cumulative_report_val_19
)
}
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" }))
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({
start = "1970-01-01T00:00:00Z",
["end"] = "1970-01-01T00:15:00Z",
deltaEnergy = 0.0,
energy = 19.0
}))
)

-- Initial Child Energy Report
test.socket.matter:__queue_receive(
{
mock_device.id,
clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data(
mock_device, 3, cumulative_report_val_19
)
}
)
test.socket.capability:__expect_send(
mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 19.0, unit = "Wh" }))
)
-- no powerConsumptionReport will be emitted now, since it has not been 15 minutes since the previous report (even though it was the parent).


test.wait_for_events()
test.mock_time.advance_time(1500)


-- Parent call to resetEnergyMeter
test.socket.capability:__queue_receive({mock_device.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}})
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" }))
)
-- Child call to resetEnergyMeter
test.socket.capability:__queue_receive({mock_child.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}})
test.socket.capability:__expect_send(
mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" }))
)

test.wait_for_events()

-- Second Child Energy Report
test.socket.matter:__queue_receive(
{
mock_device.id,
clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data(
mock_device, 3, cumulative_report_val_39
)
}
)
test.socket.capability:__expect_send(
mock_child:generate_test_message("main", capabilities.energyMeter.energy({ value = 20.0, unit = "Wh" }))
)
test.socket.capability:__expect_send(
mock_child:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({
start = "1970-01-01T00:15:01Z",
["end"] = "1970-01-01T00:40:00Z",
deltaEnergy = 0.0,
energy = 20.0
}))
)

-- Second Parent Energy Report
test.socket.matter:__queue_receive(
{
mock_device.id,
clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyImported:build_test_report_data(
mock_device, 1, cumulative_report_val_39
)
}
)
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 20.0, unit = "Wh" }))
)
-- no powerConsumptionReport will be emitted now, since it has not been 15 minutes since the previous report (even though it was the child).
end,
{ test_init = test_init }
)

test.register_coroutine_test(
"Test resetEnergyMeter command on device for PeriodicEnergyImported",
function()
test.mock_time.advance_time(901) -- move time 15 minutes past 0 (this can be assumed to be true in practice in all cases)
test.socket.matter:__queue_receive(
{
mock_device_periodic.id,
clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data(
mock_device_periodic, 1, periodic_report_val_23
)
}
)
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 23.0, unit="Wh"}))
)
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({
start = "1970-01-01T00:00:00Z",
["end"] = "1970-01-01T00:15:00Z",
deltaEnergy = 0.0,
energy = 23.0
}))
)
test.socket.matter:__queue_receive(
{
mock_device_periodic.id,
clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data(
mock_device_periodic, 1, periodic_report_val_23
)
}
)
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 46.0, unit="Wh"}))
)

test.wait_for_events()
test.mock_time.advance_time(2000)

test.socket.capability:__queue_receive({mock_device_periodic.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {}}})
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" }))
)

test.wait_for_events()

test.socket.matter:__queue_receive(
{
mock_device_periodic.id,
clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyImported:build_test_report_data(
mock_device_periodic, 1, cumulative_report_val_19
)
}
)
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 19.0, unit="Wh"}))
)
test.socket.capability:__expect_send(
mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({
start = "1970-01-01T00:15:01Z",
["end"] = "1970-01-01T00:48:20Z",
deltaEnergy = -4.0,
energy = 19.0
}))
)
end,
{ test_init = test_init_periodic }
)

test.register_message_test(
"Set level command should send the appropriate commands",
{
Expand Down
Loading