diff --git a/.gitignore b/.gitignore index 72da442d81..0c358e0f68 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ tools/coverage_output_html/ tools/__pycache__/ .DS_Store .venv/ + diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 8765be8aeb..333301197b 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -1730,6 +1730,11 @@ zigbeeManufacturer: manufacturer: LEDVANCE model: RT TW deviceProfileName: color-temp-bulb + - id: "LEDVANCE/PLUG COMPACT EU EM T" + deviceLabel: SMART ZIGBEE COMPACT OUTDOOR PLUG EU + manufacturer: LEDVANCE + model: PLUG COMPACT EU EM T + deviceProfileName: switch-power-energy - id: "OSRAM/LIGHTIFY Edge-lit flushmount" deviceLabel: SYLVANIA Light manufacturer: OSRAM diff --git a/drivers/SmartThings/zigbee-switch/src/simple-metering-config/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/can_handle.lua new file mode 100644 index 0000000000..85f3725f2f --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/can_handle.lua @@ -0,0 +1,13 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return function(opts, driver, device, ...) + local FINGERPRINTS = require("simple-metering-config.fingerprints") + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("simple-metering-config") + return true, subdriver + end + end + return false +end \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/simple-metering-config/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/fingerprints.lua new file mode 100644 index 0000000000..8c4730e1df --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/fingerprints.lua @@ -0,0 +1,6 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +return { + { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" } +} \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/simple-metering-config/init.lua b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/init.lua new file mode 100644 index 0000000000..2861e4bbc6 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/init.lua @@ -0,0 +1,55 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local zigbee_constants = require "st.zigbee.constants" +local SimpleMetering = require "st.zigbee.cluster".clusters.SimpleMetering + +local function energy_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + + if type(raw_value) ~= "number" or raw_value < 0 then + return + end + + local divisor = device:get_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY) or 100 + local multiplier = device:get_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + + if divisor == 0 then + return + end + + local calculated_value = (raw_value * multiplier) / divisor + + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + capabilities.energyMeter.energy({ value = calculated_value, unit = "kWh" }) + ) +end + +local function device_init(driver, device) + -- Set default multiplier and divisor values as suggested by SmartThings + device:set_field(zigbee_constants.SIMPLE_METERING_MULTIPLIER_KEY, 1, {persist = true}) + device:set_field(zigbee_constants.SIMPLE_METERING_DIVISOR_KEY, 100, {persist = true}) +end + +local simple_metering_config_subdriver = { + NAME = "Simple Metering Config", + supported_capabilities = { + capabilities.energyMeter, + capabilities.powerMeter + }, + zigbee_handlers = { + attr = { + [SimpleMetering.ID] = { + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler + } + } + }, + lifecycle_handlers = { + init = device_init + }, + can_handle = require("simple-metering-config.can_handle") +} + +return simple_metering_config_subdriver \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/simple-metering-config/test/test_simple_metering_config.lua b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/test/test_simple_metering_config.lua new file mode 100644 index 0000000000..bb3cde38c4 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/simple-metering-config/test/test_simple_metering_config.lua @@ -0,0 +1,71 @@ +-- Copyright 2026 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local clusters = require "st.zigbee.zcl.clusters" +local SimpleMetering = clusters.SimpleMetering +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LEDVANCE", + model = "PLUG COMPACT EU EM T", + server_clusters = { 0x0006, 0x0702 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Energy meter report should be handled with default multiplier and divisor", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 1000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 10, unit = "kWh" })) + } + } +) + +test.register_message_test( + "Energy meter report with zero value should be ignored", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 0) } + } + } +) + +test.register_message_test( + "Energy meter report with negative value should be ignored", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, -100) } + } + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua index 5dcf24ca74..71b7007c68 100644 --- a/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua +++ b/drivers/SmartThings/zigbee-switch/src/sub_drivers.lua @@ -14,6 +14,7 @@ return { lazy_load_if_possible("sinope"), lazy_load_if_possible("sinope-dimmer"), lazy_load_if_possible("zigbee-dimmer-power-energy"), + lazy_load_if_possible("simple-metering-config"), lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), lazy_load_if_possible("jasco"), lazy_load_if_possible("multi-switch-no-master"),