From f095f82df23ab8ec13f74756b226caaffe9ccd90 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Mon, 31 Jul 2023 14:41:10 +0200 Subject: [PATCH 01/22] These changes improve the Eve Energy support - A subdriver has been implemented - It implements the switch, powerMeter, energyMeter and powerConsumptionReport capabilities - It supports the refresh capability - It reads with a timer each minute the current Watt and Watt Accumulated values from custom attributes - The powerMeter and energyMeter values are updated each values or on a refresh - A powerConsumptionReport is sent each 10 minutes by comparing the previous Watt Accumulated value with the current Watt Accumulated value --- .../matter-switch/fingerprints.yml | 8 +- .../matter-switch/profiles/eve-energy.yml | 18 ++ .../matter-switch/src/eve-energy/init.lua | 233 ++++++++++++++++++ .../SmartThings/matter-switch/src/init.lua | 3 + 4 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/eve-energy.yml create mode 100644 drivers/SmartThings/matter-switch/src/eve-energy/init.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e9f2ccbe4f..88fa93fc4d 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4,22 +4,22 @@ matterManufacturer: deviceLabel: Eve Energy vendorId: 0x130A productId: 0x53 - deviceProfileName: plug-binary + deviceProfileName: eve-energy - id: "Eve/Energy/Europe" deviceLabel: Eve Energy vendorId: 0x130A productId: 0x50 - deviceProfileName: plug-binary + deviceProfileName: eve-energy - id: "Eve/Energy/U.K." deviceLabel: Eve Energy vendorId: 0x130A productId: 0x54 - deviceProfileName: plug-binary + deviceProfileName: eve-energy - id: "Eve/Energy/Australia" deviceLabel: Eve Energy vendorId: 0x130A productId: 0x5E - deviceProfileName: plug-binary + deviceProfileName: eve-energy #GE - id: "GELightingSavant/FullColor/Bulb" diff --git a/drivers/SmartThings/matter-switch/profiles/eve-energy.yml b/drivers/SmartThings/matter-switch/profiles/eve-energy.yml new file mode 100644 index 0000000000..f26fcc418a --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/eve-energy.yml @@ -0,0 +1,18 @@ +name: eve-energy +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: SmartPlug diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua new file mode 100644 index 0000000000..1091acd012 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -0,0 +1,233 @@ +------------------------------------------------------------------------------------- +-- Definitions +------------------------------------------------------------------------------------- + +local capabilities = require "st.capabilities" +local log = require "log" +local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local MatterDriver = require "st.matter.driver" +local utils = require "st.utils" +local data_types = require "st.matter.data_types" + +local EVE_MANUFACTURER_ID = 0x130A +local PRIVATE_CLUSTER_ID = 0x130AFC01 + +local PRIVATE_ATTR_ID_WATT = 0x130A000A +local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local TIMER_REPEAT = (1 * 60) -- Run the timer each minute +local REPORT_TIMEOUT = (10 * 60) -- Report the value each 10 minutes + + +------------------------------------------------------------------------------------- +-- Eve specifics +------------------------------------------------------------------------------------- + +local function is_eve_energy_products(opts, driver, device) + if device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID then + return true + end + + return false +end + +-- Return a ISO 8061 formatted timestamp in UTC (Z) +-- @return e.g. 2022-02-02T08:00:00Z +local function iso8061Timestamp(time) + return os.date("!%Y-%m-%dT%TZ", time) +end + +local function updateEnergyMeter(device, totalConsumptionWh) + -- Report the energy consumed + device:emit_event(capabilities.energyMeter.energy({ value = totalConsumptionWh, unit = "Wh" })) + + -- Only send powerConsumptionReport every 10 minutes + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + REPORT_TIMEOUT + if current_time < next_time then + return + end + + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + + -- Calculate the energy consumed between the start and the end time + local previousTotalConsumptionWh = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME) + + local deltaEnergyWh = 0.0 + if previousTotalConsumptionWh ~= nil and previousTotalConsumptionWh.energy ~= nil then + deltaEnergyWh = math.max(totalConsumptionWh - previousTotalConsumptionWh.energy, 0.0) + end + + local startTime = iso8061Timestamp(last_time) + local endTime = iso8061Timestamp(current_time - 1) + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + start = startTime, + ["end"] = endTime, + deltaEnergy = deltaEnergyWh, + energy = totalConsumptionWh + })) +end + + +------------------------------------------------------------------------------------- +-- Timer +------------------------------------------------------------------------------------- + +local function requestData(device) + -- Update the on/off status + device:send(clusters.OnOff.attributes.OnOff:read(device)) + + -- Update the Watt usage + device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT, nil)) + + -- Update the energy consumption + device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil)) +end + +local timer = nil +local function create_poll_schedule(device) + -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + -- Eve Energy generally report changes every 10 or 17 minutes + timer = device.thread:call_on_schedule(TIMER_REPEAT, function() + requestData(device) + end, "polling_schedule_timer") +end + + +------------------------------------------------------------------------------------- +-- Matter Utilities +------------------------------------------------------------------------------------- + +--- component_to_endpoint helper function to handle situations where +--- device does not have endpoint ids in sequential order from 1 +--- In this case the function returns the lowest endpoint value that isn't 0 +local function find_default_endpoint(device, component) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(nil) + table.sort(eps) + for _, v in ipairs(eps) do + if v ~= 0 then --0 is the matter RootNode endpoint + res = v + break + end + end + return res +end + +local function component_to_endpoint(device, component_id) + -- Assumes matter endpoint layout is sequentional starting at 1. + local ep_num = component_id:match("switch(%d)") + return ep_num and tonumber(ep_num) or find_default_endpoint(device, component_id) +end + +local function endpoint_to_component(device, ep) + local switch_comp = string.format("switch%d", ep) + if device.profile.components[switch_comp] ~= nil then + return switch_comp + else + return "main" + end +end + + +------------------------------------------------------------------------------------- +-- Device Management +------------------------------------------------------------------------------------- + +local function device_init(driver, device) + log.info_with({ hub_logs = true }, "device init") + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) + device:subscribe() + + create_poll_schedule(device) +end + +local function device_added(driver, device) + -- Reset the values + device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) +end + +local function device_removed(driver, device) + if timer ~= nil then + device.thread:cancel_timer(timer) + end +end + +local function handle_refresh(self, device) + requestData(device) +end + + +------------------------------------------------------------------------------------- +-- Eve Energy Handler +------------------------------------------------------------------------------------- + +local function matter_handler(driver, device, response_block) + log.info(string.format("Fallback handler for %s", response_block)) +end + +local function on_off_attr_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) + end +end + +local function watt_attr_handler(driver, device, ib, zb_rx) + if ib.data.value then + local wattValue = ib.data.value + device:emit_event(capabilities.powerMeter.power({ value = wattValue, unit = "W" })) + end +end + +local function watt_accumulated_attr_handler(driver, device, ib, zb_rx) + if ib.data.value then + local totalConsumptionRawValue = ib.data.value + local totalConsumptionWh = utils.round(1000 * totalConsumptionRawValue) + updateEnergyMeter(device, totalConsumptionWh) + end +end + +local eve_energy_handler = { + NAME = "Eve Energy Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + removed = device_removed, + }, + matter_handlers = { + attr = { + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + }, + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_ATTR_ID_WATT] = watt_attr_handler, + [PRIVATE_ATTR_ID_WATT_ACCUMULATED] = watt_accumulated_attr_handler + } + }, + fallback = matter_handler, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = handle_refresh, + }, + }, + supported_capabilities = { + capabilities.switch, + capabilities.powerMeter, + capabilities.energyMeter, + capabilities.powerConsumptionReport + }, + can_handle = is_eve_energy_products +} + +return eve_energy_handler diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index edd6af7c63..ea2849c636 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -336,6 +336,9 @@ local matter_driver_template = { capabilities.colorControl, capabilities.colorTemperature, }, + sub_drivers = { + require("eve-energy") + } } local matter_driver = MatterDriver("matter-switch", matter_driver_template) From 0816619d09a6ab18204fe932f1d54f4b12d60138 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Mon, 31 Jul 2023 14:56:22 +0200 Subject: [PATCH 02/22] Eve Energy: Fix 2 unused variable warnings from the Luacheck linter --- drivers/SmartThings/matter-switch/src/eve-energy/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 1091acd012..fdb97696b1 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -6,9 +6,7 @@ local capabilities = require "st.capabilities" local log = require "log" local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" -local MatterDriver = require "st.matter.driver" local utils = require "st.utils" -local data_types = require "st.matter.data_types" local EVE_MANUFACTURER_ID = 0x130A local PRIVATE_CLUSTER_ID = 0x130AFC01 From 456a3182243ed01e2d2a3476223e5f970794b252 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 11:17:23 +0200 Subject: [PATCH 03/22] Use 2-width spaces instead of tabs --- .../matter-switch/src/eve-energy/init.lua | 236 +++++++++--------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index fdb97696b1..2ac25188ab 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -24,52 +24,52 @@ local REPORT_TIMEOUT = (10 * 60) -- Report the value each 10 minutes ------------------------------------------------------------------------------------- local function is_eve_energy_products(opts, driver, device) - if device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID then - return true - end + if device.manufacturer_info.vendor_id == EVE_MANUFACTURER_ID then + return true + end - return false + return false end -- Return a ISO 8061 formatted timestamp in UTC (Z) -- @return e.g. 2022-02-02T08:00:00Z local function iso8061Timestamp(time) - return os.date("!%Y-%m-%dT%TZ", time) + return os.date("!%Y-%m-%dT%TZ", time) end local function updateEnergyMeter(device, totalConsumptionWh) - -- Report the energy consumed - device:emit_event(capabilities.energyMeter.energy({ value = totalConsumptionWh, unit = "Wh" })) + -- Report the energy consumed + device:emit_event(capabilities.energyMeter.energy({ value = totalConsumptionWh, unit = "Wh" })) - -- Only send powerConsumptionReport every 10 minutes - local current_time = os.time() - local last_time = device:get_field(LAST_REPORT_TIME) or 0 - local next_time = last_time + REPORT_TIMEOUT - if current_time < next_time then - return - end + -- Only send powerConsumptionReport every 10 minutes + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + REPORT_TIMEOUT + if current_time < next_time then + return + end - device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) - -- Calculate the energy consumed between the start and the end time - local previousTotalConsumptionWh = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, - capabilities.powerConsumptionReport.powerConsumption.NAME) + -- Calculate the energy consumed between the start and the end time + local previousTotalConsumptionWh = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME) - local deltaEnergyWh = 0.0 - if previousTotalConsumptionWh ~= nil and previousTotalConsumptionWh.energy ~= nil then - deltaEnergyWh = math.max(totalConsumptionWh - previousTotalConsumptionWh.energy, 0.0) - end + local deltaEnergyWh = 0.0 + if previousTotalConsumptionWh ~= nil and previousTotalConsumptionWh.energy ~= nil then + deltaEnergyWh = math.max(totalConsumptionWh - previousTotalConsumptionWh.energy, 0.0) + end - local startTime = iso8061Timestamp(last_time) - local endTime = iso8061Timestamp(current_time - 1) + local startTime = iso8061Timestamp(last_time) + local endTime = iso8061Timestamp(current_time - 1) - -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' - device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ - start = startTime, - ["end"] = endTime, - deltaEnergy = deltaEnergyWh, - energy = totalConsumptionWh - })) + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + start = startTime, + ["end"] = endTime, + deltaEnergy = deltaEnergyWh, + energy = totalConsumptionWh + })) end @@ -78,23 +78,23 @@ end ------------------------------------------------------------------------------------- local function requestData(device) - -- Update the on/off status - device:send(clusters.OnOff.attributes.OnOff:read(device)) + -- Update the on/off status + device:send(clusters.OnOff.attributes.OnOff:read(device)) - -- Update the Watt usage - device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT, nil)) + -- Update the Watt usage + device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT, nil)) - -- Update the energy consumption - device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil)) + -- Update the energy consumption + device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil)) end local timer = nil local function create_poll_schedule(device) - -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy - -- Eve Energy generally report changes every 10 or 17 minutes - timer = device.thread:call_on_schedule(TIMER_REPEAT, function() - requestData(device) - end, "polling_schedule_timer") + -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + -- Eve Energy generally report changes every 10 or 17 minutes + timer = device.thread:call_on_schedule(TIMER_REPEAT, function() + requestData(device) + end, "polling_schedule_timer") end @@ -106,31 +106,31 @@ end --- device does not have endpoint ids in sequential order from 1 --- In this case the function returns the lowest endpoint value that isn't 0 local function find_default_endpoint(device, component) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = device:get_endpoints(nil) - table.sort(eps) - for _, v in ipairs(eps) do - if v ~= 0 then --0 is the matter RootNode endpoint - res = v - break - end - end - return res + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = device:get_endpoints(nil) + table.sort(eps) + for _, v in ipairs(eps) do + if v ~= 0 then --0 is the matter RootNode endpoint + res = v + break + end + end + return res end local function component_to_endpoint(device, component_id) - -- Assumes matter endpoint layout is sequentional starting at 1. - local ep_num = component_id:match("switch(%d)") - return ep_num and tonumber(ep_num) or find_default_endpoint(device, component_id) + -- Assumes matter endpoint layout is sequentional starting at 1. + local ep_num = component_id:match("switch(%d)") + return ep_num and tonumber(ep_num) or find_default_endpoint(device, component_id) end local function endpoint_to_component(device, ep) - local switch_comp = string.format("switch%d", ep) - if device.profile.components[switch_comp] ~= nil then - return switch_comp - else - return "main" - end + local switch_comp = string.format("switch%d", ep) + if device.profile.components[switch_comp] ~= nil then + return switch_comp + else + return "main" + end end @@ -139,28 +139,28 @@ end ------------------------------------------------------------------------------------- local function device_init(driver, device) - log.info_with({ hub_logs = true }, "device init") - device:set_component_to_endpoint_fn(component_to_endpoint) - device:set_endpoint_to_component_fn(endpoint_to_component) - device:subscribe() + log.info_with({ hub_logs = true }, "device init") + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) + device:subscribe() - create_poll_schedule(device) + create_poll_schedule(device) end local function device_added(driver, device) - -- Reset the values - device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) - device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + -- Reset the values + device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) end local function device_removed(driver, device) - if timer ~= nil then - device.thread:cancel_timer(timer) - end + if timer ~= nil then + device.thread:cancel_timer(timer) + end end local function handle_refresh(self, device) - requestData(device) + requestData(device) end @@ -169,63 +169,63 @@ end ------------------------------------------------------------------------------------- local function matter_handler(driver, device, response_block) - log.info(string.format("Fallback handler for %s", response_block)) + log.info(string.format("Fallback handler for %s", response_block)) end local function on_off_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) - end + if ib.data.value then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) + end end local function watt_attr_handler(driver, device, ib, zb_rx) - if ib.data.value then - local wattValue = ib.data.value - device:emit_event(capabilities.powerMeter.power({ value = wattValue, unit = "W" })) - end + if ib.data.value then + local wattValue = ib.data.value + device:emit_event(capabilities.powerMeter.power({ value = wattValue, unit = "W" })) + end end local function watt_accumulated_attr_handler(driver, device, ib, zb_rx) - if ib.data.value then - local totalConsumptionRawValue = ib.data.value - local totalConsumptionWh = utils.round(1000 * totalConsumptionRawValue) - updateEnergyMeter(device, totalConsumptionWh) - end + if ib.data.value then + local totalConsumptionRawValue = ib.data.value + local totalConsumptionWh = utils.round(1000 * totalConsumptionRawValue) + updateEnergyMeter(device, totalConsumptionWh) + end end local eve_energy_handler = { - NAME = "Eve Energy Handler", - lifecycle_handlers = { - init = device_init, - added = device_added, - removed = device_removed, - }, - matter_handlers = { - attr = { - [clusters.OnOff.ID] = { - [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, - }, - [PRIVATE_CLUSTER_ID] = { - [PRIVATE_ATTR_ID_WATT] = watt_attr_handler, - [PRIVATE_ATTR_ID_WATT_ACCUMULATED] = watt_accumulated_attr_handler - } - }, - fallback = matter_handler, - }, - capability_handlers = { - [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = handle_refresh, - }, - }, - supported_capabilities = { - capabilities.switch, - capabilities.powerMeter, - capabilities.energyMeter, - capabilities.powerConsumptionReport - }, - can_handle = is_eve_energy_products + NAME = "Eve Energy Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + removed = device_removed, + }, + matter_handlers = { + attr = { + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + }, + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_ATTR_ID_WATT] = watt_attr_handler, + [PRIVATE_ATTR_ID_WATT_ACCUMULATED] = watt_accumulated_attr_handler + } + }, + fallback = matter_handler, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = handle_refresh, + }, + }, + supported_capabilities = { + capabilities.switch, + capabilities.powerMeter, + capabilities.energyMeter, + capabilities.powerConsumptionReport + }, + can_handle = is_eve_energy_products } return eve_energy_handler From 3d5cc7a6502fe517d3242e1cd6a7757bd8424e63 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 15:52:23 +0200 Subject: [PATCH 04/22] Rename the profile to power-energy-powerConsumption --- drivers/SmartThings/matter-switch/fingerprints.yml | 8 ++++---- drivers/SmartThings/matter-switch/profiles/eve-energy.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 88fa93fc4d..f40e8429a6 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -4,22 +4,22 @@ matterManufacturer: deviceLabel: Eve Energy vendorId: 0x130A productId: 0x53 - deviceProfileName: eve-energy + deviceProfileName: power-energy-powerConsumption - id: "Eve/Energy/Europe" deviceLabel: Eve Energy vendorId: 0x130A productId: 0x50 - deviceProfileName: eve-energy + deviceProfileName: power-energy-powerConsumption - id: "Eve/Energy/U.K." deviceLabel: Eve Energy vendorId: 0x130A productId: 0x54 - deviceProfileName: eve-energy + deviceProfileName: power-energy-powerConsumption - id: "Eve/Energy/Australia" deviceLabel: Eve Energy vendorId: 0x130A productId: 0x5E - deviceProfileName: eve-energy + deviceProfileName: power-energy-powerConsumption #GE - id: "GELightingSavant/FullColor/Bulb" diff --git a/drivers/SmartThings/matter-switch/profiles/eve-energy.yml b/drivers/SmartThings/matter-switch/profiles/eve-energy.yml index f26fcc418a..362edd1a30 100644 --- a/drivers/SmartThings/matter-switch/profiles/eve-energy.yml +++ b/drivers/SmartThings/matter-switch/profiles/eve-energy.yml @@ -1,4 +1,4 @@ -name: eve-energy +name: power-energy-powerConsumption components: - id: main capabilities: From ae7038f9044da0f1f3ed9b7dc65fd3e8e51d11eb Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 16:04:52 +0200 Subject: [PATCH 05/22] We now store the timer in the device storage and not as a driver-level variable --- .../matter-switch/src/eve-energy/init.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 2ac25188ab..f9b3793738 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -15,6 +15,7 @@ local PRIVATE_ATTR_ID_WATT = 0x130A000A local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local RECURRING_POLL_TIMER = "RECURRING_POLL_TIMER" local TIMER_REPEAT = (1 * 60) -- Run the timer each minute local REPORT_TIMEOUT = (10 * 60) -- Report the value each 10 minutes @@ -88,13 +89,14 @@ local function requestData(device) device:send(cluster_base.read(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil)) end -local timer = nil local function create_poll_schedule(device) -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy -- Eve Energy generally report changes every 10 or 17 minutes - timer = device.thread:call_on_schedule(TIMER_REPEAT, function() + local timer = device.thread:call_on_schedule(TIMER_REPEAT, function() requestData(device) end, "polling_schedule_timer") + + device:set_field(RECURRING_POLL_TIMER, timer) end @@ -154,8 +156,10 @@ local function device_added(driver, device) end local function device_removed(driver, device) - if timer ~= nil then - device.thread:cancel_timer(timer) + local poll_timer = device:get_field(RECURRING_POLL_TIMER) + if poll_timer ~= nil then + device.thread:cancel_timer(poll_timer) + device:set_field(RECURRING_POLL_TIMER, nil) end end From 395cb7cbba29b847ad20e870b2358115d75c3ce0 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 16:10:46 +0200 Subject: [PATCH 06/22] Don't override the on_off_attr_handler since it is equivalent to the one in the base driver --- .../SmartThings/matter-switch/src/eve-energy/init.lua | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index f9b3793738..4b3daf07bb 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -176,14 +176,6 @@ local function matter_handler(driver, device, response_block) log.info(string.format("Fallback handler for %s", response_block)) end -local function on_off_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) - end -end - local function watt_attr_handler(driver, device, ib, zb_rx) if ib.data.value then local wattValue = ib.data.value @@ -208,9 +200,6 @@ local eve_energy_handler = { }, matter_handlers = { attr = { - [clusters.OnOff.ID] = { - [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, - }, [PRIVATE_CLUSTER_ID] = { [PRIVATE_ATTR_ID_WATT] = watt_attr_handler, [PRIVATE_ATTR_ID_WATT_ACCUMULATED] = watt_accumulated_attr_handler From 7de11f21397ca324709b469687bccc7ae8f2f4a7 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 17:37:39 +0200 Subject: [PATCH 07/22] Rename the profile to power-energy-powerConsumption --- .../{eve-energy.yml => power-energy-powerConsumption.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename drivers/SmartThings/matter-switch/profiles/{eve-energy.yml => power-energy-powerConsumption.yml} (100%) diff --git a/drivers/SmartThings/matter-switch/profiles/eve-energy.yml b/drivers/SmartThings/matter-switch/profiles/power-energy-powerConsumption.yml similarity index 100% rename from drivers/SmartThings/matter-switch/profiles/eve-energy.yml rename to drivers/SmartThings/matter-switch/profiles/power-energy-powerConsumption.yml From 860f112738cb6a58e8cbec242bac87fb621d70ae Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 17:45:01 +0200 Subject: [PATCH 08/22] Add initial basic tests --- .../src/test/test_eve_energy.lua | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua new file mode 100644 index 0000000000..3edf52c50e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -0,0 +1,115 @@ +-- 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. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local clusters = require "st.matter.clusters" + +local PRIVATE_ATTR_ID_WATT = 0x130A000A +local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("power-energy-powerConsumption.yml"), + manufacturer_info = { + vendor_id = 0x130A, + product_id = 0x0050, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1, -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, --u32 bitmap + } + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + test.socket.matter:__set_channel_ordering("relaxed") + 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.mock_device.add_test_device(mock_device) + +end +test.set_test_init_function(test_init) + +test.register_message_test( + "On command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Off command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "off", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, 1) + } + } + } +) + +test.run_registered_tests() From 43316570ab4a113ab174a330e11ee2c1d02017cb Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 17:46:20 +0200 Subject: [PATCH 09/22] Add missing copyright --- .../matter-switch/src/eve-energy/init.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 4b3daf07bb..c785173193 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -1,3 +1,17 @@ +-- Copyright 2023 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. + ------------------------------------------------------------------------------------- -- Definitions ------------------------------------------------------------------------------------- From c62a6a9a6baa8e661c794bed43b4bb5db47f39e7 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 31 Aug 2023 17:51:28 +0200 Subject: [PATCH 10/22] Remove the matter_handler fallback which is identical to the base driver --- drivers/SmartThings/matter-switch/src/eve-energy/init.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index c785173193..1833d20806 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -186,10 +186,6 @@ end -- Eve Energy Handler ------------------------------------------------------------------------------------- -local function matter_handler(driver, device, response_block) - log.info(string.format("Fallback handler for %s", response_block)) -end - local function watt_attr_handler(driver, device, ib, zb_rx) if ib.data.value then local wattValue = ib.data.value @@ -219,7 +215,6 @@ local eve_energy_handler = { [PRIVATE_ATTR_ID_WATT_ACCUMULATED] = watt_accumulated_attr_handler } }, - fallback = matter_handler, }, capability_handlers = { [capabilities.refresh.ID] = { From e95b20b609cb1d16a0c5ff908808a6c4d6e9e3d3 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 09:50:51 +0200 Subject: [PATCH 11/22] Fix formatting --- .../src/test/test_eve_energy.lua | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 3edf52c50e..ebdd02a4bc 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -31,7 +31,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = 0, clusters = { - {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + { cluster_id = clusters.Basic.ID, cluster_type = "SERVER" }, }, device_types = { device_type_id = 0x0016, device_type_revision = 1, -- RootNode @@ -62,32 +62,31 @@ local function test_init() subscribe_request:merge(cluster:subscribe(mock_device)) end end - test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.socket.matter:__expect_send({ mock_device.id, subscribe_request }) test.mock_device.add_test_device(mock_device) - end test.set_test_init_function(test_init) test.register_message_test( - "On command should send the appropriate commands", - { - { - channel = "capability", - direction = "receive", - message = { - mock_device.id, - { capability = "switch", component = "main", command = "on", args = { } } - } - }, - { - channel = "matter", - direction = "send", - message = { - mock_device.id, - clusters.OnOff.server.commands.On(mock_device, 1) - } - } - } + "On command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 1) + } + } + } ) test.register_message_test( @@ -98,7 +97,7 @@ test.register_message_test( direction = "receive", message = { mock_device.id, - { capability = "switch", component = "main", command = "off", args = { } } + { capability = "switch", component = "main", command = "off", args = {} } } }, { From ce13ff4e8d662b04cb1d00336e6834271ba80eda Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 09:51:16 +0200 Subject: [PATCH 12/22] Add unit test to chech that the power meter and energy meter are properly set up when the device is added --- .../matter-switch/src/test/test_eve_energy.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index ebdd02a4bc..b0ed339f9d 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -111,4 +111,19 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Check the power and energy meter when the device is added", function() + test.socket.matter:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + ) + end +) + test.run_registered_tests() From d2b3c73d4077c89b60cdeb71392580000542ec5f Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 11:41:01 +0200 Subject: [PATCH 13/22] Add unit test to check that the timer created in create_poll_schedule triggers the function requestData(). This function will read the standard and custom attributes from the device. --- .../src/test/test_eve_energy.lua | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index b0ed339f9d..ae8ebf81ec 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -17,7 +17,9 @@ local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" +local cluster_base = require "st.matter.cluster_base" +local PRIVATE_CLUSTER_ID = 0x130AFC01 local PRIVATE_ATTR_ID_WATT = 0x130A000A local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B @@ -45,6 +47,12 @@ local mock_device = test.mock_device.build_test_matter_device({ cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, --u32 bitmap + }, + { + cluster_id = PRIVATE_CLUSTER_ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, --u32 bitmap } } } @@ -123,7 +131,44 @@ test.register_coroutine_test( test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) ) + + test.wait_for_events() end ) + +test.register_coroutine_test( + "Check that the timer created in create_poll_schedule properly reads the device in requestData", + function() + test.mock_time.advance_time(60000) -- Ensure that the timer created in create_poll_schedule triggers + test.socket.matter:__set_channel_ordering("relaxed") + + test.socket.matter:__expect_send({ mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device) }) + test.socket.matter:__expect_send({ mock_device.id, + cluster_base.read(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT, nil) }) + test.socket.matter:__expect_send({ mock_device.id, + cluster_base.read(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil) }) + + test.wait_for_events() + end, + { + test_init = function() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + } + test.socket.matter:__set_channel_ordering("relaxed") + 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.mock_device.add_test_device(mock_device) + + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + test.run_registered_tests() From 4c60b6d754f2f1fce59550e2b4916f7a1a546b28 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 12:14:12 +0200 Subject: [PATCH 14/22] Unit test to check the refresh command --- .../matter-switch/src/test/test_eve_energy.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index ae8ebf81ec..261ce40b82 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -171,4 +171,22 @@ test.register_coroutine_test( } ) +test.register_coroutine_test( + "Check the refresh command", function() + test.socket.capability:__queue_receive( + { + mock_device.id, + { capability = capabilities.refresh.ID, command = capabilities.refresh.commands.refresh.NAME, args = {} }, + } + ) + + test.socket.matter:__expect_send({ mock_device.id, clusters.OnOff.attributes.OnOff:read(mock_device) }) + test.socket.matter:__expect_send({ mock_device.id, + cluster_base.read(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT, nil) }) + test.socket.matter:__expect_send({ mock_device.id, + cluster_base.read(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_WATT_ACCUMULATED, nil) }) + test.wait_for_events() + end +) + test.run_registered_tests() From 81978bb2da236c917f60953afa34647847f1fc3b Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 14:51:15 +0200 Subject: [PATCH 15/22] Add a unit test when reporting custom Watt data --- .../src/test/test_eve_energy.lua | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 261ce40b82..a24197acf3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -15,6 +15,7 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" +local data_types = require "st.matter.data_types" local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" @@ -189,4 +190,28 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Report with the custom Watt attribute", function() + local data = data_types.validate_or_build_type(50, data_types.Uint16, "watt") + test.socket.matter:__queue_receive( + { + mock_device.id, + cluster_base.build_test_report_data( + mock_device, + 0x01, + PRIVATE_CLUSTER_ID, + PRIVATE_ATTR_ID_WATT, + data + ) + } + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 50, unit = "W" })) + ) + + test.wait_for_events() + end +) + test.run_registered_tests() From 7fca5febb173a1c92a25717eac565fb7d08f3803 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 14:57:16 +0200 Subject: [PATCH 16/22] Add a unit test when reporting custom Watt accumulated data --- .../src/test/test_eve_energy.lua | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index a24197acf3..66c0196216 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -214,4 +214,28 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Report with the custom Watt accumulated attribute", function() + local data = data_types.validate_or_build_type(50, data_types.Uint16, "watt accumulated") + test.socket.matter:__queue_receive( + { + mock_device.id, + cluster_base.build_test_report_data( + mock_device, + 0x01, + PRIVATE_CLUSTER_ID, + PRIVATE_ATTR_ID_WATT_ACCUMULATED, + data + ) + } + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 50000, unit = "Wh" })) + ) + + test.wait_for_events() + end +) + test.run_registered_tests() From 984709cac74676cd1d80a2f47c58636213ac78c5 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Fri, 1 Sep 2023 15:09:43 +0200 Subject: [PATCH 17/22] Unit test for a report with the custom Watt accumulated attribute sent after 10 minutes --- .../src/test/test_eve_energy.lua | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 66c0196216..c4f8a0ca09 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -238,4 +238,41 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Report with the custom Watt accumulated attribute after 10 minutes", function() + local currentTime = 60000 + test.mock_time.advance_time(currentTime) + + local data = data_types.validate_or_build_type(50, data_types.Uint16, "watt accumulated") + test.socket.matter:__queue_receive( + { + mock_device.id, + cluster_base.build_test_report_data( + mock_device, + 0x01, + PRIVATE_CLUSTER_ID, + PRIVATE_ATTR_ID_WATT_ACCUMULATED, + data + ) + } + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 50000, unit = "Wh" })) + ) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 50000, + deltaEnergy = 0.0, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T16:39:59Z" + })) + ) + + test.wait_for_events() + end +) + test.run_registered_tests() From 6894453f9c246cfb5bd43acec7a76544bd9454b3 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Wed, 6 Sep 2023 09:24:46 +0200 Subject: [PATCH 18/22] Set the correct creation year in the copyright --- drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index c4f8a0ca09..5a4a4ca8e3 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -1,4 +1,4 @@ --- Copyright 2022 SmartThings +-- Copyright 2023 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. From f39feaf4cceef3eb5b6d5a955e295c3d570e6adf Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Mon, 11 Sep 2023 09:33:45 +0200 Subject: [PATCH 19/22] Only send the powerConsumptionReport event every 15 minutes instead of every 10 minutes --- drivers/SmartThings/matter-switch/src/eve-energy/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 1833d20806..9579c45107 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -31,7 +31,7 @@ local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B local LAST_REPORT_TIME = "LAST_REPORT_TIME" local RECURRING_POLL_TIMER = "RECURRING_POLL_TIMER" local TIMER_REPEAT = (1 * 60) -- Run the timer each minute -local REPORT_TIMEOUT = (10 * 60) -- Report the value each 10 minutes +local REPORT_TIMEOUT = (15 * 60) -- Report the value each 15 minutes ------------------------------------------------------------------------------------- @@ -56,7 +56,7 @@ local function updateEnergyMeter(device, totalConsumptionWh) -- Report the energy consumed device:emit_event(capabilities.energyMeter.energy({ value = totalConsumptionWh, unit = "Wh" })) - -- Only send powerConsumptionReport every 10 minutes + -- Only send powerConsumptionReport every couple of minutes (REPORT_TIMEOUT) local current_time = os.time() local last_time = device:get_field(LAST_REPORT_TIME) or 0 local next_time = last_time + REPORT_TIMEOUT From d21f0e1ff8f5ee9fb151ca7e3c331a9f3270b230 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 21 Sep 2023 14:05:40 +0200 Subject: [PATCH 20/22] Eve Energy: Reset the consumption when using the SmartThings command to reset the energy meter --- .../matter-switch/src/eve-energy/init.lua | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 9579c45107..56c3db7c24 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -21,12 +21,14 @@ local log = require "log" local clusters = require "st.matter.clusters" local cluster_base = require "st.matter.cluster_base" local utils = require "st.utils" +local data_types = require "st.matter.data_types" local EVE_MANUFACTURER_ID = 0x130A local PRIVATE_CLUSTER_ID = 0x130AFC01 local PRIVATE_ATTR_ID_WATT = 0x130A000A local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B +local PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT = 0x130A000E local LAST_REPORT_TIME = "LAST_REPORT_TIME" local RECURRING_POLL_TIMER = "RECURRING_POLL_TIMER" @@ -181,6 +183,30 @@ local function handle_refresh(self, device) requestData(device) end +local function handle_resetEnergyMeter(self, device) + -- 978307200 is the number of seconds from 1 January 1970 to 1 January 2001 + local current_time = os.time() + local current_time_2001 = current_time - 978307200 + + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local startTime = iso8061Timestamp(last_time) + local endTime = iso8061Timestamp(current_time - 1) + + -- Reset the consumption on the device + local data = data_types.validate_or_build_type(current_time_2001, data_types.Uint32) + device:send(cluster_base.write(device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT, nil, + data)) + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + start = startTime, + ["end"] = endTime, + deltaEnergy = 0, + energy = 0 + })) +end ------------------------------------------------------------------------------------- -- Eve Energy Handler @@ -220,6 +246,9 @@ local eve_energy_handler = { [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = handle_refresh, }, + [capabilities.energyMeter.ID] = { + [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = handle_resetEnergyMeter, + }, }, supported_capabilities = { capabilities.switch, From e90b65ce7097debd7de34dc36f86a56c8438e255 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 21 Sep 2023 14:54:24 +0200 Subject: [PATCH 21/22] - Set LAST_REPORT_TIME after reading it - Add safety check in case os.time() returns a value before 2001 which could be the cases in unit tests --- drivers/SmartThings/matter-switch/src/eve-energy/init.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua index 56c3db7c24..196707fc61 100644 --- a/drivers/SmartThings/matter-switch/src/eve-energy/init.lua +++ b/drivers/SmartThings/matter-switch/src/eve-energy/init.lua @@ -187,8 +187,9 @@ local function handle_resetEnergyMeter(self, device) -- 978307200 is the number of seconds from 1 January 1970 to 1 January 2001 local current_time = os.time() local current_time_2001 = current_time - 978307200 - - device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + if current_time_2001 < 0 then + current_time_2001 = 0 + end local last_time = device:get_field(LAST_REPORT_TIME) or 0 local startTime = iso8061Timestamp(last_time) @@ -206,6 +207,8 @@ local function handle_resetEnergyMeter(self, device) deltaEnergy = 0, energy = 0 })) + + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) end ------------------------------------------------------------------------------------- From c80f08e24322ffd71f31a18849115d67b83083f7 Mon Sep 17 00:00:00 2001 From: Alexandre Colucci Date: Thu, 21 Sep 2023 14:54:42 +0200 Subject: [PATCH 22/22] Add unit tests for the reset command --- .../src/test/test_eve_energy.lua | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua index 5a4a4ca8e3..2f3f6a276b 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_eve_energy.lua @@ -23,6 +23,7 @@ local cluster_base = require "st.matter.cluster_base" local PRIVATE_CLUSTER_ID = 0x130AFC01 local PRIVATE_ATTR_ID_WATT = 0x130A000A local PRIVATE_ATTR_ID_WATT_ACCUMULATED = 0x130A000B +local PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT = 0x130A000E local mock_device = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("power-energy-powerConsumption.yml"), @@ -275,4 +276,39 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Check the reset command", function() + local timeDiff = 1 + local currentTime = 978307200 + timeDiff -- 1 January 2001 + test.mock_time.advance_time(currentTime) + + test.socket.capability:__queue_receive( + { + mock_device.id, + { + capability = capabilities.energyMeter.ID, + command = capabilities.energyMeter.commands.resetEnergyMeter.NAME, + args = {} + }, + } + ) + + local data = data_types.validate_or_build_type(timeDiff, data_types.Uint32) + test.socket.matter:__expect_send({ mock_device.id, + cluster_base.write(mock_device, 0x01, PRIVATE_CLUSTER_ID, PRIVATE_ATTR_ID_ACCUMULATED_CONTROL_POINT, nil, data) }) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 0, + deltaEnergy = 0, + start = "1970-01-01T00:00:00Z", + ["end"] = "2001-01-01T00:00:00Z" + })) + ) + + test.wait_for_events() + end +) + test.run_registered_tests()