From 13fa587c2dda58af5d4ceac745835264970ebef6 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 16 Sep 2024 15:40:38 -0700 Subject: [PATCH 1/9] CHAD-13777 Allow virtual device events to not always be a state change Adds a new certified preference 'forceStateChange' that allows users to select whether they want their virtual device events to always be a state change or not. --- .../profiles/virtual-dimmer-switch.yml | 3 +++ .../profiles/virtual-dimmer.yml | 3 +++ .../profiles/virtual-switch.yml | 3 +++ .../SmartThings/virtual-switch/src/init.lua | 18 +++++++++------ .../src/test/test_virtual_switch.lua | 23 ++++++++++++++++++- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer-switch.yml b/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer-switch.yml index b62e0ae1e6..3490d307a2 100644 --- a/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer-switch.yml +++ b/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer-switch.yml @@ -8,3 +8,6 @@ components: version: 1 categories: - name: Switch +preferences: + - preferenceId: certifiedpreferences.forceStateChange + explicit: true diff --git a/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer.yml b/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer.yml index 2d5ebfb578..4784e93bb0 100644 --- a/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer.yml +++ b/drivers/SmartThings/virtual-switch/profiles/virtual-dimmer.yml @@ -6,3 +6,6 @@ components: version: 1 categories: - name: Switch +preferences: + - preferenceId: certifiedpreferences.forceStateChange + explicit: true diff --git a/drivers/SmartThings/virtual-switch/profiles/virtual-switch.yml b/drivers/SmartThings/virtual-switch/profiles/virtual-switch.yml index 4347fde8d0..f6796dd220 100644 --- a/drivers/SmartThings/virtual-switch/profiles/virtual-switch.yml +++ b/drivers/SmartThings/virtual-switch/profiles/virtual-switch.yml @@ -6,3 +6,6 @@ components: version: 1 categories: - name: Switch +preferences: + - preferenceId: certifiedpreferences.forceStateChange + explicit: true diff --git a/drivers/SmartThings/virtual-switch/src/init.lua b/drivers/SmartThings/virtual-switch/src/init.lua index c140b4eb3f..116427a6ad 100644 --- a/drivers/SmartThings/virtual-switch/src/init.lua +++ b/drivers/SmartThings/virtual-switch/src/init.lua @@ -1,26 +1,30 @@ local capabilities = require "st.capabilities" local Driver = require "st.driver" -local additional_fields = { - state_change = true -} +local function force_state_change(device) + if device.preferences["certifiedpreferences.forceStateChange"] then + return {state_change = true} + else + return nil + end +end local function handle_set_level(driver, device, command) if (command.args.level == 0) then - device:emit_event(capabilities.switch.switch.off(additional_fields)) + device:emit_event(capabilities.switch.switch.off(force_state_change(device))) else - device:emit_event(capabilities.switchLevel.level(command.args.level, additional_fields)) + device:emit_event(capabilities.switchLevel.level(command.args.level, force_state_change(device))) device:emit_event(capabilities.switch.switch.on()) end end local function handle_on(driver, device, command) - device:emit_event(capabilities.switch.switch.on(additional_fields)) + device:emit_event(capabilities.switch.switch.on(force_state_change(device))) end local function handle_off(driver, device, command) - device:emit_event(capabilities.switch.switch.off(additional_fields)) + device:emit_event(capabilities.switch.switch.off(force_state_change(device))) end local virtual_driver = Driver("virtual-switch", { diff --git a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua index 498c716f59..a42dfc4a1f 100644 --- a/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua +++ b/drivers/SmartThings/virtual-switch/src/test/test_virtual_switch.lua @@ -4,7 +4,10 @@ local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" local mock_simple_device = test.mock_device.build_test_generic_device( - { profile = t_utils.get_profile_definition("virtual-dimmer-switch.yml") } + { + profile = t_utils.get_profile_definition("virtual-dimmer-switch.yml"), + preferences = { ["certifiedpreferences.forceStateChange"] = true }, + } ) local function test_init() @@ -105,4 +108,22 @@ test.register_message_test( } ) +test.register_coroutine_test( + "State change should not be true when forceStateChange is false", + function() + test.socket.device_lifecycle():__queue_receive({mock_simple_device.id, "init"}) + test.socket.device_lifecycle():__queue_receive(mock_simple_device:generate_info_changed( + { + preferences = { + ["certifiedpreferences.forceStateChange"] = false + } + } + )) + test.wait_for_events() + test.socket.capability:__queue_receive({ mock_simple_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.switch.switch.on())) + end +) + test.run_registered_tests() From f60a867bc19a1334684ef476ce37cc8944fdceee Mon Sep 17 00:00:00 2001 From: Steven Green Date: Mon, 23 Sep 2024 16:13:50 -0700 Subject: [PATCH 2/9] Yale Zigbee Locks: Use MASTER_CODE user type When setting the code at index 0 for yale locks, the MASTER_CODE user type should be sent. --- .../zigbee-lock/src/test/test_zigbee_yale.lua | 47 +++++++++++++++++++ .../SmartThings/zigbee-lock/src/yale/init.lua | 3 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index c31ab48eb5..6635b36477 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -147,6 +147,53 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Setting the master code should result in the correct user type being used", + function() + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 0, "1234", "test" } } }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 0, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.MASTER_USER, + "1234" + ) + } + ) + test.wait_for_events() + + test.mock_time.advance_time(4) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.GetPINCode(mock_device, 0) + } + ) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x00, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.MASTER_USER, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("0 set", { data = { codeName = "test"}, state_change = true })) + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["0"] = "test"}), { visibility = { displayed = false }}))) + end +) + test.register_message_test( "Pin response reporting should be handled when the Lock User status is disabled", diff --git a/drivers/SmartThings/zigbee-lock/src/yale/init.lua b/drivers/SmartThings/zigbee-lock/src/yale/init.lua index e72d7dae3a..73e984036e 100644 --- a/drivers/SmartThings/zigbee-lock/src/yale/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/yale/init.lua @@ -51,10 +51,11 @@ local set_code = function(driver, device, command) args = {command.args.codeSlot, command.args.codeName} }) else + local user_type = command.args.codeSlot == 0 and UserTypeEnum.MASTER_USER or UserTypeEnum.UNRESTRICTED device:send(LockCluster.server.commands.SetPINCode(device, command.args.codeSlot, UserStatusEnum.OCCUPIED_ENABLED, - UserTypeEnum.UNRESTRICTED, + user_type, command.args.codePIN) ) if (command.args.codeName ~= nil) then From 17d018c7ad4818628fa65301f35b705dc4f3bd32 Mon Sep 17 00:00:00 2001 From: Alissa Dornbos <79465613+lelandblue@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:06:11 -0400 Subject: [PATCH 3/9] new-device-LEDvance-34c (#1556) --- drivers/SmartThings/matter-switch/fingerprints.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index da643d39a9..72760a5be9 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -180,6 +180,12 @@ matterManufacturer: vendorId: 0x1339 productId: 0x006B deviceProfileName: light-color-level-2000K-7000K +#Ledvance + - id: "4489/844" + deviceLabel: Matter Filament RGBW + vendorId: 0x1189 + productId: 0x034C + deviceProfileName: light-color-level - id: "4921/64" deviceLabel: Cync Indoor Plug vendorId: 0x1339 From 4ca882cfe04eeac136ff031007a22a0180cd41f7 Mon Sep 17 00:00:00 2001 From: Steven Green Date: Fri, 27 Sep 2024 11:30:13 -0700 Subject: [PATCH 4/9] CHAD-14071 Zigbee: Add generic fingerprint for ZLL color temp light --- .../zigbee-switch/fingerprints.yml | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 61ddca27b1..49c6761c75 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2253,12 +2253,12 @@ zigbeeManufacturer: deviceProfileName: on-off-level # Wall Hero - id: "WALL HERO/ACL-401S4I" - deviceLabel: 四位智能开关面板 1 + deviceLabel: 四位智能开关面板 1 manufacturer: WALL HERO model: ACL-401S4I deviceProfileName: wallhero - id: "WALL HERO/ACL-401S8I" - deviceLabel: 八位智能开关/场景面板 1 + deviceLabel: 八位智能开关/场景面板 1 manufacturer: WALL HERO model: ACL-401S8I deviceProfileName: wallhero @@ -2266,17 +2266,17 @@ zigbeeManufacturer: deviceLabel: 智能墙面五孔插座 manufacturer: WALL HERO model: ACL-401ON - deviceProfileName: wallhero + deviceProfileName: wallhero - id: "WALL HERO/ACL-401S1I" deviceLabel: 一位智能开关面板 manufacturer: WALL HERO model: ACL-401S1I - deviceProfileName: wallhero + deviceProfileName: wallhero - id: "WALL HERO/ACL-401S3I" deviceLabel: 三位智能开关面板 1 manufacturer: WALL HERO model: ACL-401S3I - deviceProfileName: wallhero + deviceProfileName: wallhero - id: "WALL HERO/ACL-401S2I" deviceLabel: 二位智能开关面板 1 manufacturer: WALL HERO @@ -2315,6 +2315,16 @@ zigbeeGeneric: - 0x0008 #Level - 0x0300 #Color Control deviceProfileName: rgbw-bulb + - id: "genericZLLColorTempBulb" + deviceLabel: Zigbee Light + clusters: + server: + - 0x0006 #OnOff + - 0x0008 #Level + - 0x0300 #Color Control + deviceIdentifiers: + - 0x0220 + deviceProfileName: color-temp-bulb-2700K-6500K - id: "genericMeteredSwitch" deviceLabel: Zigbee Switch clusters: From 7821a5d168d89fb449391a584a3566c605389265 Mon Sep 17 00:00:00 2001 From: Harrison Carter <137556605+hcarter-775@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:23:39 -0500 Subject: [PATCH 5/9] Matter 1.3 Driver Release (#1640) Initial Matter 1.3 Device Type Support --------- Co-authored-by: Nick DeBoom Co-authored-by: Hunsup Jung --- .../matter-appliance/fingerprints.yml | 26 + .../cook-surface-one-cook-surface-two-tl.yml | 24 + .../cook-surface-one-cook-surface-two.yml | 22 + ...ook-surface-one-tl-cook-surface-two-tl.yml | 26 + .../cook-surface-one-tl-cook-surface-two.yml | 24 + .../profiles/cook-surface-one-tl.yml | 19 + .../profiles/cook-surface-one.yml | 17 + .../matter-appliance/profiles/cook-top.yml | 13 + .../profiles/extractor-hood-ac-wind.yml | 26 + .../profiles/extractor-hood-ac.yml | 24 + .../profiles/extractor-hood-hepa-ac-wind.yml | 35 + .../profiles/extractor-hood-hepa-ac.yml | 33 + .../profiles/extractor-hood-hepa-wind.yml | 26 + .../profiles/extractor-hood-hepa.yml | 24 + .../profiles/extractor-hood-wind.yml | 16 + .../profiles/extractor-hood.yml | 14 + .../profiles/laundry-dryer-tl.yml | 21 + .../profiles/laundry-dryer-tn-tl.yml | 23 + .../profiles/laundry-dryer-tn.yml | 21 + .../profiles/laundry-dryer.yml | 19 + .../profiles/microwave-oven.yml | 19 + ...ook-surface-one-tl-cook-surface-two-tl.yml | 50 ++ .../ActivatedCarbonFilterMonitoring/init.lua | 79 ++ .../server/attributes/AttributeList.lua | 126 ++++ .../server/attributes/ChangeIndication.lua | 116 +++ .../server/attributes/Condition.lua | 68 ++ .../server/attributes/init.lua | 24 + .../types/ChangeIndicationEnum.lua | 33 + .../types/Feature.lua | 98 +++ .../types/init.lua | 15 + .../src/HepaFilterMonitoring/init.lua | 79 ++ .../server/attributes/AttributeList.lua | 126 ++++ .../server/attributes/ChangeIndication.lua | 116 +++ .../server/attributes/Condition.lua | 68 ++ .../server/attributes/init.lua | 24 + .../types/ChangeIndicationEnum.lua | 33 + .../HepaFilterMonitoring/types/Feature.lua | 98 +++ .../src/HepaFilterMonitoring/types/init.lua | 15 + .../src/MicrowaveOvenControl/init.lua | 84 +++ .../server/attributes/AcceptedCommandList.lua | 74 ++ .../server/attributes/CookTime.lua | 68 ++ .../server/attributes/MaxCookTime.lua | 68 ++ .../server/attributes/init.lua | 23 + .../server/commands/AddMoreTime.lua | 97 +++ .../server/commands/SetCookingParameters.lua | 125 +++ .../server/commands/init.lua | 22 + .../MicrowaveOvenControl/types/Feature.lua | 97 +++ .../src/MicrowaveOvenControl/types/init.lua | 15 + .../src/MicrowaveOvenMode/init.lua | 68 ++ .../server/attributes/AcceptedCommandList.lua | 74 ++ .../server/attributes/CurrentMode.lua | 68 ++ .../server/attributes/SupportedModes.lua | 75 ++ .../server/attributes/init.lua | 23 + .../src/MicrowaveOvenMode/types/Feature.lua | 53 ++ .../types/ModeOptionStruct.lua | 85 +++ .../src/MicrowaveOvenMode/types/ModeTag.lua | 27 + .../MicrowaveOvenMode/types/ModeTagStruct.lua | 78 ++ .../src/MicrowaveOvenMode/types/init.lua | 17 + .../matter-appliance/src/OvenMode/init.lua | 80 ++ .../server/attributes/CurrentMode.lua | 67 ++ .../server/attributes/SupportedModes.lua | 74 ++ .../src/OvenMode/server/attributes/init.lua | 23 + .../OvenMode/server/commands/ChangeToMode.lua | 86 +++ .../src/OvenMode/server/commands/init.lua | 22 + .../src/OvenMode/types/Feature.lua | 54 ++ .../src/OvenMode/types/ModeOptionStruct.lua | 84 +++ .../src/OvenMode/types/ModeTag.lua | 48 ++ .../src/OvenMode/types/ModeTagStruct.lua | 77 ++ .../src/OvenMode/types/init.lua | 15 + .../src/embedded-cluster-utils.lua | 15 + .../SmartThings/matter-appliance/src/init.lua | 120 ++- .../src/matter-cook-top/init.lua | 227 ++++++ .../src/matter-dishwasher/init.lua | 2 + .../src/matter-extractor-hood/init.lua | 240 ++++++ .../init.lua | 19 +- .../src/matter-microwave-oven/init.lua | 275 +++++++ .../matter-appliance/src/matter-oven/init.lua | 286 +++++++ .../src/matter-refrigerator/init.lua | 4 +- .../src/test/test_cook_top.lua | 202 +++++ .../src/test/test_laundry_dryer.lua | 538 +++++++++++++ .../src/test/test_matter_extractor_hood.lua | 580 ++++++++++++++ .../src/test/test_microwave_oven.lua | 569 ++++++++++++++ .../matter-appliance/src/test/test_oven.lua | 528 +++++++++++++ drivers/SmartThings/matter-evse/config.yml | 6 + .../SmartThings/matter-evse/fingerprints.yml | 7 + .../evse-energy-meas-energy-mgmt-mode.yml | 27 + ...nergy-meas-power-meas-energy-mgmt-mode.yml | 32 + .../matter-evse/profiles/evse-energy-meas.yml | 22 + .../evse-power-meas-energy-mgmt-mode.yml | 30 + .../matter-evse/profiles/evse-power-meas.yml | 25 + .../SmartThings/matter-evse/profiles/evse.yml | 19 + .../src/DeviceEnergyManagementMode/init.lua | 86 +++ .../server/attributes/AcceptedCommandList.lua | 81 ++ .../server/attributes/AttributeList.lua | 81 ++ .../server/attributes/CurrentMode.lua | 67 ++ .../server/attributes/SupportedModes.lua | 74 ++ .../server/attributes/init.lua | 24 + .../server/commands/ChangeToMode.lua | 79 ++ .../server/commands/init.lua | 23 + .../types/Feature.lua | 54 ++ .../types/ModeOptionStruct.lua | 85 +++ .../types/ModeTag.lua | 33 + .../types/ModeTagStruct.lua | 77 ++ .../DeviceEnergyManagementMode/types/init.lua | 17 + .../src/ElectricalEnergyMeasurement/init.lua | 60 ++ .../server/attributes/AcceptedCommandList.lua | 80 ++ .../server/attributes/AttributeList.lua | 80 ++ .../attributes/CumulativeEnergyImported.lua | 78 ++ .../attributes/PeriodicEnergyImported.lua | 78 ++ .../server/attributes/init.lua | 24 + .../types/EnergyMeasurementStruct.lua | 93 +++ .../types/Feature.lua | 103 +++ .../types/MeasurementTypeEnum.lua | 51 ++ .../types/init.lua | 17 + .../src/ElectricalPowerMeasurement/init.lua | 95 +++ .../server/attributes/AcceptedCommandList.lua | 79 ++ .../server/attributes/ActivePower.lua | 80 ++ .../server/attributes/ApparentPower.lua | 78 ++ .../server/attributes/AttributeList.lua | 80 ++ .../server/attributes/PowerMode.lua | 86 +++ .../server/attributes/init.lua | 24 + .../server/commands/init.lua | 23 + .../server/events/MeasurementPeriodRanges.lua | 98 +++ .../server/events/init.lua | 24 + .../types/Feature.lua | 138 ++++ .../types/HarmonicMeasurementStruct.lua | 73 ++ .../types/MeasurementAccuracyRangeStruct.lua | 115 +++ .../types/MeasurementAccuracyStruct.lua | 91 +++ .../types/MeasurementRangeStruct.lua | 126 ++++ .../types/MeasurementTypeEnum.lua | 51 ++ .../types/PowerModeEnum.lua | 27 + .../ElectricalPowerMeasurement/types/init.lua | 17 + .../matter-evse/src/EnergyEvse/init.lua | 162 ++++ .../server/attributes/AcceptedCommandList.lua | 79 ++ .../attributes/ApproximateEVEfficiency.lua | 76 ++ .../server/attributes/AttributeList.lua | 81 ++ .../server/attributes/BatteryCapacity.lua | 79 ++ .../attributes/ChargingEnabledUntil.lua | 64 ++ .../server/attributes/CircuitCapacity.lua | 80 ++ .../attributes/DischargingEnabledUntil.lua | 64 ++ .../server/attributes/EventList.lua | 80 ++ .../server/attributes/FaultState.lua | 114 +++ .../attributes/MaximumChargeCurrent.lua | 80 ++ .../attributes/MaximumDischargeCurrent.lua | 80 ++ .../attributes/MinimumChargeCurrent.lua | 80 ++ .../attributes/NextChargeRequiredEnergy.lua | 78 ++ .../server/attributes/NextChargeStartTime.lua | 64 ++ .../server/attributes/NextChargeTargetSoC.lua | 64 ++ .../attributes/NextChargeTargetTime.lua | 64 ++ .../attributes/RandomizationDelayWindow.lua | 91 +++ .../server/attributes/SessionDuration.lua | 78 ++ .../attributes/SessionEnergyCharged.lua | 78 ++ .../attributes/SessionEnergyDischarged.lua | 78 ++ .../server/attributes/SessionID.lua | 65 ++ .../EnergyEvse/server/attributes/State.lua | 93 +++ .../server/attributes/StateOfCharge.lua | 64 ++ .../server/attributes/SupplyState.lua | 89 +++ .../attributes/UserMaximumChargeCurrent.lua | 93 +++ .../server/attributes/VehicleID.lua | 64 ++ .../src/EnergyEvse/server/attributes/init.lua | 24 + .../server/commands/ClearTargets.lua | 84 +++ .../EnergyEvse/server/commands/Disable.lua | 84 +++ .../server/commands/EnableCharging.lua | 106 +++ .../server/commands/EnableDischarging.lua | 99 +++ .../EnergyEvse/server/commands/GetTargets.lua | 72 ++ .../EnergyEvse/server/commands/SetTargets.lua | 93 +++ .../server/commands/StartDiagnostics.lua | 84 +++ .../src/EnergyEvse/server/commands/init.lua | 23 + .../EnergyEvse/server/events/EVConnected.lua | 97 +++ .../server/events/EVNotDetected.lua | 129 ++++ .../server/events/EnergyTransferStarted.lua | 114 +++ .../server/events/EnergyTransferStopped.lua | 124 +++ .../src/EnergyEvse/server/events/Fault.lua | 123 +++ .../src/EnergyEvse/server/events/Rfid.lua | 98 +++ .../src/EnergyEvse/server/events/init.lua | 24 + .../types/ChargingTargetScheduleStruct.lua | 71 ++ .../EnergyEvse/types/ChargingTargetStruct.lua | 79 ++ .../types/EnergyTransferStoppedReasonEnum.lua | 26 + .../src/EnergyEvse/types/FaultStateEnum.lua | 54 ++ .../src/EnergyEvse/types/Feature.lua | 138 ++++ .../src/EnergyEvse/types/StateEnum.lua | 34 + .../src/EnergyEvse/types/SupplyStateEnum.lua | 30 + .../types/TargetDayOfWeekBitmap.lua | 171 +++++ .../matter-evse/src/EnergyEvse/types/init.lua | 17 + .../matter-evse/src/EnergyEvseMode/init.lua | 96 +++ .../server/attributes/AcceptedCommandList.lua | 81 ++ .../server/attributes/AttributeList.lua | 81 ++ .../server/attributes/CurrentMode.lua | 64 ++ .../server/attributes/SupportedModes.lua | 74 ++ .../EnergyEvseMode/server/attributes/init.lua | 24 + .../server/commands/ChangeToMode.lua | 79 ++ .../EnergyEvseMode/server/commands/init.lua | 23 + .../src/EnergyEvseMode/types/Feature.lua | 56 ++ .../EnergyEvseMode/types/ModeOptionStruct.lua | 85 +++ .../src/EnergyEvseMode/types/ModeTag.lua | 30 + .../EnergyEvseMode/types/ModeTagStruct.lua | 77 ++ .../src/EnergyEvseMode/types/init.lua | 17 + .../src/embedded_cluster_utils.lua | 59 ++ drivers/SmartThings/matter-evse/src/init.lua | 699 +++++++++++++++++ .../matter-evse/src/test/test_evse.lua | 511 +++++++++++++ .../src/test/test_evse_energy_meas.lua | 216 ++++++ .../matter-sensor/fingerprints.yml | 15 + ...freeze-battery-fault-freezeSensitivity.yml | 25 + .../profiles/freeze-battery-fault.yml | 23 + .../freeze-battery-freezeSensitivity.yml | 23 + .../matter-sensor/profiles/freeze-battery.yml | 20 + .../freeze-fault-freezeSensitivity.yml | 23 + .../matter-sensor/profiles/freeze-fault.yml | 20 + ...ault-freezeSensitivity-leakSensitivity.yml | 27 + .../leak-battery-fault-leakSensitivity.yml | 19 + .../profiles/leak-battery-fault.yml | 16 + .../profiles/leak-battery-leakSensitivity.yml | 17 + .../matter-sensor/profiles/leak-battery.yml | 14 + .../profiles/leak-fault-leakSensitivity.yml | 17 + .../matter-sensor/profiles/leak-fault.yml | 14 + .../rain-battery-fault-rainSensitivity.yml | 19 + .../profiles/rain-battery-fault.yml | 16 + .../profiles/rain-battery-rainSensitivity.yml | 17 + .../matter-sensor/profiles/rain-battery.yml | 14 + .../profiles/rain-fault-rainSensitivity.yml | 17 + .../matter-sensor/profiles/rain-fault.yml | 14 + .../src/BooleanStateConfiguration/init.lua | 81 ++ .../attributes/CurrentSensitivityLevel.lua | 80 ++ .../attributes/DefaultSensitivityLevel.lua | 67 ++ .../server/attributes/SensorFault.lua | 67 ++ .../attributes/SupportedSensitivityLevels.lua | 67 ++ .../server/attributes/init.lua | 25 + .../types/Feature.lua | 119 +++ .../types/SensorFaultBitmap.lua | 44 ++ .../BooleanStateConfiguration/types/init.lua | 14 + .../src/embedded-cluster-utils.lua | 4 + .../SmartThings/matter-sensor/src/init.lua | 178 ++++- .../test/test_matter_freeze_leak_sensor.lua | 252 +++++++ .../src/test/test_matter_pressure_sensor.lua | 4 +- .../src/test/test_matter_rain_sensor.lua | 145 ++++ .../test/test_matter_sensor_featuremap.lua | 12 + .../src/test/test_matter_sensor_rpc.lua | 3 + .../matter-switch/fingerprints.yml | 11 + .../profiles/plug-energy-powerConsumption.yml | 16 + .../plug-power-energy-powerConsumption.yml | 18 + .../matter-switch/profiles/plug-power.yml | 14 + .../profiles/water-valve-level.yml | 18 + .../matter-switch/profiles/water-valve.yml | 12 + .../src/ElectricalEnergyMeasurement/init.lua | 67 ++ .../attributes/CumulativeEnergyExported.lua | 68 ++ .../attributes/PeriodicEnergyExported.lua | 68 ++ .../server/attributes/init.lua | 24 + .../types/EnergyMeasurementStruct.lua | 98 +++ .../types/Feature.lua | 116 +++ .../types/init.lua | 15 + .../src/ElectricalPowerMeasurement/init.lua | 94 +++ .../server/attributes/ActivePower.lua | 68 ++ .../server/attributes/init.lua | 24 + .../types/Feature.lua | 138 ++++ .../ElectricalPowerMeasurement/types/init.lua | 15 + .../src/ValveConfigurationAndControl/init.lua | 123 +++ .../server/attributes/CurrentLevel.lua | 67 ++ .../server/attributes/CurrentState.lua | 67 ++ .../server/attributes/init.lua | 23 + .../server/commands/Close.lua | 89 +++ .../server/commands/Open.lua | 103 +++ .../server/commands/init.lua | 22 + .../types/Feature.lua | 74 ++ .../types/ValveStateEnum.lua | 29 + .../types/init.lua | 14 + .../src/embedded-cluster-utils.lua | 53 ++ .../SmartThings/matter-switch/src/init.lua | 278 ++++++- .../src/test/test_electrical_sensor.lua | 711 ++++++++++++++++++ .../test/test_matter_switch_device_types.lua | 40 + .../src/test/test_matter_water_valve.lua | 235 ++++++ tools/config.luacov | 10 + 271 files changed, 20769 insertions(+), 71 deletions(-) create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/cook-top.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac-wind.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac-wind.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-wind.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood-wind.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/extractor-hood.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/laundry-dryer.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/microwave-oven.yml create mode 100644 drivers/SmartThings/matter-appliance/profiles/oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl.yml create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/Feature.lua create mode 100644 drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/Condition.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/Feature.lua create mode 100644 drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/CookTime.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/MaxCookTime.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/AddMoreTime.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/SetCookingParameters.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/Feature.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/CurrentMode.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/SupportedModes.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/Feature.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeOptionStruct.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTag.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTagStruct.lua create mode 100644 drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/CurrentMode.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/SupportedModes.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/ChangeToMode.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/types/Feature.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeOptionStruct.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTag.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTagStruct.lua create mode 100644 drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua rename drivers/SmartThings/matter-appliance/src/{matter-laundry-washer => matter-laundry}/init.lua (96%) create mode 100644 drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/matter-oven/init.lua create mode 100644 drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua create mode 100644 drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua create mode 100644 drivers/SmartThings/matter-appliance/src/test/test_matter_extractor_hood.lua create mode 100644 drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua create mode 100644 drivers/SmartThings/matter-appliance/src/test/test_oven.lua create mode 100644 drivers/SmartThings/matter-evse/config.yml create mode 100644 drivers/SmartThings/matter-evse/fingerprints.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml create mode 100644 drivers/SmartThings/matter-evse/profiles/evse.yml create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua create mode 100644 drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua create mode 100644 drivers/SmartThings/matter-evse/src/init.lua create mode 100644 drivers/SmartThings/matter-evse/src/test/test_evse.lua create mode 100644 drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault-freezeSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-battery-freezeSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-fault-freezeSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/freeze-leak-fault-freezeSensitivity-leakSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-battery-fault-leakSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-battery-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-battery-leakSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-fault-leakSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/leak-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-battery-fault-rainSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-battery-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-battery-rainSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-fault-rainSensitivity.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/rain-fault.yml create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua create mode 100644 drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-energy-powerConsumption.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-power-energy-powerConsumption.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/plug-power.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/water-valve-level.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/water-valve.yml create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/Feature.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/Feature.lua create mode 100644 drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentState.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Close.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Open.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/Feature.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/ValveStateEnum.lua create mode 100644 drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/init.lua create mode 100644 drivers/SmartThings/matter-switch/src/embedded-cluster-utils.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua create mode 100644 drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua diff --git a/drivers/SmartThings/matter-appliance/fingerprints.yml b/drivers/SmartThings/matter-appliance/fingerprints.yml index f487ddd381..891ce2619f 100644 --- a/drivers/SmartThings/matter-appliance/fingerprints.yml +++ b/drivers/SmartThings/matter-appliance/fingerprints.yml @@ -32,3 +32,29 @@ matterGeneric: deviceTypes: - id: 0x0075 deviceProfileName: dishwasher-tn-tl + - id: "matter/extractor-hood" + deviceLabel: Matter Extractor Hood + deviceTypes: + - id: 0x007A + deviceProfileName: extractor-hood + - id: "matter/laundry-dryer" + deviceLabel: Matter Laundry Dryer + deviceTypes: + - id: 0x007C + deviceProfileName: laundry-dryer + - id: "matter/cook-top" + deviceLabel: Matter Cook Top + deviceTypes: + - id: 0x0078 + deviceProfileName: cook-top + - id: "matter/microwave-oven" + deviceLabel: Matter Microwave Oven + deviceTypes: + - id: 0x0079 + deviceProfileName: microwave-oven + - id: "matter/oven" + deviceLabel: Matter Oven + deviceTypes: + - id: 0x007B + - id: 0x0071 + deviceProfileName: oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml new file mode 100644 index 0000000000..b36eccc624 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml @@ -0,0 +1,24 @@ +name: cook-surface-one-cook-surface-two-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml new file mode 100644 index 0000000000..83d6298bb1 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml @@ -0,0 +1,22 @@ +name: cook-surface-one-cook-surface-two +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml new file mode 100644 index 0000000000..4d73638a7d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml @@ -0,0 +1,26 @@ +name: cook-surface-one-tl-cook-surface-two-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml new file mode 100644 index 0000000000..4a6464030d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml @@ -0,0 +1,24 @@ +name: cook-surface-one-tl-cook-surface-two +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml new file mode 100644 index 0000000000..bb206ac8d3 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml @@ -0,0 +1,19 @@ +name: cook-surface-one-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml new file mode 100644 index 0000000000..44fee23b77 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml @@ -0,0 +1,17 @@ +name: cook-surface-one +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-top.yml b/drivers/SmartThings/matter-appliance/profiles/cook-top.yml new file mode 100644 index 0000000000..9ecb6d9566 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/cook-top.yml @@ -0,0 +1,13 @@ +name: cook-top +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop + \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac-wind.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac-wind.yml new file mode 100644 index 0000000000..34aadcb458 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac-wind.yml @@ -0,0 +1,26 @@ +name: extractor-hood-ac-wind +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: windMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: activatedCarbonFilter + label: Activated carbon filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac.yml new file mode 100644 index 0000000000..23f3aedfe5 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-ac.yml @@ -0,0 +1,24 @@ +name: extractor-hood-ac +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: activatedCarbonFilter + label: Activated carbon filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac-wind.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac-wind.yml new file mode 100644 index 0000000000..53a143569d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac-wind.yml @@ -0,0 +1,35 @@ +name: extractor-hood-hepa-ac-wind +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: windMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: hepaFilter + label: Hepa filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood +- id: activatedCarbonFilter + label: Activated carbon filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac.yml new file mode 100644 index 0000000000..a605ca2013 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-ac.yml @@ -0,0 +1,33 @@ +name: extractor-hood-hepa-ac +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: hepaFilter + label: Hepa filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood +- id: activatedCarbonFilter + label: Activated carbon filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-wind.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-wind.yml new file mode 100644 index 0000000000..af88b84ee2 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa-wind.yml @@ -0,0 +1,26 @@ +name: extractor-hood-hepa-wind +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: windMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: hepaFilter + label: Hepa filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa.yml new file mode 100644 index 0000000000..a69cb17507 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-hepa.yml @@ -0,0 +1,24 @@ +name: extractor-hood-hepa +components: +- id: main + label: Main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood +- id: hepaFilter + label: Hepa filter + capabilities: + - id: filterState + version: 1 + - id: filterStatus + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood-wind.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-wind.yml new file mode 100644 index 0000000000..074a20c78e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood-wind.yml @@ -0,0 +1,16 @@ +name: extractor-hood-wind +components: +- id: main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: windMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/extractor-hood.yml b/drivers/SmartThings/matter-appliance/profiles/extractor-hood.yml new file mode 100644 index 0000000000..c07813afcf --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/extractor-hood.yml @@ -0,0 +1,14 @@ +name: extractor-hood +components: +- id: main + capabilities: + - id: fanMode + version: 1 + - id: fanSpeedPercent + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: KitchenHood diff --git a/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tl.yml b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tl.yml new file mode 100644 index 0000000000..6edd6edc01 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tl.yml @@ -0,0 +1,21 @@ +name: laundry-dryer-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: mode + version: 1 + - id: operationalState + version: 1 + - id: temperatureLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Dryer +metadata: + mnmn: SmartThingsEdge + vid: generic-laundry-dryer diff --git a/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn-tl.yml b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn-tl.yml new file mode 100644 index 0000000000..ea54dfe097 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn-tl.yml @@ -0,0 +1,23 @@ +name: laundry-dryer-tn-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: mode + version: 1 + - id: temperatureSetpoint + version: 1 + - id: operationalState + version: 1 + - id: temperatureLevel + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Dryer +metadata: + mnmn: SmartThingsEdge + vid: generic-laundry-dryer diff --git a/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn.yml b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn.yml new file mode 100644 index 0000000000..8182eded36 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer-tn.yml @@ -0,0 +1,21 @@ +name: laundry-dryer-tn +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: mode + version: 1 + - id: temperatureSetpoint + version: 1 + - id: operationalState + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Dryer +metadata: + mnmn: SmartThingsEdge + vid: generic-laundry-dryer diff --git a/drivers/SmartThings/matter-appliance/profiles/laundry-dryer.yml b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer.yml new file mode 100644 index 0000000000..d04fe0f74b --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/laundry-dryer.yml @@ -0,0 +1,19 @@ +name: laundry-dryer +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: mode + version: 1 + - id: operationalState + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Dryer +metadata: + mnmn: SmartThingsEdge + vid: generic-laundry-dryer \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/profiles/microwave-oven.yml b/drivers/SmartThings/matter-appliance/profiles/microwave-oven.yml new file mode 100644 index 0000000000..5f15869b65 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/microwave-oven.yml @@ -0,0 +1,19 @@ +name: microwave-oven +components: +- id: main + capabilities: + - id: mode + version: 1 + - id: operationalState + version: 1 + - id: cookTime + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Microwave +metadata: + mnmn: SmartThingsEdge + vid: generic-microwave-oven diff --git a/drivers/SmartThings/matter-appliance/profiles/oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl.yml b/drivers/SmartThings/matter-appliance/profiles/oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl.yml new file mode 100644 index 0000000000..b5a17e129f --- /dev/null +++ b/drivers/SmartThings/matter-appliance/profiles/oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl.yml @@ -0,0 +1,50 @@ +name: oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl +components: +- id: main + capabilities: + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Oven +- id: tccOne + label: Oven Cabinet 1 + capabilities: + - id: mode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: temperatureSetpoint + version: 1 +- id: tccTwo + label: Oven Cabinet 2 + capabilities: + - id: mode + version: 1 + - id: temperatureMeasurement + version: 1 + - id: temperatureLevel + version: 1 +- id: cookTop + label: Cook Top + capabilities: + - id: switch + version: 1 +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-oven diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua new file mode 100644 index 0000000000..c7057f82e4 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/init.lua @@ -0,0 +1,79 @@ +local cluster_base = require "st.matter.cluster_base" +local ActivatedCarbonFilterMonitoringServerAttributes = require "ActivatedCarbonFilterMonitoring.server.attributes" +local ActivatedCarbonFilterMonitoringTypes = require "ActivatedCarbonFilterMonitoring.types" + +local ActivatedCarbonFilterMonitoring = {} + +ActivatedCarbonFilterMonitoring.ID = 0x0072 +ActivatedCarbonFilterMonitoring.NAME = "ActivatedCarbonFilterMonitoring" +ActivatedCarbonFilterMonitoring.server = {} +ActivatedCarbonFilterMonitoring.client = {} +ActivatedCarbonFilterMonitoring.server.attributes = ActivatedCarbonFilterMonitoringServerAttributes:set_parent_cluster(ActivatedCarbonFilterMonitoring) +ActivatedCarbonFilterMonitoring.types = ActivatedCarbonFilterMonitoringTypes + +function ActivatedCarbonFilterMonitoring:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "Condition", + [0x0001] = "DegradationDirection", + [0x0002] = "ChangeIndication", + [0x0003] = "InPlaceIndicator", + [0x0004] = "LastChangedTime", + [0x0005] = "ReplacementProductList", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function ActivatedCarbonFilterMonitoring:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ResetCondition", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +ActivatedCarbonFilterMonitoring.attribute_direction_map = { + ["Condition"] = "server", + ["DegradationDirection"] = "server", + ["ChangeIndication"] = "server", + ["InPlaceIndicator"] = "server", + ["LastChangedTime"] = "server", + ["ReplacementProductList"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + + +ActivatedCarbonFilterMonitoring.FeatureMap = ActivatedCarbonFilterMonitoring.types.Feature + +function ActivatedCarbonFilterMonitoring.are_features_supported(feature, feature_map) + if (ActivatedCarbonFilterMonitoring.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ActivatedCarbonFilterMonitoring.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ActivatedCarbonFilterMonitoring.NAME)) + end + return ActivatedCarbonFilterMonitoring[direction].attributes[key] +end +ActivatedCarbonFilterMonitoring.attributes = {} +setmetatable(ActivatedCarbonFilterMonitoring.attributes, attribute_helper_mt) + +setmetatable(ActivatedCarbonFilterMonitoring, {__index = cluster_base}) + +return ActivatedCarbonFilterMonitoring + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..ef3fb00a6a --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/AttributeList.lua @@ -0,0 +1,126 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.ActivatedCarbonFilterMonitoring.AttributeList +--- @alias AttributeList +--- +--- @field public ID number 0xFFFB the ID of this attribute +--- @field public NAME string "AttributeList" the name of this attribute +--- @field public data_type st.matter.data_types.Array the data type of this attribute + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +--- Add additional functionality to the base type object +--- +--- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to +function AttributeList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) + end +end + +--- Create a Array object of this attribute with any additional features provided for the attribute +--- This is also usable with the AttributeList(...) syntax +--- +--- @vararg vararg the values needed to construct a Array +--- @return st.matter.data_types.Array +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: AttributeList => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an AttributeList test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) +return AttributeList + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua new file mode 100644 index 0000000000..dd6fe1ecfb --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/ChangeIndication.lua @@ -0,0 +1,116 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.ActivatedCarbonFilterMonitoring.ChangeIndication +--- @alias ChangeIndication +--- +--- @field public ID number 0x0002 the ID of this attribute +--- @field public NAME string "ChangeIndication" the name of this attribute +--- @field public data_type ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum the data type of this attribute + +local ChangeIndication = { + ID = 0x0002, + NAME = "ChangeIndication", + base_type = require "ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum", +} + +--- Create a ChangeIndicationEnum object of this attribute with any additional features provided for the attribute +--- This is also usable with the ChangeIndication(...) syntax +--- +--- @vararg vararg the values needed to construct a ChangeIndicationEnum +--- @return ActivatedCarbonFilterMonitoring.types.ChangeIndicationEnum +function ChangeIndication:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function ChangeIndication:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: ChangeIndication => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function ChangeIndication:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ChangeIndication:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an ChangeIndication test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function ChangeIndication:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ChangeIndication:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ChangeIndication, {__call = ChangeIndication.new_value, __index = ChangeIndication.base_type}) +return ChangeIndication + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua new file mode 100644 index 0000000000..e668aa4c48 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/Condition.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Condition = { + ID = 0x0000, + NAME = "Condition", + base_type = require "st.matter.data_types.Uint8", +} + +function Condition:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function Condition:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function Condition:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function Condition:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Condition:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Condition:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(Condition, {__call = Condition.new_value, __index = Condition.base_type}) +return Condition + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua new file mode 100644 index 0000000000..a02378a50d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ActivatedCarbonFilterMonitoring.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ActivatedCarbonFilterMonitoringServerAttributes = {} + +function ActivatedCarbonFilterMonitoringServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ActivatedCarbonFilterMonitoringServerAttributes, attr_mt) + +return ActivatedCarbonFilterMonitoringServerAttributes + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua new file mode 100644 index 0000000000..438de24c94 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/ChangeIndicationEnum.lua @@ -0,0 +1,33 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ChangeIndicationEnum = {} +-- Note: the name here is intentionally set to Uint8 to maintain backwards compatibility +-- with how types were handled in api < 10. +local new_mt = UintABC.new_mt({NAME = "Uint8", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.OK] = "OK", + [self.WARNING] = "WARNING", + [self.CRITICAL] = "CRITICAL", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.OK = 0x00 +new_mt.__index.WARNING = 0x01 +new_mt.__index.CRITICAL = 0x02 + +ChangeIndicationEnum.OK = 0x00 +ChangeIndicationEnum.WARNING = 0x01 +ChangeIndicationEnum.CRITICAL = 0x02 + +ChangeIndicationEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ChangeIndicationEnum, new_mt) + +return ChangeIndicationEnum + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/Feature.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/Feature.lua new file mode 100644 index 0000000000..88474d1b0f --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/Feature.lua @@ -0,0 +1,98 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.CONDITION = 0x0001 +Feature.WARNING = 0x0002 +Feature.REPLACEMENT_PRODUCT_LIST = 0x0004 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + CONDITION = 0x0001, + WARNING = 0x0002, + REPLACEMENT_PRODUCT_LIST = 0x0004, +} + +Feature.is_condition_set = function(self) + return (self.value & self.CONDITION) ~= 0 +end + +Feature.set_condition = function(self) + if self.value ~= nil then + self.value = self.value | self.CONDITION + else + self.value = self.CONDITION + end +end + +Feature.unset_condition = function(self) + self.value = self.value & (~self.CONDITION & self.BASE_MASK) +end + +Feature.is_warning_set = function(self) + return (self.value & self.WARNING) ~= 0 +end + +Feature.set_warning = function(self) + if self.value ~= nil then + self.value = self.value | self.WARNING + else + self.value = self.WARNING + end +end + +Feature.unset_warning = function(self) + self.value = self.value & (~self.WARNING & self.BASE_MASK) +end + +Feature.is_replacement_product_list_set = function(self) + return (self.value & self.REPLACEMENT_PRODUCT_LIST) ~= 0 +end + +Feature.set_replacement_product_list = function(self) + if self.value ~= nil then + self.value = self.value | self.REPLACEMENT_PRODUCT_LIST + else + self.value = self.REPLACEMENT_PRODUCT_LIST + end +end + +Feature.unset_replacement_product_list = function(self) + self.value = self.value & (~self.REPLACEMENT_PRODUCT_LIST & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.CONDITION | + Feature.WARNING | + Feature.REPLACEMENT_PRODUCT_LIST + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_condition_set = Feature.is_condition_set, + set_condition = Feature.set_condition, + unset_condition = Feature.unset_condition, + is_warning_set = Feature.is_warning_set, + set_warning = Feature.set_warning, + unset_warning = Feature.unset_warning, + is_replacement_product_list_set = Feature.is_replacement_product_list_set, + set_replacement_product_list = Feature.set_replacement_product_list, + unset_replacement_product_list = Feature.unset_replacement_product_list, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua new file mode 100644 index 0000000000..2ff8e6e89a --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/ActivatedCarbonFilterMonitoring/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("ActivatedCarbonFilterMonitoring.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ActivatedCarbonFilterMonitoringTypes = {} + +setmetatable(ActivatedCarbonFilterMonitoringTypes, types_mt) + +return ActivatedCarbonFilterMonitoringTypes + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua new file mode 100644 index 0000000000..21795104b7 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/init.lua @@ -0,0 +1,79 @@ +local cluster_base = require "st.matter.cluster_base" +local HepaFilterMonitoringServerAttributes = require "HepaFilterMonitoring.server.attributes" +local HepaFilterMonitoringTypes = require "HepaFilterMonitoring.types" + +local HepaFilterMonitoring = {} + +HepaFilterMonitoring.ID = 0x0071 +HepaFilterMonitoring.NAME = "HepaFilterMonitoring" +HepaFilterMonitoring.server = {} +HepaFilterMonitoring.client = {} +HepaFilterMonitoring.server.attributes = HepaFilterMonitoringServerAttributes:set_parent_cluster(HepaFilterMonitoring) +HepaFilterMonitoring.types = HepaFilterMonitoringTypes + +function HepaFilterMonitoring:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "Condition", + [0x0001] = "DegradationDirection", + [0x0002] = "ChangeIndication", + [0x0003] = "InPlaceIndicator", + [0x0004] = "LastChangedTime", + [0x0005] = "ReplacementProductList", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function HepaFilterMonitoring:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ResetCondition", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +HepaFilterMonitoring.attribute_direction_map = { + ["Condition"] = "server", + ["DegradationDirection"] = "server", + ["ChangeIndication"] = "server", + ["InPlaceIndicator"] = "server", + ["LastChangedTime"] = "server", + ["ReplacementProductList"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + + +HepaFilterMonitoring.FeatureMap = HepaFilterMonitoring.types.Feature + +function HepaFilterMonitoring.are_features_supported(feature, feature_map) + if (HepaFilterMonitoring.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = HepaFilterMonitoring.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, HepaFilterMonitoring.NAME)) + end + return HepaFilterMonitoring[direction].attributes[key] +end +HepaFilterMonitoring.attributes = {} +setmetatable(HepaFilterMonitoring.attributes, attribute_helper_mt) + +setmetatable(HepaFilterMonitoring, {__index = cluster_base}) + +return HepaFilterMonitoring + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..c4f817e428 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/AttributeList.lua @@ -0,0 +1,126 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.HepaFilterMonitoring.AttributeList +--- @alias AttributeList +--- +--- @field public ID number 0xFFFB the ID of this attribute +--- @field public NAME string "AttributeList" the name of this attribute +--- @field public data_type st.matter.data_types.Array the data type of this attribute + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +--- Add additional functionality to the base type object +--- +--- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to +function AttributeList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) + end +end + +--- Create a Array object of this attribute with any additional features provided for the attribute +--- This is also usable with the AttributeList(...) syntax +--- +--- @vararg vararg the values needed to construct a Array +--- @return st.matter.data_types.Array +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: AttributeList => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an AttributeList test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) +return AttributeList + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua new file mode 100644 index 0000000000..06a80153e3 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/ChangeIndication.lua @@ -0,0 +1,116 @@ +-- Copyright 2024 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.HepaFilterMonitoring.ChangeIndication +--- @alias ChangeIndication +--- +--- @field public ID number 0x0002 the ID of this attribute +--- @field public NAME string "ChangeIndication" the name of this attribute +--- @field public data_type HepaFilterMonitoring.types.ChangeIndicationEnum the data type of this attribute + +local ChangeIndication = { + ID = 0x0002, + NAME = "ChangeIndication", + base_type = require "HepaFilterMonitoring.types.ChangeIndicationEnum", +} + +--- Create a ChangeIndicationEnum object of this attribute with any additional features provided for the attribute +--- This is also usable with the ChangeIndication(...) syntax +--- +--- @vararg vararg the values needed to construct a ChangeIndicationEnum +--- @return HepaFilterMonitoring.types.ChangeIndicationEnum +function ChangeIndication:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function ChangeIndication:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: ChangeIndication => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function ChangeIndication:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ChangeIndication:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an ChangeIndication test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function ChangeIndication:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ChangeIndication:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ChangeIndication, {__call = ChangeIndication.new_value, __index = ChangeIndication.base_type}) +return ChangeIndication + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/Condition.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/Condition.lua new file mode 100644 index 0000000000..e668aa4c48 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/Condition.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Condition = { + ID = 0x0000, + NAME = "Condition", + base_type = require "st.matter.data_types.Uint8", +} + +function Condition:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function Condition:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function Condition:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function Condition:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Condition:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Condition:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(Condition, {__call = Condition.new_value, __index = Condition.base_type}) +return Condition + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/init.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/init.lua new file mode 100644 index 0000000000..8d7ffe6c00 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("HepaFilterMonitoring.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local HepaFilterMonitoringServerAttributes = {} + +function HepaFilterMonitoringServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(HepaFilterMonitoringServerAttributes, attr_mt) + +return HepaFilterMonitoringServerAttributes + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua new file mode 100644 index 0000000000..438de24c94 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/ChangeIndicationEnum.lua @@ -0,0 +1,33 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ChangeIndicationEnum = {} +-- Note: the name here is intentionally set to Uint8 to maintain backwards compatibility +-- with how types were handled in api < 10. +local new_mt = UintABC.new_mt({NAME = "Uint8", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.OK] = "OK", + [self.WARNING] = "WARNING", + [self.CRITICAL] = "CRITICAL", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.OK = 0x00 +new_mt.__index.WARNING = 0x01 +new_mt.__index.CRITICAL = 0x02 + +ChangeIndicationEnum.OK = 0x00 +ChangeIndicationEnum.WARNING = 0x01 +ChangeIndicationEnum.CRITICAL = 0x02 + +ChangeIndicationEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ChangeIndicationEnum, new_mt) + +return ChangeIndicationEnum + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/Feature.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/Feature.lua new file mode 100644 index 0000000000..88474d1b0f --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/Feature.lua @@ -0,0 +1,98 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.CONDITION = 0x0001 +Feature.WARNING = 0x0002 +Feature.REPLACEMENT_PRODUCT_LIST = 0x0004 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + CONDITION = 0x0001, + WARNING = 0x0002, + REPLACEMENT_PRODUCT_LIST = 0x0004, +} + +Feature.is_condition_set = function(self) + return (self.value & self.CONDITION) ~= 0 +end + +Feature.set_condition = function(self) + if self.value ~= nil then + self.value = self.value | self.CONDITION + else + self.value = self.CONDITION + end +end + +Feature.unset_condition = function(self) + self.value = self.value & (~self.CONDITION & self.BASE_MASK) +end + +Feature.is_warning_set = function(self) + return (self.value & self.WARNING) ~= 0 +end + +Feature.set_warning = function(self) + if self.value ~= nil then + self.value = self.value | self.WARNING + else + self.value = self.WARNING + end +end + +Feature.unset_warning = function(self) + self.value = self.value & (~self.WARNING & self.BASE_MASK) +end + +Feature.is_replacement_product_list_set = function(self) + return (self.value & self.REPLACEMENT_PRODUCT_LIST) ~= 0 +end + +Feature.set_replacement_product_list = function(self) + if self.value ~= nil then + self.value = self.value | self.REPLACEMENT_PRODUCT_LIST + else + self.value = self.REPLACEMENT_PRODUCT_LIST + end +end + +Feature.unset_replacement_product_list = function(self) + self.value = self.value & (~self.REPLACEMENT_PRODUCT_LIST & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.CONDITION | + Feature.WARNING | + Feature.REPLACEMENT_PRODUCT_LIST + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_condition_set = Feature.is_condition_set, + set_condition = Feature.set_condition, + unset_condition = Feature.unset_condition, + is_warning_set = Feature.is_warning_set, + set_warning = Feature.set_warning, + unset_warning = Feature.unset_warning, + is_replacement_product_list_set = Feature.is_replacement_product_list_set, + set_replacement_product_list = Feature.set_replacement_product_list, + unset_replacement_product_list = Feature.unset_replacement_product_list, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua new file mode 100644 index 0000000000..77aca088ff --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/HepaFilterMonitoring/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("HepaFilterMonitoring.types." .. key) + end + return types_mt.__types_cache[key] +end + +local HepaFilterMonitoringTypes = {} + +setmetatable(HepaFilterMonitoringTypes, types_mt) + +return HepaFilterMonitoringTypes + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua new file mode 100644 index 0000000000..697aa3fd05 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/init.lua @@ -0,0 +1,84 @@ +local cluster_base = require "st.matter.cluster_base" +local MicrowaveOvenControlServerAttributes = require "MicrowaveOvenControl.server.attributes" +local MicrowaveOvenControlServerCommands = require "MicrowaveOvenControl.server.commands" +local MicrowaveOvenControlTypes = require "MicrowaveOvenControl.types" + +local MicrowaveOvenControl = {} + +MicrowaveOvenControl.ID = 0x005F +MicrowaveOvenControl.NAME = "MicrowaveOvenControl" +MicrowaveOvenControl.server = {} +MicrowaveOvenControl.client = {} +MicrowaveOvenControl.server.attributes = MicrowaveOvenControlServerAttributes:set_parent_cluster(MicrowaveOvenControl) +MicrowaveOvenControl.server.commands = MicrowaveOvenControlServerCommands:set_parent_cluster(MicrowaveOvenControl) +MicrowaveOvenControl.types = MicrowaveOvenControlTypes + +function MicrowaveOvenControl:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "CookTime", + [0x0001] = "MaxCookTime", + [0xFFF9] = "AcceptedCommandList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function MicrowaveOvenControl:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "SetCookingParameters", + [0x0001] = "AddMoreTime", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +MicrowaveOvenControl.attribute_direction_map = { + ["CookTime"] = "server", + ["MaxCookTime"] = "server", + ["AcceptedCommandList"] = "server", +} + +MicrowaveOvenControl.command_direction_map = { + ["SetCookingParameters"] = "server", + ["AddMoreTime"] = "server", +} + +MicrowaveOvenControl.FeatureMap = MicrowaveOvenControl.types.Feature + +function MicrowaveOvenControl.are_features_supported(feature, feature_map) + if (MicrowaveOvenControl.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = MicrowaveOvenControl.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, MicrowaveOvenControl.NAME)) + end + return MicrowaveOvenControl[direction].attributes[key] +end +MicrowaveOvenControl.attributes = {} +setmetatable(MicrowaveOvenControl.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = MicrowaveOvenControl.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, MicrowaveOvenControl.NAME)) + end + return MicrowaveOvenControl[direction].commands[key] +end +MicrowaveOvenControl.commands = {} +setmetatable(MicrowaveOvenControl.commands, command_helper_mt) + +setmetatable(MicrowaveOvenControl, {__index = cluster_base}) + +return MicrowaveOvenControl \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..1c41ddafa1 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +function AcceptedCommandList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AcceptedCommandList.element_type) + end +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) +return AcceptedCommandList diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/CookTime.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/CookTime.lua new file mode 100644 index 0000000000..7af3f4a3da --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/CookTime.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CookTime = { + ID = 0x0000, + NAME = "CookTime", + base_type = require "st.matter.data_types.Uint32", +} + +function CookTime:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CookTime:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CookTime:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CookTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CookTime:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CookTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CookTime, {__call = CookTime.new_value, __index = CookTime.base_type}) +return CookTime + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/MaxCookTime.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/MaxCookTime.lua new file mode 100644 index 0000000000..1eba1d229f --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/MaxCookTime.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local MaxCookTime = { + ID = 0x0001, + NAME = "MaxCookTime", + base_type = require "st.matter.data_types.Uint32", +} + +function MaxCookTime:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function MaxCookTime:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaxCookTime:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaxCookTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MaxCookTime:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MaxCookTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MaxCookTime, {__call = MaxCookTime.new_value, __index = MaxCookTime.base_type}) +return MaxCookTime + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/init.lua new file mode 100644 index 0000000000..7dadc923c6 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/attributes/init.lua @@ -0,0 +1,23 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("MicrowaveOvenControl.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local MicrowaveOvenControlServerAttributes = {} + +function MicrowaveOvenControlServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(MicrowaveOvenControlServerAttributes, attr_mt) + +return MicrowaveOvenControlServerAttributes diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/AddMoreTime.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/AddMoreTime.lua new file mode 100644 index 0000000000..d9372ff1ec --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/AddMoreTime.lua @@ -0,0 +1,97 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AddMoreTime = {} + +AddMoreTime.NAME = "AddMoreTime" +AddMoreTime.ID = 0x0001 +AddMoreTime.field_defs = { + { + name = "time_to_add", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint32", + }, +} + +function AddMoreTime:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function AddMoreTime:init(device, endpoint_id, time_to_add) + local out = {} + local args = {time_to_add} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = AddMoreTime, + __tostring = AddMoreTime.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function AddMoreTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AddMoreTime:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function AddMoreTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AddMoreTime, {__call = AddMoreTime.init}) + +return AddMoreTime \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/SetCookingParameters.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/SetCookingParameters.lua new file mode 100644 index 0000000000..5ef5e16190 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/SetCookingParameters.lua @@ -0,0 +1,125 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SetCookingParameters = {} + +SetCookingParameters.NAME = "SetCookingParameters" +SetCookingParameters.ID = 0x0000 +SetCookingParameters.field_defs = { + { + name = "cook_mode", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "cook_time", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", + }, + { + name = "power_setting", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "watt_setting_index", + field_id = 3, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "start_after_setting", + field_id = 4, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Boolean", + }, +} + +function SetCookingParameters:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function SetCookingParameters:init(device, endpoint_id, cook_mode, cook_time, power_setting, watt_setting_index, start_after_setting) + local out = {} + local args = {cook_mode, cook_time, power_setting, watt_setting_index, start_after_setting} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = SetCookingParameters, + __tostring = SetCookingParameters.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function SetCookingParameters:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SetCookingParameters:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function SetCookingParameters:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SetCookingParameters, {__call = SetCookingParameters.init}) + +return SetCookingParameters \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/init.lua new file mode 100644 index 0000000000..1b345a90d6 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/server/commands/init.lua @@ -0,0 +1,22 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("MicrowaveOvenControl.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local MicrowaveOvenControlServerCommands = {} + +function MicrowaveOvenControlServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(MicrowaveOvenControlServerCommands, command_mt) + +return MicrowaveOvenControlServerCommands diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/Feature.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/Feature.lua new file mode 100644 index 0000000000..31a881aff6 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/Feature.lua @@ -0,0 +1,97 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.POWER_AS_NUMBER = 0x0001 +Feature.POWER_IN_WATTS = 0x0002 +Feature.POWER_NUMBER_LIMITS = 0x0004 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + POWER_AS_NUMBER = 0x0001, + POWER_IN_WATTS = 0x0002, + POWER_NUMBER_LIMITS = 0x0004, +} + +Feature.is_power_as_number_set = function(self) + return (self.value & self.POWER_AS_NUMBER) ~= 0 +end + +Feature.set_power_as_number = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_AS_NUMBER + else + self.value = self.POWER_AS_NUMBER + end +end + +Feature.unset_power_as_number = function(self) + self.value = self.value & (~self.POWER_AS_NUMBER & self.BASE_MASK) +end + +Feature.is_power_in_watts_set = function(self) + return (self.value & self.POWER_IN_WATTS) ~= 0 +end + +Feature.set_power_in_watts = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_IN_WATTS + else + self.value = self.POWER_IN_WATTS + end +end + +Feature.unset_power_in_watts = function(self) + self.value = self.value & (~self.POWER_IN_WATTS & self.BASE_MASK) +end + +Feature.is_power_number_limits_set = function(self) + return (self.value & self.POWER_NUMBER_LIMITS) ~= 0 +end + +Feature.set_power_number_limits = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_NUMBER_LIMITS + else + self.value = self.POWER_NUMBER_LIMITS + end +end + +Feature.unset_power_number_limits = function(self) + self.value = self.value & (~self.POWER_NUMBER_LIMITS & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.POWER_AS_NUMBER | + Feature.POWER_IN_WATTS | + Feature.POWER_NUMBER_LIMITS + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_power_as_number_set = Feature.is_power_as_number_set, + set_power_as_number = Feature.set_power_as_number, + unset_power_as_number = Feature.unset_power_as_number, + is_power_in_watts_set = Feature.is_power_in_watts_set, + set_power_in_watts = Feature.set_power_in_watts, + unset_power_in_watts = Feature.unset_power_in_watts, + is_power_number_limits_set = Feature.is_power_number_limits_set, + set_power_number_limits = Feature.set_power_number_limits, + unset_power_number_limits = Feature.unset_power_number_limits, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua new file mode 100644 index 0000000000..29c27b80ae --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenControl/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("MicrowaveOvenControl.types." .. key) + end + return types_mt.__types_cache[key] +end + +local MicrowaveOvenControlTypes = {} + +setmetatable(MicrowaveOvenControlTypes, types_mt) + +return MicrowaveOvenControlTypes + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua new file mode 100644 index 0000000000..fdbbf4c443 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/init.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local MicrowaveOvenModeServerAttributes = require "MicrowaveOvenMode.server.attributes" +local MicrowaveOvenModeTypes = require "MicrowaveOvenMode.types" + +local MicrowaveOvenMode = {} + +MicrowaveOvenMode.ID = 0x005E +MicrowaveOvenMode.NAME = "MicrowaveOvenMode" +MicrowaveOvenMode.server = {} +MicrowaveOvenMode.client = {} +MicrowaveOvenMode.server.attributes = MicrowaveOvenModeServerAttributes:set_parent_cluster(MicrowaveOvenMode) +MicrowaveOvenMode.types = MicrowaveOvenModeTypes + +function MicrowaveOvenMode:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SupportedModes", + [0x0001] = "CurrentMode", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function MicrowaveOvenMode:get_server_command_by_id(command_id) + local server_id_map = { + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +MicrowaveOvenMode.attribute_direction_map = { + ["SupportedModes"] = "server", + ["CurrentMode"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +MicrowaveOvenMode.FeatureMap = MicrowaveOvenMode.types.Feature + +function MicrowaveOvenMode.are_features_supported(feature, feature_map) + if (MicrowaveOvenMode.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = MicrowaveOvenMode.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, MicrowaveOvenMode.NAME)) + end + return MicrowaveOvenMode[direction].attributes[key] +end +MicrowaveOvenMode.attributes = {} +setmetatable(MicrowaveOvenMode.attributes, attribute_helper_mt) + +setmetatable(MicrowaveOvenMode, {__index = cluster_base}) + +return MicrowaveOvenMode diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..1c41ddafa1 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +function AcceptedCommandList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AcceptedCommandList.element_type) + end +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) +return AcceptedCommandList diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/CurrentMode.lua new file mode 100644 index 0000000000..242a598bfd --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/CurrentMode.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentMode = { + ID = 0x0001, + NAME = "CurrentMode", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentMode:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CurrentMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CurrentMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentMode:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentMode, {__call = CurrentMode.new_value, __index = CurrentMode.base_type}) +return CurrentMode + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/SupportedModes.lua new file mode 100644 index 0000000000..c6c19b685e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/SupportedModes.lua @@ -0,0 +1,75 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupportedModes = { + ID = 0x0000, + NAME = "SupportedModes", + base_type = require "st.matter.data_types.Array", + element_type = require "MicrowaveOvenMode.types.ModeOptionStruct", +} + +function SupportedModes:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, SupportedModes.element_type) + end +end + +function SupportedModes:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SupportedModes:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupportedModes:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupportedModes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupportedModes:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupportedModes:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SupportedModes, {__call = SupportedModes.new_value, __index = SupportedModes.base_type}) +return SupportedModes + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/init.lua new file mode 100644 index 0000000000..a7fd9d169c --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/server/attributes/init.lua @@ -0,0 +1,23 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("MicrowaveOvenMode.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local MicrowaveOvenModeServerAttributes = {} + +function MicrowaveOvenModeServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(MicrowaveOvenModeServerAttributes, attr_mt) + +return MicrowaveOvenModeServerAttributes diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/Feature.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/Feature.lua new file mode 100644 index 0000000000..16f8292349 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/Feature.lua @@ -0,0 +1,53 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.ON_OFF = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + ON_OFF = 0x0001, +} + +Feature.is_on_off_set = function(self) + return (self.value & self.ON_OFF) ~= 0 +end + +Feature.set_on_off = function(self) + if self.value ~= nil then + self.value = self.value | self.ON_OFF + else + self.value = self.ON_OFF + end +end + +Feature.unset_on_off = function(self) + self.value = self.value & (~self.ON_OFF & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.ON_OFF + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_on_off_set = Feature.is_on_off_set, + set_on_off = Feature.set_on_off, + unset_on_off = Feature.unset_on_off, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeOptionStruct.lua new file mode 100644 index 0000000000..f834d4be6e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeOptionStruct.lua @@ -0,0 +1,85 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local ModeOptionStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeOptionStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeOptionStruct.field_defs = { + { + name = "label", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.UTF8String1", + }, + { + name = "mode", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "mode_tags", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Array", + element_type = require "MicrowaveOvenMode.types.ModeTagStruct", + }, +} + +ModeOptionStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeOptionStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeOptionStruct.init +new_mt.__index.serialize = ModeOptionStruct.serialize + +ModeOptionStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeOptionStruct, new_mt) + +return ModeOptionStruct + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTag.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTag.lua new file mode 100644 index 0000000000..1ede9e638a --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTag.lua @@ -0,0 +1,27 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ModeTag = {} +local new_mt = UintABC.new_mt({NAME = "ModeTag", ID = data_types.name_to_id_map["Uint16"]}, 2) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.NORMAL] = "NORMAL", + [self.DEFROST] = "DEFROST", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.NORMAL = 0x4000 +new_mt.__index.DEFROST = 0x4001 + +ModeTag.NORMAL = 0x4000 +ModeTag.DEFROST = 0x4001 + +ModeTag.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ModeTag, new_mt) + +return ModeTag diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTagStruct.lua new file mode 100644 index 0000000000..700ad7f597 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/ModeTagStruct.lua @@ -0,0 +1,78 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "value", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, +} + +ModeTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeTagStruct.init +new_mt.__index.serialize = ModeTagStruct.serialize + +ModeTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeTagStruct, new_mt) + +return ModeTagStruct + diff --git a/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua new file mode 100644 index 0000000000..412d69cabd --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/MicrowaveOvenMode/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("MicrowaveOvenMode.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local MicrowaveOvenModeTypes = {} + +setmetatable(MicrowaveOvenModeTypes, types_mt) + +return MicrowaveOvenModeTypes + diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua new file mode 100644 index 0000000000..76e1817dc1 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/init.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local OvenModeServerAttributes = require "OvenMode.server.attributes" +local OvenModeServerCommands = require "OvenMode.server.commands" +local OvenModeTypes = require "OvenMode.types" + +local OvenMode = {} + +OvenMode.ID = 0x0049 +OvenMode.NAME = "OvenMode" +OvenMode.server = {} +OvenMode.client = {} +OvenMode.server.attributes = OvenModeServerAttributes:set_parent_cluster(OvenMode) +OvenMode.server.commands = OvenModeServerCommands:set_parent_cluster(OvenMode) +OvenMode.types = OvenModeTypes + +function OvenMode:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SupportedModes", + [0x0001] = "CurrentMode", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function OvenMode:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ChangeToMode", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +OvenMode.attribute_direction_map = { + ["SupportedModes"] = "server", + ["CurrentMode"] = "server", +} + +OvenMode.command_direction_map = { + ["ChangeToMode"] = "server", +} + +OvenMode.FeatureMap = OvenMode.types.Feature + +function OvenMode.are_features_supported(feature, feature_map) + if (OvenMode.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = OvenMode.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, OvenMode.NAME)) + end + return OvenMode[direction].attributes[key] +end +OvenMode.attributes = {} +setmetatable(OvenMode.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = OvenMode.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, OvenMode.NAME)) + end + return OvenMode[direction].commands[key] +end +OvenMode.commands = {} +setmetatable(OvenMode.commands, command_helper_mt) + +setmetatable(OvenMode, {__index = cluster_base}) + +return OvenMode diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/CurrentMode.lua new file mode 100644 index 0000000000..f2987e294d --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/CurrentMode.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentMode = { + ID = 0x0001, + NAME = "CurrentMode", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentMode:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentMode:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentMode, {__call = CurrentMode.new_value, __index = CurrentMode.base_type}) +return CurrentMode diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/SupportedModes.lua new file mode 100644 index 0000000000..3803e1cd01 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/SupportedModes.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupportedModes = { + ID = 0x0000, + NAME = "SupportedModes", + base_type = require "st.matter.data_types.Array", + element_type = require "OvenMode.types.ModeOptionStruct", +} + +function SupportedModes:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, SupportedModes.element_type) + end +end + +function SupportedModes:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SupportedModes:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupportedModes:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupportedModes:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SupportedModes, {__call = SupportedModes.new_value, __index = SupportedModes.base_type}) +return SupportedModes diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/init.lua new file mode 100644 index 0000000000..6527039dfc --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/server/attributes/init.lua @@ -0,0 +1,23 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("OvenMode.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local OvenModeServerAttributes = {} + +function OvenModeServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(OvenModeServerAttributes, attr_mt) + +return OvenModeServerAttributes diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/ChangeToMode.lua new file mode 100644 index 0000000000..4e492640a9 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/ChangeToMode.lua @@ -0,0 +1,86 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ChangeToMode = {} + +ChangeToMode.NAME = "ChangeToMode" +ChangeToMode.ID = 0x0000 +ChangeToMode.field_defs = { + { + name = "new_mode", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, +} + +function ChangeToMode:init(device, endpoint_id, new_mode) + local out = {} + local args = {new_mode} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ChangeToMode, + __tostring = ChangeToMode.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ChangeToMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ChangeToMode:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function ChangeToMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ChangeToMode, {__call = ChangeToMode.init}) + +return ChangeToMode diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/init.lua new file mode 100644 index 0000000000..78434b8acd --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/server/commands/init.lua @@ -0,0 +1,22 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("OvenMode.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local OvenModeServerCommands = {} + +function OvenModeServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(OvenModeServerCommands, command_mt) + +return OvenModeServerCommands diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/Feature.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/Feature.lua new file mode 100644 index 0000000000..da49bf4115 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/Feature.lua @@ -0,0 +1,54 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.ON_OFF = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + ON_OFF = 0x0001, +} + +Feature.is_on_off_set = function(self) + return (self.value & self.ON_OFF) ~= 0 +end + +Feature.set_on_off = function(self) + if self.value ~= nil then + self.value = self.value | self.ON_OFF + else + self.value = self.ON_OFF + end +end + +Feature.unset_on_off = function(self) + self.value = self.value & (~self.ON_OFF & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.ON_OFF + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_on_off_set = Feature.is_on_off_set, + set_on_off = Feature.set_on_off, + unset_on_off = Feature.unset_on_off, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeOptionStruct.lua new file mode 100644 index 0000000000..5278300221 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeOptionStruct.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeOptionStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeOptionStruct", ID = data_types.name_to_id_map["Structure"]}) +ModeOptionStruct.field_defs = { + { + name = "label", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.UTF8String1", + }, + { + name = "mode", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "mode_tags", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Array", + element_type = require "OvenMode.types.ModeTagStruct", + }, +} + +ModeOptionStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeOptionStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeOptionStruct.init +new_mt.__index.serialize = ModeOptionStruct.serialize + +ModeOptionStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeOptionStruct, new_mt) + +return ModeOptionStruct diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTag.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTag.lua new file mode 100644 index 0000000000..233b2a0905 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTag.lua @@ -0,0 +1,48 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ModeTag = {} +local new_mt = UintABC.new_mt({NAME = "ModeTag", ID = data_types.name_to_id_map["Uint16"]}, 2) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.BAKE] = "BAKE", + [self.CONVECTION] = "CONVECTION", + [self.GRILL] = "GRILL", + [self.ROAST] = "ROAST", + [self.CLEAN] = "CLEAN", + [self.CONVECTION_BAKE] = "CONVECTION_BAKE", + [self.CONVECTION_ROAST] = "CONVECTION_ROAST", + [self.WARMING] = "WARMING", + [self.PROOFING] = "PROOFING", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.BAKE = 0x4000 +new_mt.__index.CONVECTION = 0x4001 +new_mt.__index.GRILL = 0x4002 +new_mt.__index.ROAST = 0x4003 +new_mt.__index.CLEAN = 0x4004 +new_mt.__index.CONVECTION_BAKE = 0x4005 +new_mt.__index.CONVECTION_ROAST = 0x4006 +new_mt.__index.WARMING = 0x4007 +new_mt.__index.PROOFING = 0x4008 + +ModeTag.BAKE = 0x4000 +ModeTag.CONVECTION = 0x4001 +ModeTag.GRILL = 0x4002 +ModeTag.ROAST = 0x4003 +ModeTag.CLEAN = 0x4004 +ModeTag.CONVECTION_BAKE = 0x4005 +ModeTag.CONVECTION_ROAST = 0x4006 +ModeTag.WARMING = 0x4007 +ModeTag.PROOFING = 0x4008 + +ModeTag.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ModeTag, new_mt) + +return ModeTag diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTagStruct.lua new file mode 100644 index 0000000000..610fb90ad7 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/ModeTagStruct.lua @@ -0,0 +1,77 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "value", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, +} + +ModeTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeTagStruct.init +new_mt.__index.serialize = ModeTagStruct.serialize + +ModeTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeTagStruct, new_mt) + +return ModeTagStruct diff --git a/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua b/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua new file mode 100644 index 0000000000..8bd0777339 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/OvenMode/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("OvenMode.types." .. key) + end + return types_mt.__types_cache[key] +end + +local OvenModeTypes = {} + +setmetatable(OvenModeTypes, types_mt) + +return OvenModeTypes + diff --git a/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua index febc27b569..c08a4cc44f 100644 --- a/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-appliance/src/embedded-cluster-utils.lua @@ -3,8 +3,10 @@ local utils = require "st.utils" local version = require "version" if version.api < 10 then + clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" clusters.DishwasherAlarm = require "DishwasherAlarm" clusters.DishwasherMode = require "DishwasherMode" + clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" clusters.LaundryWasherControls = require "LaundryWasherControls" clusters.LaundryWasherMode = require "LaundryWasherMode" clusters.OperationalState = require "OperationalState" @@ -13,17 +15,30 @@ if version.api < 10 then clusters.TemperatureControl = require "TemperatureControl" end +if version.api < 11 then + clusters.MicrowaveOvenControl = require "MicrowaveOvenControl" + clusters.MicrowaveOvenMode = require "MicrowaveOvenMode" +end + +-- this cluster is not supported in any release of the lua libs +clusters.OvenMode = require "OvenMode" + local embedded_cluster_utils = {} local embedded_clusters = { + [clusters.ActivatedCarbonFilterMonitoring.ID] = clusters.ActivatedCarbonFilterMonitoring, [clusters.DishwasherAlarm.ID] = clusters.DishwasherAlarm, [clusters.DishwasherMode.ID] = clusters.DishwasherMode, + [clusters.HepaFilterMonitoring.ID] = clusters.HepaFilterMonitoring, [clusters.LaundryWasherControls.ID] = clusters.LaundryWasherControls, [clusters.LaundryWasherMode.ID] = clusters.LaundryWasherMode, [clusters.OperationalState.ID] = clusters.OperationalState, [clusters.RefrigeratorAlarm.ID] = clusters.RefrigeratorAlarm, [clusters.RefrigeratorAndTemperatureControlledCabinetMode.ID] = clusters.RefrigeratorAndTemperatureControlledCabinetMode, [clusters.TemperatureControl.ID] = clusters.TemperatureControl, + [clusters.MicrowaveOvenControl.ID] = clusters.MicrowaveOvenControl, + [clusters.MicrowaveOvenMode.ID] = clusters.MicrowaveOvenMode, + [clusters.OvenMode.ID] = clusters.OvenMode, } function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) diff --git a/drivers/SmartThings/matter-appliance/src/init.lua b/drivers/SmartThings/matter-appliance/src/init.lua index e6a46b3579..fa87f7177c 100644 --- a/drivers/SmartThings/matter-appliance/src/init.lua +++ b/drivers/SmartThings/matter-appliance/src/init.lua @@ -17,14 +17,15 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local im = require "st.matter.interaction_model" local embedded_cluster_utils = require "embedded-cluster-utils" - local log = require "log" local utils = require "st.utils" - local version = require "version" + if version.api < 10 then + clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" clusters.DishwasherAlarm = require "DishwasherAlarm" clusters.DishwasherMode = require "DishwasherMode" + clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" clusters.LaundryWasherControls = require "LaundryWasherControls" clusters.LaundryWasherMode = require "LaundryWasherMode" clusters.OperationalState = require "OperationalState" @@ -33,14 +34,27 @@ if version.api < 10 then clusters.TemperatureControl = require "TemperatureControl" end +if version.api < 11 then + clusters.MicrowaveOvenControl = require "MicrowaveOvenControl" + clusters.MicrowaveOvenMode = require "MicrowaveOvenMode" +end + +--this cluster is not supported in any releases of the lua libs +clusters.OvenMode = require "OvenMode" + local dishwasher = require("matter-dishwasher") -local laundryWasher = require("matter-laundry-washer") +local laundry = require("matter-laundry") local refrigerator = require("matter-refrigerator") +local extractor_hood = require("matter-extractor-hood") +local cook_top = require("matter-cook-top") +local microwave_oven = require("matter-microwave-oven") +local oven = require("matter-oven") local setpoint_limit_device_field = { MIN_TEMP = "MIN_TEMP", MAX_TEMP = "MAX_TEMP", } +local LAUNDRY_WASHER_DEVICE_TYPE_ID = 0x0073 local subscribed_attributes = { [capabilities.switch.ID] = { @@ -67,6 +81,10 @@ local subscribed_attributes = { clusters.LaundryWasherMode.attributes.CurrentMode, clusters.RefrigeratorAndTemperatureControlledCabinetMode.attributes.SupportedModes, clusters.RefrigeratorAndTemperatureControlledCabinetMode.attributes.CurrentMode, + clusters.MicrowaveOvenMode.attributes.CurrentMode, + clusters.MicrowaveOvenMode.attributes.SupportedModes, + clusters.OvenMode.attributes.SupportedModes, + clusters.OvenMode.attributes.CurrentMode, }, [capabilities.laundryWasherRinseMode.ID] = { clusters.LaundryWasherControls.attributes.NumberOfRinses, @@ -89,6 +107,29 @@ local subscribed_attributes = { [capabilities.temperatureAlarm.ID] = { clusters.DishwasherAlarm.attributes.State }, + [capabilities.fanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.fanSpeedPercent.ID] = { + clusters.FanControl.attributes.PercentCurrent + }, + [capabilities.windMode.ID] = { + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting + }, + [capabilities.filterState.ID] = { + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition + }, + [capabilities.filterStatus.ID] = { + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication + }, + [capabilities.cookTime.ID] = { + clusters.MicrowaveOvenControl.attributes.MaxCookTime, + clusters.MicrowaveOvenControl.attributes.CookTime + } } local function device_init(driver, device) @@ -98,41 +139,60 @@ end local function do_configure(driver, device) local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER}) local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL}) + local hepa_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.HepaFilterMonitoring.ID) + local ac_filter_eps = embedded_cluster_utils.get_endpoints(device, clusters.ActivatedCarbonFilterMonitoring.ID) + local wind_eps = device:get_endpoints(clusters.FanControl.ID, {feature_bitmap = clusters.FanControl.types.FanControlFeature.WIND}) if dishwasher.can_handle({}, driver, device) then local profile_name = "dishwasher" - if #tn_eps > 0 and #tl_eps > 0 then - profile_name = profile_name .. "-tn" .. "-tl" - elseif #tn_eps > 0 then + if #tn_eps > 0 then profile_name = profile_name .. "-tn" - elseif #tl_eps > 0 then + end + if #tl_eps > 0 then profile_name = profile_name .. "-tl" end device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) device:try_update_metadata({profile = profile_name}) - elseif laundryWasher.can_handle({}, driver, device) then - local profile_name = "laundry-washer" - if #tn_eps > 0 and #tl_eps > 0 then - profile_name = profile_name .. "-tn" .. "-tl" - elseif #tn_eps > 0 then + elseif laundry.can_handle({}, driver, device) then + local device_type = laundry.can_handle({}, driver, device) + local profile_name = "laundry" + if (device_type == LAUNDRY_WASHER_DEVICE_TYPE_ID) then + profile_name = profile_name.."-washer" + else + profile_name = profile_name.."-dryer" + end + if #tn_eps > 0 then profile_name = profile_name .. "-tn" - elseif #tl_eps > 0 then + end + if #tl_eps > 0 then profile_name = profile_name .. "-tl" end device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) device:try_update_metadata({profile = profile_name}) elseif refrigerator.can_handle({}, driver, device) then local profile_name = "refrigerator-freezer" - if #tn_eps > 0 and #tl_eps > 0 then - profile_name = profile_name .. "-tn" .. "-tl" - elseif #tn_eps > 0 then + if #tn_eps > 0 then profile_name = profile_name .. "-tn" - elseif #tl_eps > 0 then + end + if #tl_eps > 0 then profile_name = profile_name .. "-tl" end device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) device:try_update_metadata({profile = profile_name}) + elseif extractor_hood.can_handle({}, driver, device) then + local profile_name = "extractor-hood" + if #hepa_filter_eps > 0 then + profile_name = profile_name .. "-hepa" + end + if #ac_filter_eps > 0 then + profile_name = profile_name .. "-ac" + end + if #wind_eps > 0 then + profile_name = profile_name .. "-wind" + end + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) else - device.log.warn_with({hub_logs=true}, "Device has not sub driver") + device.log.warn_with({hub_logs=true}, "Device has no sub driver") end --Query setpoint limits if needed @@ -176,10 +236,10 @@ end local function temperature_setpoint_attr_handler(driver, device, ib, response) local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER}) if #tn_eps == 0 then - device.log.warn_with({ hub_logs = true }, string.format("Device does not support TEMPERATURE_NUMBER feature")) + device.log.warn_with({hub_logs = true}, string.format("Device does not support TEMPERATURE_NUMBER feature")) return end - device.log.info_with({ hub_logs = true }, + device.log.info_with({hub_logs = true}, string.format("temperature_setpoint_attr_handler: %d", ib.data.value)) local min = device:get_field(setpoint_limit_device_field.MIN_TEMP) or 0 @@ -199,7 +259,7 @@ local function setpoint_limit_handler(limit_field) return function(driver, device, ib, response) local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER}) if #tn_eps == 0 then - device.log.warn_with({ hub_logs = true }, string.format("Device does not support TEMPERATURE_NUMBER feature")) + device.log.warn_with({hub_logs = true}, string.format("Device does not support TEMPERATURE_NUMBER feature")) return end local val = ib.data.value / 100.0 @@ -224,11 +284,10 @@ end local function handle_temperature_setpoint(driver, device, cmd) local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, {feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER}) if #tn_eps == 0 then - device.log.warn_with({ hub_logs = true }, string.format("Device does not support TEMPERATURE_NUMBER feature")) + device.log.warn_with({hub_logs = true}, string.format("Device does not support TEMPERATURE_NUMBER feature")) return end - device.log.info_with({ hub_logs = true }, - string.format("handle_temperature_setpoint: %s", cmd.args.setpoint)) + device.log.info(string.format("handle_temperature_setpoint: %s", cmd.args.setpoint)) local value = cmd.args.setpoint local _, temp_setpoint = device:get_latest_state( @@ -289,11 +348,20 @@ local matter_driver_template = { capabilities.temperatureMeasurement, capabilities.waterFlowAlarm, capabilities.temperatureAlarm, + capabilities.filterState, + capabilities.filterStatus, + capabilities.fanMode, + capabilities.fanSpeedPercent, + capabilities.windMode }, sub_drivers = { dishwasher, - laundryWasher, - refrigerator + laundry, + refrigerator, + cook_top, + microwave_oven, + extractor_hood, + oven, } } diff --git a/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua new file mode 100644 index 0000000000..a153ba007b --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua @@ -0,0 +1,227 @@ +-- Copyright 2024 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 capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "embedded-cluster-utils" + +local log = require "log" + +local version = require "version" +if version.api < 10 then + clusters.TemperatureControl = require "TemperatureControl" +end + +local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" +local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 +local COOK_TOP_DEVICE_TYPE_ID = 0x0078 +local OVEN_DEVICE_ID = 0x007B + +local SUPPORTED_TEMPERATURE_LEVELS_MAP = "__supported_temperature_levels_map" + +local function get_endpoints_for_dt(device, device_type) + local endpoints = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + +local function table_contains(tab, val) + for _, tab_val in ipairs(tab) do + if tab_val == val then + return true + end + end + return false +end + +local function endpoint_to_component(device, ep) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + for component, endpoint in pairs(map) do + if endpoint == ep then + return component + end + end + return "main" +end + +local function component_to_endpoint(device, component) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + if map[component] then + return map[component] + end + return device.MATTER_DEFAULT_ENDPOINT +end + +local function device_init(driver, device) + device:subscribe() + device:set_endpoint_to_component_fn(endpoint_to_component) + device:set_component_to_endpoint_fn(component_to_endpoint) +end + +local function device_added(driver, device) + local cook_surface_endpoints = get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + local componentToEndpointMap = { + ["cookSurfaceOne"] = cook_surface_endpoints[1], + ["cookSurfaceTwo"] = cook_surface_endpoints[2] + } + device:set_field(COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) +end + +local function do_configure(driver, device) + local cook_surface_endpoints = get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + + local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) + + local profile_name + if #cook_surface_endpoints > 0 then + profile_name = "cook-surface-one" + if table_contains(tl_eps, cook_surface_endpoints[1]) then + profile_name = profile_name .. "-tl" + end + + -- we only support up to two cook surfaces + if #cook_surface_endpoints > 1 then + profile_name = profile_name .. "-cook-surface-two" + if table_contains(tl_eps, cook_surface_endpoints[2]) then + profile_name = profile_name .. "-tl" + end + end + end + + if profile_name then + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({ profile = profile_name }) + end +end + +local function is_cook_top_device(opts, driver, device, ...) + local cook_top_eps = get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID) + local oven_eps = get_endpoints_for_dt(device, OVEN_DEVICE_ID) + -- we want to skip lifecycle events in cases where the device is an oven with a composed cook-top device + if (#oven_eps > 0) and opts.dispatcher_class == "DeviceLifecycleDispatcher" then + return false + end + if #cook_top_eps > 0 then + return true + end + return false +end + +-- Matter Handlers -- +local function selected_temperature_level_attr_handler(driver, device, ib, response) + local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) + if #tl_eps == 0 then + log.warn_with({hub_logs = true}, string.format("Device does not support TEMPERATURE_LEVEL feature")) + return + end + log.info(string.format("selected_temperature_level_attr_handler: %s", ib.data.value)) + + local temperatureLevel = ib.data.value + local supportedTemperatureLevelsMap = device:get_field(SUPPORTED_TEMPERATURE_LEVELS_MAP) or {} + local supportedTemperatureLevels = supportedTemperatureLevelsMap[ib.endpoint_id] or {} + if supportedTemperatureLevels[temperatureLevel+1] then + local tempLevel = supportedTemperatureLevels[temperatureLevel+1] + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureLevel.temperatureLevel(tempLevel)) + end + log.warn("Received unsupported temperature level for endpoint "..(ib.endpoint_id)) +end + +local function supported_temperature_levels_attr_handler(driver, device, ib, response) + local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) + if #tl_eps == 0 then + log.warn_with({hub_logs = true}, string.format("Device does not support TEMPERATURE_LEVEL feature")) + return + end + + local supportedTemperatureLevelsMap = device:get_field(SUPPORTED_TEMPERATURE_LEVELS_MAP) or {} + local supportedTemperatureLevels = {} + for _, tempLevel in ipairs(ib.data.elements) do + log.info(string.format("supported_temperature_levels_attr_handler: %s", tempLevel.value)) + table.insert(supportedTemperatureLevels, tempLevel.value) + end + supportedTemperatureLevelsMap[ib.endpoint_id] = supportedTemperatureLevels + device:set_field(SUPPORTED_TEMPERATURE_LEVELS_MAP, supportedTemperatureLevelsMap, { persist = true }) + local event = capabilities.temperatureLevel.supportedTemperatureLevels(supportedTemperatureLevels, + { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function temp_event_handler(driver, device, ib, response) + device.log.info(string.format("temp_event_handler: %s", ib.data.value)) + + local temp + local unit = "C" + if ib.data.value == nil then + temp = 0 + else + temp = ib.data.value / 100.0 + end + device:emit_event_for_endpoint(ib.endpoint_id, + capabilities.temperatureMeasurement.temperature({ value = temp, unit = unit })) +end + +local function handle_temperature_level(driver, device, cmd) + log.info(string.format("handle_temperature_level: %s", cmd.args.temperatureLevel)) + + local endpoint_id = device:component_to_endpoint(cmd.component) + local supportedTemperatureLevelsMap = device:get_field(SUPPORTED_TEMPERATURE_LEVELS_MAP) or {} + local supportedTemperatureLevels = supportedTemperatureLevelsMap[endpoint_id] or {} + for i, tempLevel in ipairs(supportedTemperatureLevels) do + if cmd.args.temperatureLevel == tempLevel then + device:send(clusters.TemperatureControl.commands.SetTemperature(device, endpoint_id, nil, i - 1)) + return + end + end +end + +local matter_cook_top_handler = { + NAME = "matter-cook-top", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure + }, + matter_handlers = { + attr = { + [clusters.TemperatureControl.ID] = { + [clusters.TemperatureControl.attributes.SelectedTemperatureLevel.ID] = + selected_temperature_level_attr_handler, + [clusters.TemperatureControl.attributes.SupportedTemperatureLevels.ID] = + supported_temperature_levels_attr_handler, + }, + [clusters.TemperatureMeasurement.ID] = { + [clusters.TemperatureMeasurement.attributes.MeasuredValue.ID] = temp_event_handler, + }, + } + }, + capability_handlers = { + [capabilities.temperatureLevel.ID] = { + [capabilities.temperatureLevel.commands.setTemperatureLevel.NAME] = handle_temperature_level, + } + }, + can_handle = is_cook_top_device, +} + +return matter_cook_top_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua index 6e3100ae1b..165aae7d1e 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-dishwasher/init.lua @@ -99,6 +99,8 @@ local function dishwasher_supported_modes_attr_handler(driver, device, ib, respo device:set_field(SUPPORTED_DISHWASHER_MODES, supportedDishwasherModes, { persist = true }) local event = capabilities.mode.supportedModes(supportedDishwasherModes, {visibility = {displayed = false}}) device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportedDishwasherModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) end local function dishwasher_mode_attr_handler(driver, device, ib, response) diff --git a/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua new file mode 100644 index 0000000000..21a79da52e --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-extractor-hood/init.lua @@ -0,0 +1,240 @@ +-- Copyright 2024 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 capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" + +local EXTRACTOR_HOOD_DEVICE_TYPE_ID = 0x007A +local version = require "version" +if version.api < 10 then + clusters.ActivatedCarbonFilterMonitoring = require "ActivatedCarbonFilterMonitoring" + clusters.HepaFilterMonitoring = require "HepaFilterMonitoring" +end + +local WIND_MODE_MAP = { + [0] = capabilities.windMode.windMode.sleepWind, + [1] = capabilities.windMode.windMode.naturalWind +} + +local function device_init(driver, device) + device:subscribe() +end + +-- Matter Handlers -- +local function is_matter_extractor_hood(opts, driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == EXTRACTOR_HOOD_DEVICE_TYPE_ID then + return true + end + end + end + return false +end + +local function fan_mode_handler(driver, device, ib, response) + if ib.data.value == clusters.FanControl.attributes.FanMode.OFF then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.off()) + elseif ib.data.value == clusters.FanControl.attributes.FanMode.LOW then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.low()) + elseif ib.data.value == clusters.FanControl.attributes.FanMode.MEDIUM then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.medium()) + elseif ib.data.value == clusters.FanControl.attributes.FanMode.HIGH then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.high()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanMode.fanMode.auto()) + end +end + +local function fan_mode_sequence_handler(driver, device, ib, response) + local supportedFanModes + if ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH then + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.medium.NAME, + capabilities.fanMode.fanMode.high.NAME + } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH then + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.high.NAME + } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO then + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.medium.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO then + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + } + elseif ib.data.value == clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO then + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + } + else + supportedFanModes = { + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.high.NAME + } + end + local event = capabilities.fanMode.supportedFanModes(supportedFanModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function fan_speed_percent_attr_handler(driver, device, ib, response) + local speed = ib.data.value + if speed ~= nil and speed <= 100 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.fanSpeedPercent.percent(speed)) + end +end + +local function wind_support_handler(driver, device, ib, response) + local supported_wind_modes = {capabilities.windMode.windMode.noWind.NAME} + for mode, wind_mode in pairs(WIND_MODE_MAP) do + if ((ib.data.value >> mode) & 1) > 0 then + table.insert(supported_wind_modes, wind_mode.NAME) + end + end + local event = capabilities.windMode.supportedWindModes(supported_wind_modes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function wind_setting_handler(driver, device, ib, response) + for index, wind_mode in pairs(WIND_MODE_MAP) do + if ((ib.data.value >> index) & 1) > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, wind_mode()) + return + end + end + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windMode.windMode.noWind()) +end + +local function hepa_filter_condition_handler(driver, device, ib, response) + local component = device.profile.components["hepaFilter"] + local condition = ib.data.value + device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) +end + +local function hepa_filter_change_indication_handler(driver, device, ib, response) + local component = device.profile.components["hepaFilter"] + if ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.OK then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.WARNING then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.HepaFilterMonitoring.attributes.ChangeIndication.CRITICAL then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) + end +end + +local function activated_carbon_filter_condition_handler(driver, device, ib, response) + local component = device.profile.components["activatedCarbonFilter"] + local condition = ib.data.value + device:emit_component_event(component, capabilities.filterState.filterLifeRemaining(condition)) +end + +local function activated_carbon_filter_change_indication_handler(driver, device, ib, response) + local component = device.profile.components["activatedCarbonFilter"] + if ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.OK then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.WARNING then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.normal()) + elseif ib.data.value == clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.CRITICAL then + device:emit_component_event(component, capabilities.filterStatus.filterStatus.replace()) + end +end + +-- Capability Handlers -- +local function set_fan_mode(driver, device, cmd) + local fan_mode_id + if cmd.args.fanMode == capabilities.fanMode.fanMode.low.NAME then + fan_mode_id = clusters.FanControl.attributes.FanMode.LOW + elseif cmd.args.fanMode == capabilities.fanMode.fanMode.medium.NAME then + fan_mode_id = clusters.FanControl.attributes.FanMode.MEDIUM + elseif cmd.args.fanMode == capabilities.fanMode.fanMode.high.NAME then + fan_mode_id = clusters.FanControl.attributes.FanMode.HIGH + elseif cmd.args.fanMode == capabilities.fanMode.fanMode.auto.NAME then + fan_mode_id = clusters.FanControl.attributes.FanMode.AUTO + else + fan_mode_id = clusters.FanControl.attributes.FanMode.OFF + end + if fan_mode_id then + device:send(clusters.FanControl.attributes.FanMode:write(device, device:component_to_endpoint(cmd.component), fan_mode_id)) + end +end + +local function set_fan_speed_percent(driver, device, cmd) + local speed = math.floor(cmd.args.percent) + device:send(clusters.FanControl.attributes.PercentSetting:write(device, device:component_to_endpoint(cmd.component), speed)) +end + +local function set_wind_mode(driver, device, cmd) + local wind_mode = 0 + if cmd.args.windMode == capabilities.windMode.windMode.sleepWind.NAME then + wind_mode = clusters.FanControl.types.WindSupportMask.SLEEP_WIND + elseif cmd.args.windMode == capabilities.windMode.windMode.naturalWind.NAME then + wind_mode = clusters.FanControl.types.WindSupportMask.NATURAL_WIND + end + device:send(clusters.FanControl.attributes.WindSetting:write(device, device:component_to_endpoint(cmd.component), wind_mode)) +end + +local matter_extractor_hood_handler = { + NAME = "matter-extractor-hood", + lifecycle_handlers = { + init = device_init + }, + matter_handlers = { + attr = { + [clusters.HepaFilterMonitoring.ID] = { + [clusters.HepaFilterMonitoring.attributes.Condition.ID] = hepa_filter_condition_handler, + [clusters.HepaFilterMonitoring.attributes.ChangeIndication.ID] = hepa_filter_change_indication_handler + }, + [clusters.ActivatedCarbonFilterMonitoring.ID] = { + [clusters.ActivatedCarbonFilterMonitoring.attributes.Condition.ID] = activated_carbon_filter_condition_handler, + [clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.ID] = activated_carbon_filter_change_indication_handler + }, + [clusters.FanControl.ID] = { + [clusters.FanControl.attributes.FanModeSequence.ID] = fan_mode_sequence_handler, + [clusters.FanControl.attributes.FanMode.ID] = fan_mode_handler, + [clusters.FanControl.attributes.PercentCurrent.ID] = fan_speed_percent_attr_handler, + [clusters.FanControl.attributes.WindSupport.ID] = wind_support_handler, + [clusters.FanControl.attributes.WindSetting.ID] = wind_setting_handler + }, + } + }, + capability_handlers = { + [capabilities.fanMode.ID] = { + [capabilities.fanMode.commands.setFanMode.NAME] = set_fan_mode + }, + [capabilities.fanSpeedPercent.ID] = { + [capabilities.fanSpeedPercent.commands.setPercent.NAME] = set_fan_speed_percent + }, + [capabilities.windMode.ID] = { + [capabilities.windMode.commands.setWindMode.NAME] = set_wind_mode + } + }, + can_handle = is_matter_extractor_hood +} + +return matter_extractor_hood_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-laundry-washer/init.lua b/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua similarity index 96% rename from drivers/SmartThings/matter-appliance/src/matter-laundry-washer/init.lua rename to drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua index 4dcf571cfb..8e65e3d652 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-laundry-washer/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-laundry/init.lua @@ -1,4 +1,4 @@ --- Copyright 2023 SmartThings +-- Copyright 2024 SmartThings -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ if version.api < 10 then end local LAUNDRY_WASHER_DEVICE_TYPE_ID = 0x0073 +local LAUNDRY_DRYER_DEVICE_TYPE_ID = 0x007C local LAUNDRY_WASHER_RINSE_MODE_MAP = { [clusters.LaundryWasherControls.types.NumberOfRinsesEnum.NONE] = capabilities.laundryWasherRinseMode.rinseMode.none, @@ -51,11 +52,11 @@ local function device_init(driver, device) end -- Matter Handlers -- -local function is_matter_laundry_washer(opts, driver, device) +local function is_matter_laundry_device(opts, driver, device) for _, ep in ipairs(device.endpoints) do for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == LAUNDRY_WASHER_DEVICE_TYPE_ID then - return true + if dt.device_type_id == LAUNDRY_WASHER_DEVICE_TYPE_ID or dt.device_type_id == LAUNDRY_DRYER_DEVICE_TYPE_ID then + return dt.device_type_id end end end @@ -112,6 +113,8 @@ local function laundry_washer_supported_modes_attr_handler(driver, device, ib, r device:set_field(SUPPORTED_LAUNDRY_WASHER_MODES, supportedLaundryWasherModes, {persist = true}) local event = capabilities.mode.supportedModes(supportedLaundryWasherModes, {visibility = {displayed = false}}) device:emit_component_event(component, event) + event = capabilities.mode.supportedArguments(supportedLaundryWasherModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) end local function laundry_washer_mode_attr_handler(driver, device, ib, response) @@ -307,8 +310,8 @@ local function handle_operational_state_pause(driver, device, cmd) device:send(clusters.OperationalState.attributes.OperationalError:read(device, endpoint_id)) end -local matter_laundry_washer_handler = { - NAME = "matter-laundry-washer", +local matter_laundry_handler = { + NAME = "matter-laundry", lifecycle_handlers = { init = device_init, }, @@ -355,7 +358,7 @@ local matter_laundry_washer_handler = { [capabilities.operationalState.commands.resume.NAME] = handle_operational_state_resume, }, }, - can_handle = is_matter_laundry_washer, + can_handle = is_matter_laundry_device, } -return matter_laundry_washer_handler +return matter_laundry_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua new file mode 100644 index 0000000000..5b5174fcd1 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-microwave-oven/init.lua @@ -0,0 +1,275 @@ +-- Copyright 2024 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 capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local log = require "log" +local version = require "version" + +if version.api < 10 then + clusters.OperationalState = require "OperationalState" +end + +if version.api < 11 then + clusters.MicrowaveOvenControl = require "MicrowaveOvenControl" + clusters.MicrowaveOvenMode = require "MicrowaveOvenMode" +end + +local OPERATIONAL_STATE_COMMAND_MAP = { + [clusters.OperationalState.commands.Pause.ID] = "pause", + [clusters.OperationalState.commands.Stop.ID] = "stop", + [clusters.OperationalState.commands.Start.ID] = "start", + [clusters.OperationalState.commands.Resume.ID] = "resume", +} + +local MICROWAVE_OVEN_DEVICE_TYPE_ID = 0x0079 +local DEFAULT_COOKING_MODE = 0 +local DEFAULT_COOKING_TIME = 30 +local MICROWAVE_OVEN_SUPPORTED_MODES_KEY = "__microwave_oven_supported_modes__" + +local function device_init(driver, device) + device:subscribe() + device:send(clusters.MicrowaveOvenControl.attributes.MaxCookTime:read(device, device.MATTER_DEFAULT_ENDPOINT)) +end + +local function is_matter_mircowave_oven(opts, driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == MICROWAVE_OVEN_DEVICE_TYPE_ID then + return true + end + end + end + return false +end + +local function get_last_set_cooking_parameters(device) + local cookingTime = device:get_latest_state("main", capabilities.cookTime.ID, capabilities.cookTime.cookTime.NAME) or DEFAULT_COOKING_TIME + local cookingMode = device:get_latest_state("main", capabilities.mode.ID, capabilities.mode.mode.ID) + local cookingModeId = DEFAULT_COOKING_MODE + local microwaveOvenModeSupportedModes = device:get_field(MICROWAVE_OVEN_SUPPORTED_MODES_KEY) or {} + for i, mode in ipairs(microwaveOvenModeSupportedModes) do + if cookingMode == mode then + cookingModeId = i - 1 + break + end + end + return cookingTime, cookingModeId +end + +local function operational_state_accepted_command_list_attr_handler(driver, device, ib, response) + log.info_with({ hub_logs = true }, + string.format("operational_state_accepted_command_list_attr_handler: %s", ib.data.elements)) + + local accepted_command_list = {} + for _, accepted_command in ipairs(ib.data.elements) do + local accepted_command_id = accepted_command.value + if OPERATIONAL_STATE_COMMAND_MAP[accepted_command_id] then + log.info_with({ hub_logs = true }, + string.format("AcceptedCommand: %s => %s", accepted_command_id, + OPERATIONAL_STATE_COMMAND_MAP[accepted_command_id])) + table.insert(accepted_command_list, OPERATIONAL_STATE_COMMAND_MAP[accepted_command_id]) + end + end + local event = capabilities.operationalState.supportedCommands(accepted_command_list, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function operational_state_attr_handler(driver, device, ib, response) + log.info_with({ hub_logs = true }, + string.format("operational_state_attr_handler operationalState: %s", ib.data.value)) + + local supported_mode = {} + if ib.data.value == clusters.OperationalState.types.OperationalStateEnum.STOPPED then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.stopped()) + supported_mode = device:get_field(MICROWAVE_OVEN_SUPPORTED_MODES_KEY) + elseif ib.data.value == clusters.OperationalState.types.OperationalStateEnum.RUNNING then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.running()) + elseif ib.data.value == clusters.OperationalState.types.OperationalStateEnum.PAUSED then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.paused()) + end + local event = capabilities.mode.supportedModes(supported_mode, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) + event = capabilities.mode.supportedArguments(supported_mode, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) +end + +local function operational_error_attr_handler(driver, device, ib, response) + if version.api < 10 then + clusters.OperationalState.types.ErrorStateStruct:augment_type(ib.data) + end + log.info_with({ hub_logs = true }, + string.format("operational_error_attr_handler errorStateID: %s", ib.data.elements.error_state_id.value)) + local operationalError = ib.data.elements.error_state_id.value + if operationalError == clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_START_OR_RESUME then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.unableToStartOrResume()) + elseif operationalError == clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_COMPLETE_OPERATION then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.unableToCompleteOperation()) + elseif operationalError == clusters.OperationalState.types.ErrorStateEnum.COMMAND_INVALID_IN_STATE then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.operationalState.operationalState.commandInvalidInCurrentState()) + end + if operationalError ~= clusters.OperationalState.types.ErrorStateEnum.NO_ERROR then + local event = capabilities.mode.supportedModes({}, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) + event = capabilities.mode.supportedArguments({}, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) + end +end + +local function microwave_oven_supported_modes_handler(driver, device, ib, response) + local microwaveOvenModeSupportedModes = {} + for _, mode in ipairs(ib.data.elements) do + if version.api < 11 then + clusters.MicrowaveOvenMode.types.ModeOptionStruct:augment_type(mode) + end + log.info_with({hub_logs=true},"Inserting supported microwave mode:", mode.elements.label.value) + table.insert(microwaveOvenModeSupportedModes, mode.elements.label.value) + end + local event = capabilities.mode.supportedModes(microwaveOvenModeSupportedModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) + event = capabilities.mode.supportedArguments(microwaveOvenModeSupportedModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) + device:set_field(MICROWAVE_OVEN_SUPPORTED_MODES_KEY, microwaveOvenModeSupportedModes) +end + +local function microwave_oven_current_mode_handler(driver, device, ib, response) + log.info_with({ hub_logs = true }, + string.format("microwave_oven_current_mode_handler currentMode: %s", ib.data.value)) + + local currentMode = ib.data.value + local microwaveOvenModeSupportedModes = device:get_field(MICROWAVE_OVEN_SUPPORTED_MODES_KEY) or {} + + if microwaveOvenModeSupportedModes[currentMode + 1] then + local mode = microwaveOvenModeSupportedModes[currentMode + 1] + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, capabilities.mode.mode(mode)) + return + end + log.warn(string.format("Microwave oven mode %s not found in supported microwave oven modes", currentMode)) +end + +local function microwave_oven_cook_time_handler(driver, device, ib, response) + local cookingTime = (ib.data.value == 0) and DEFAULT_COOKING_TIME or ib.data.value + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, capabilities.cookTime.cookTime(cookingTime)) +end + +local function microwave_oven_max_cook_time_handler(driver, device, ib, response) + local cook_time_range = { + minimum = 1, + maximum = ib.data.value + } + local event = capabilities.cookTime.cookTimeRange(cook_time_range, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(device.MATTER_DEFAULT_ENDPOINT, event) +end + +--------------------- +--capability handlers +--------------------- +local function update_device_state(device, endpoint) + device:send(clusters.OperationalState.attributes.OperationalState:read(device, endpoint)) + device:send(clusters.OperationalState.attributes.OperationalError:read(device, endpoint)) +end + +local function handle_operational_state_start(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.OperationalState.server.commands.Start(device, endpoint_id)) + update_device_state(device, endpoint_id) +end + +local function handle_operational_state_stop(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.OperationalState.server.commands.Stop(device, endpoint_id)) + update_device_state(device, endpoint_id) +end + +local function handle_operational_state_resume(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.OperationalState.server.commands.Resume(device, endpoint_id)) + update_device_state(device, endpoint_id) +end + +local function handle_operational_state_pause(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + device:send(clusters.OperationalState.server.commands.Pause(device, endpoint_id)) + update_device_state(device, endpoint_id) +end + +local function handle_microwave_oven_mode(driver, device, cmd) + log.info_with({ hub_logs = true }, + string.format("microwave_oven_mode[%s] mode: %s", cmd.component, cmd.args.mode)) + local microwaveOvenModeSupportedModes = device:get_field(MICROWAVE_OVEN_SUPPORTED_MODES_KEY) or {} + for i, mode in ipairs(microwaveOvenModeSupportedModes) do + if cmd.args.mode == mode then + local cookingTime, _ = get_last_set_cooking_parameters(device) + device:send(clusters.MicrowaveOvenControl.commands.SetCookingParameters(device, device.MATTER_DEFAULT_ENDPOINT, + i - 1, + cookingTime)) + return + end + end + log.warn(string.format("Microwave oven mode %s not found in supported modes", cmd.args.mode)) +end + +local function handle_set_cooking_time(driver, device, cmd) + local cookingTime = cmd.args.time + local _, mode_id = get_last_set_cooking_parameters(device) + device:send(clusters.MicrowaveOvenControl.commands.SetCookingParameters(device, device.MATTER_DEFAULT_ENDPOINT, mode_id, + cookingTime)) +end + +local matter_microwave_oven = { + NAME = "matter-microwave-oven", + lifecycle_handlers = { + init = device_init, + }, + matter_handlers = { + attr = { + [clusters.OperationalState.ID] = { + [clusters.OperationalState.attributes.AcceptedCommandList.ID] = + operational_state_accepted_command_list_attr_handler, + [clusters.OperationalState.attributes.OperationalState.ID] = operational_state_attr_handler, + [clusters.OperationalState.attributes.OperationalError.ID] = operational_error_attr_handler, + }, + [clusters.MicrowaveOvenMode.ID] = { + [clusters.MicrowaveOvenMode.attributes.SupportedModes.ID] = microwave_oven_supported_modes_handler, + [clusters.MicrowaveOvenMode.attributes.CurrentMode.ID] = microwave_oven_current_mode_handler, + }, + [clusters.MicrowaveOvenControl.ID] = { + [clusters.MicrowaveOvenControl.attributes.MaxCookTime.ID] = microwave_oven_max_cook_time_handler, + [clusters.MicrowaveOvenControl.attributes.CookTime.ID] = microwave_oven_cook_time_handler, + } + }, + }, + capability_handlers = { + [capabilities.operationalState.ID] = { + [capabilities.operationalState.commands.start.NAME] = handle_operational_state_start, + [capabilities.operationalState.commands.stop.NAME] = handle_operational_state_stop, + [capabilities.operationalState.commands.pause.NAME] = handle_operational_state_pause, + [capabilities.operationalState.commands.resume.NAME] = handle_operational_state_resume, + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = handle_microwave_oven_mode, + }, + [capabilities.cookTime.ID] = { + [capabilities.cookTime.commands.setCookTime.NAME] = handle_set_cooking_time, + }, + }, + supported_capabilities = { + capabilities.operationalState, + capabilities.mode, + capabilities.cookTime + }, + can_handle = is_matter_mircowave_oven, +} + +return matter_microwave_oven diff --git a/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua b/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua new file mode 100644 index 0000000000..d284501551 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/matter-oven/init.lua @@ -0,0 +1,286 @@ +-- Copyright 2024 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 capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local log = require "log" +local version = require "version" +local embedded_cluster_utils = require "embedded-cluster-utils" +local utils = require "st.utils" + +if version.api < 10 then + clusters.TemperatureControl = require "TemperatureControl" +end + +--this cluster is not supported in any releases of the lua libs +clusters.OvenMode = require "OvenMode" + +local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" +local SUPPORTED_OVEN_MODES_MAP = "__supported_oven_modes_map_key_" +local CURRENT_CONFIGURED_UNIT = "__current_configured_unit" + +local OVEN_DEVICE_ID = 0x007B +local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 +local COOK_TOP_DEVICE_TYPE_ID = 0x0078 +local TCC_DEVICE_TYPE_ID = 0x0071 + +-- This is a work around to handle when units for temperatureSetpoint is changed for the App. +-- When units are switched, we will never know the recevied command value is for what unit as the arguments don't contain the unit. +-- So to handle this we assume the following ranges considering usual oven temperatures: +-- 1. if the recieved setpoint command value is in range 127 ~ 260, it is inferred as *C +-- 2. if the received setpoint command value is in range 261 ~ 500, it is inferred as *F +local OVEN_MAX_TEMP_IN_C = 260 +local OVEN_MIN_TEMP_IN_C = 127 + +local setpoint_limit_device_field = { + MIN_TEMP = "MIN_TEMP", + MAX_TEMP = "MAX_TEMP", +} + +local function get_endpoints_for_dt(device, device_type) + local endpoints = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + +local function endpoint_to_component(device, ep) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + for component, endpoint in pairs(map) do + if endpoint == ep then + return component + end + end + return "main" +end + +local function component_to_endpoint(device, component) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + if map[component] then + return map[component] + end + return device.MATTER_DEFAULT_ENDPOINT +end + +-- Lifecycle Handlers -- +local function device_init(driver, device) + device:subscribe() + device:set_endpoint_to_component_fn(endpoint_to_component) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_field(CURRENT_CONFIGURED_UNIT, "C") +end + +local function device_added(driver, device) + -- We assume the following endpoint structure of oven device for now + local cook_surface_endpoints = get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + local cook_top_endpoint = get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID)[1] or device.MATTER_DEFAULT_ENDPOINT + local tcc_endpoints = get_endpoints_for_dt(device, TCC_DEVICE_TYPE_ID) + local componentToEndpointMap = { + ["tccOne"] = tcc_endpoints[1], + ["tccTwo"] = tcc_endpoints[2], + ["cookTop"] = cook_top_endpoint, + ["cookSurfaceOne"] = cook_surface_endpoints[1], + ["cookSurfaceTwo"] = cook_surface_endpoints[2] + } + device:set_field(COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) +end + +local function is_oven_device(opts, driver, device) + local oven_eps = get_endpoints_for_dt(device, OVEN_DEVICE_ID) + if #oven_eps > 0 then + return true + end + return false +end + +-- Matter Handlers -- +local function oven_supported_modes_attr_handler(driver, device, ib, response) + local supportedOvenModesMap = device:get_field(SUPPORTED_OVEN_MODES_MAP) or {} + local supportedOvenModes = {} + for _, mode in ipairs(ib.data.elements) do + clusters.OvenMode.types.ModeOptionStruct:augment_type(mode) + local modeLabel = mode.elements.label.value + log.info("Inserting supported oven mode: "..modeLabel) + table.insert(supportedOvenModes, modeLabel) + end + supportedOvenModesMap[string.format(ib.endpoint_id)] = supportedOvenModes + device:set_field(SUPPORTED_OVEN_MODES_MAP, supportedOvenModesMap, {persist = true}) + local event = capabilities.mode.supportedModes(supportedOvenModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) + local event = capabilities.mode.supportedArguments(supportedOvenModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function oven_mode_attr_handler(driver, device, ib, response) + log.info(string.format("oven_mode_attr_handler currentMode: %s", ib.data.value)) + + local supportedOvenModesMap = device:get_field(SUPPORTED_OVEN_MODES_MAP) or {} + local supportedOvenModes = supportedOvenModesMap[string.format(ib.endpoint_id)] or {} + local currentMode = ib.data.value + if supportedOvenModes[currentMode+1] then + local mode = supportedOvenModes[currentMode+1] + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode)) + return + end + log.warn("oven_mode_attr_handler received unsupported mode for endpoint"..ib.endpoint_id) +end + +local function temperature_setpoint_attr_handler(driver, device, ib, response) + local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER }) + if #tn_eps == 0 then + device.log.warn(string.format("Device does not support TEMPERATURE_NUMBER feature")) + return + end + device.log.info(string.format("temperature_setpoint_attr_handler: %d", ib.data.value)) + + local min_field = string.format("%s-%d", setpoint_limit_device_field.MIN_TEMP, ib.endpoint_id) + local max_field = string.format("%s-%d", setpoint_limit_device_field.MAX_TEMP, ib.endpoint_id) + local min = device:get_field(min_field) or OVEN_MIN_TEMP_IN_C + local max = device:get_field(max_field) or OVEN_MAX_TEMP_IN_C + local temp = ib.data.value / 100.0 + local unit = "C" + local range = { + minimum = min, + maximum = max, + } + + -- Only emit the capability for RPC version >= 5, since unit conversion for + -- range capabilities is only supported in that case. + if version.rpc >= 5 then + device:emit_event_for_endpoint(ib.endpoint_id, + capabilities.temperatureSetpoint.temperatureSetpointRange({value = range, unit = unit},{visibility = {displayed = false}})) + end + + device:emit_event_for_endpoint(ib.endpoint_id, + capabilities.temperatureSetpoint.temperatureSetpoint({value = temp, unit = unit})) +end + +local function setpoint_limit_handler(limit_field) + return function(driver, device, ib, response) + local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER }) + if #tn_eps == 0 then + device.log.warn(string.format("Device does not support TEMPERATURE_NUMBER feature")) + return + end + local field = string.format("%s-%d", limit_field, ib.endpoint_id) + local val = ib.data.value / 100.0 + + -- We clamp the max and min values as per the assumed oven temperature ranges. + if val < OVEN_MIN_TEMP_IN_C or val > OVEN_MAX_TEMP_IN_C then + if limit_field == setpoint_limit_device_field.MIN_TEMP then + val = OVEN_MIN_TEMP_IN_C + else + val = OVEN_MAX_TEMP_IN_C + end + end + + log.info("Setting " .. field .. " to " .. string.format("%s", val)) + device:set_field(field, val, { persist = true }) + end +end + +-- Capability Handlers -- +local function handle_oven_mode(driver, device, cmd) + log.info(string.format("handle_oven_mode mode: %s", cmd.args.mode)) + local ep = component_to_endpoint(device, cmd.component) + local supportedOvenModesMap = device:get_field(SUPPORTED_OVEN_MODES_MAP) or {} + local supportedOvenModes = supportedOvenModesMap[string.format(ep)] or {} + for i, mode in ipairs(supportedOvenModes) do + if cmd.args.mode == mode then + device:send(clusters.OvenMode.commands.ChangeToMode(device, ep, i - 1)) + return + end + end + log.warn("handle_oven_mode received unsupported mode: ".." for endpoint: "..ep) +end + +local function handle_temperature_setpoint(driver, device, cmd) + local tn_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_NUMBER }) + if #tn_eps == 0 then + device.log.warn(string.format("Device does not support TEMPERATURE_NUMBER feature")) + return + end + device.log.info(string.format("handle_temperature_setpoint: %s", cmd.args.setpoint)) + + local value = cmd.args.setpoint + local _, temp_setpoint = device:get_latest_state( + cmd.component, capabilities.temperatureSetpoint.ID, + capabilities.temperatureSetpoint.temperatureSetpoint.NAME, + 0, { value = 0, unit = "C" } + ) + local ep = component_to_endpoint(device, cmd.component) + local min_field = string.format("%s-%d", setpoint_limit_device_field.MIN_TEMP, ep) + local max_field = string.format("%s-%d", setpoint_limit_device_field.MAX_TEMP, ep) + local min = device:get_field(min_field) or OVEN_MIN_TEMP_IN_C + local max = device:get_field(max_field) or OVEN_MAX_TEMP_IN_C + + if value > OVEN_MAX_TEMP_IN_C then + value = utils.f_to_c(value) + end + if value < min or value > max then + log.warn(string.format( + "Invalid setpoint (%s) outside the min (%s) and the max (%s)", + value, min, max + )) + device:emit_event_for_endpoint(ep, capabilities.temperatureSetpoint.temperatureSetpoint(temp_setpoint)) + return + end + + local ep = component_to_endpoint(device, cmd.component) + device:send(clusters.TemperatureControl.commands.SetTemperature(device, ep, utils.round(value * 100), nil)) +end + +local matter_oven_handler = { + NAME = "matter-oven", + lifecycle_handlers = { + init = device_init, + added = device_added, + }, + matter_handlers = { + attr = { + [clusters.TemperatureControl.ID] = { + [clusters.TemperatureControl.attributes.TemperatureSetpoint.ID] = temperature_setpoint_attr_handler, + [clusters.TemperatureControl.attributes.MinTemperature.ID] = setpoint_limit_handler( + setpoint_limit_device_field.MIN_TEMP), + [clusters.TemperatureControl.attributes.MaxTemperature.ID] = setpoint_limit_handler( + setpoint_limit_device_field.MAX_TEMP), + }, + [clusters.OvenMode.ID] = { + [clusters.OvenMode.attributes.SupportedModes.ID] = oven_supported_modes_attr_handler, + [clusters.OvenMode.attributes.CurrentMode.ID] = oven_mode_attr_handler, + }, + }, + }, + capability_handlers = { + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = handle_oven_mode, + }, + [capabilities.temperatureSetpoint.ID] = { + [capabilities.temperatureSetpoint.commands.setTemperatureSetpoint.NAME] = handle_temperature_setpoint, + } + }, + can_handle = is_oven_device, +} + +return matter_oven_handler diff --git a/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua index b0ffa57d31..487c60b515 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-refrigerator/init.lua @@ -142,7 +142,7 @@ local function temperature_setpoint_attr_handler(driver, device, ib, response) minimum = min, maximum = max, } - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureSetpoint.temperatureSetpointRange({value = range, unit = unit})) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureSetpoint.temperatureSetpointRange({value = range, unit = unit}), { visibility = { displayed = false } }) local temp = ib.data.value / 100.0 device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureSetpoint.temperatureSetpoint({value = temp, unit = unit})) @@ -214,6 +214,8 @@ local function refrigerator_tcc_supported_modes_attr_handler(driver, device, ib, device:set_field(SUPPORTED_REFRIGERATOR_TCC_MODES_MAP, supportedRefrigeratorTccModesMap, {persist = true}) local event = capabilities.mode.supportedModes(supportedRefrigeratorTccModes, {visibility = {displayed = false}}) device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportedRefrigeratorTccModes, {visibility = {displayed = false}}) + device:emit_event_for_endpoint(ib.endpoint_id, event) end local function refrigerator_tcc_mode_attr_handler(driver, device, ib, response) diff --git a/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua new file mode 100644 index 0000000000..37129ba025 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/test/test_cook_top.lua @@ -0,0 +1,202 @@ +-- Copyright 2024 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 t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" + +local COOK_TOP_ENDPOINT = 1 +local COOK_SURFACE_ONE_ENDPOINT = 2 +local COOK_SURFACE_TWO_ENDPOINT = 3 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("cook-surface-one-tl-cook-surface-two-tl.yml"), --on an actual device we would switch to this over doConfigure. + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = COOK_TOP_ENDPOINT, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", feature_map = 4}, --OffOnly feature + }, + device_types = { + { device_type_id = 0x0078, device_type_revision = 1 } -- Cook Top + } + }, + { + endpoint_id = COOK_SURFACE_ONE_ENDPOINT, + clusters = { + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 2 }, --Temperature Level + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + { device_type_id = 0x0077, device_type_revision = 1 } -- Cook Surface + } + }, + { + endpoint_id = COOK_SURFACE_TWO_ENDPOINT, + clusters = { + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 2}, --Temperature Level + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0077, device_type_revision = 1 } -- Cook Surface + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureControl.attributes.SelectedTemperatureLevel, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels + } + 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.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Verify device profile update", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure"}) + mock_device:expect_metadata_update({ profile = "cook-surface-one-tl-cook-surface-two-tl" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Assert component to endpoint map", + function() + local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") + assert(component_to_endpoint_map["cookSurfaceOne"] == COOK_SURFACE_ONE_ENDPOINT, "Cook Surface One Endpoint must be 2") + assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, "Cook Surface Two Endpoint must be 3") + end +) + +test.register_message_test( + "Off command should send appropriate commands", + -- we do not test "on" command, as cook-top is supposed to have offOnly feature. + { + { + 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, COOK_TOP_ENDPOINT) + } + } + } +) + +local utf1 = require "st.matter.data_types.UTF8String1" + +test.register_message_test( + "Cook Surface Two: TemperatureControl Supported Levels must be registered and setTemperatureLevel level command should send appropriate command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels:build_test_report_data(mock_device, COOK_SURFACE_TWO_ENDPOINT, {utf1("Level 1"), utf1("Level 2"), utf1("Level 3")}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureLevel.supportedTemperatureLevels({"Level 1", "Level 2", "Level 3"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureLevel", component = "cookSurfaceTwo", command = "setTemperatureLevel", args = {"Level 1"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_TWO_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. + } + }, + } +) + +test.register_message_test( + "MeasuredValue of TemperatureMeasurement clusters should be reported correctly.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 2, 40*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceOne", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 3, 20*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) + } + } +) + +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua new file mode 100644 index 0000000000..7fd12ff1f9 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/test/test_laundry_dryer.lua @@ -0,0 +1,538 @@ +-- Copyright 2024 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 t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local version = require "version" + +if version.api < 10 then + clusters.LaundryWasherMode = require "LaundryWasherMode" + clusters.OperationalState = require "OperationalState" + clusters.TemperatureControl = require "TemperatureControl" +end + +local APPLICATION_ENDPOINT = 1 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("laundry-dryer-tn-tl.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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, + }, + { cluster_id = clusters.LaundryWasherMode.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.OperationalState.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 3}, + }, + device_types = { + { device_type_id = 0x007C, device_type_revision = 1 } -- LaundryDryer + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LaundryWasherMode.attributes.CurrentMode, + clusters.LaundryWasherMode.attributes.SupportedModes, + clusters.OperationalState.attributes.OperationalState, + clusters.OperationalState.attributes.OperationalError, + clusters.OperationalState.attributes.AcceptedCommandList, + clusters.TemperatureControl.attributes.TemperatureSetpoint, + clusters.TemperatureControl.attributes.MaxTemperature, + clusters.TemperatureControl.attributes.MinTemperature, + clusters.TemperatureControl.attributes.SelectedTemperatureLevel, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels + } + 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( + "Off command should send appropriate commands", + -- we do not test "on" command, as laundry devices are supposed to have offOnly feature. + { + { + 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, APPLICATION_ENDPOINT) + } + } + } +) + +test.register_message_test( + "Setting operationalState command to 'start' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "start", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Start(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.RUNNING) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.running()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "Setting operationalState command to 'stop' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "stop", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Stop(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.STOPPED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.stopped()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "Setting operationalState command to 'pause' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "pause", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Pause(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.PAUSED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.paused()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "On receiving OperationalError, the appropriate operationalState event must be emitted", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_START_OR_RESUME, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.unableToStartOrResume()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_COMPLETE_OPERATION, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.unableToCompleteOperation()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.COMMAND_INVALID_IN_STATE, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.commandInvalidInCurrentState()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "Laundry Washer Suppoerted Modes must be registered and Laundry Washer Mode command should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LaundryWasherMode.attributes.SupportedModes:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + { + clusters.LaundryWasherMode.types.ModeOptionStruct({ ["label"] = "Quick", ["mode"] = 0, ["mode_tags"] = {} }), + clusters.LaundryWasherMode.types.ModeOptionStruct({ ["label"] = "Super Dry", ["mode"] = 1, ["mode_tags"] = {} }) + } + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({"Quick", "Super Dry"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({"Quick", "Super Dry"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = {"Quick"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.LaundryWasherMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, 0) --0 is the index where Quick is stored. + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = {"Super Dry"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.LaundryWasherMode.server.commands.ChangeToMode(mock_device, APPLICATION_ENDPOINT, 1) --1 is the index where Super Dry is stored. + } + } + } +) + +local utf1 = require "st.matter.data_types.UTF8String1" + +test.register_message_test( + "TemperatureControl Supported Levels must be registered and setTemperatureLevel level command should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels:build_test_report_data(mock_device, APPLICATION_ENDPOINT, {utf1("Level 1"), utf1("Level 2"), utf1("Level 3")}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureLevel.supportedTemperatureLevels({"Level 1", "Level 2", "Level 3"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureLevel", component = "main", command = "setTemperatureLevel", args = {"Level 1"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.server.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. + } + }, + } +) + +test.register_message_test( + "temperatureSetpoint command should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.MinTemperature:build_test_report_data(mock_device, APPLICATION_ENDPOINT, 0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.MaxTemperature:build_test_report_data(mock_device, APPLICATION_ENDPOINT, 10000) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.TemperatureSetpoint:build_test_report_data(mock_device, APPLICATION_ENDPOINT, 9000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureSetpoint.temperatureSetpointRange({value = {minimum=0.0,maximum=100.0}, unit = "C"})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureSetpoint.temperatureSetpoint({value = 90.0, unit = "C"})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureSetpoint", component = "main", command = "setTemperatureSetpoint", args = {40.0}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.commands.SetTemperature(mock_device, APPLICATION_ENDPOINT, 40 * 100, nil) + } + }, + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-appliance/src/test/test_matter_extractor_hood.lua b/drivers/SmartThings/matter-appliance/src/test/test_matter_extractor_hood.lua new file mode 100644 index 0000000000..e8aa17162c --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/test/test_matter_extractor_hood.lua @@ -0,0 +1,580 @@ +-- Copyright 2024 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 mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("extractor-hood-hepa-ac-wind.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.HepaFilterMonitoring.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.ActivatedCarbonFilterMonitoring.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.FanControl.ID, cluster_type = "SERVER", feature_map = clusters.FanControl.types.FanControlFeature.WIND}, + }, + device_types = { + {device_type_id = 0x007A, device_type_revision = 1} -- Extractor Hood + } + } + } +}) + +local function test_init() + local subscribed_attributes = { + [capabilities.fanMode.ID] = { + clusters.FanControl.attributes.FanModeSequence, + clusters.FanControl.attributes.FanMode + }, + [capabilities.fanSpeedPercent.ID] = { + clusters.FanControl.attributes.PercentCurrent + }, + [capabilities.windMode.ID] = { + clusters.FanControl.attributes.WindSupport, + clusters.FanControl.attributes.WindSetting + }, + [capabilities.filterState.ID] = { + clusters.HepaFilterMonitoring.attributes.Condition, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition + }, + [capabilities.filterStatus.ID] = { + clusters.HepaFilterMonitoring.attributes.ChangeIndication, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication + } + } + local subscribe_request = nil + for _, attributes in pairs(subscribed_attributes) do + for _, attribute in ipairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device) + else + subscribe_request:merge(attribute:subscribe(mock_device)) + end + 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( + "Test fan percent", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.server.attributes.PercentCurrent:build_test_report_data(mock_device, 1, 10) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanSpeedPercent.percent(10)) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanSpeedPercent", component = "main", command = "setPercent", args = { 50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.PercentSetting:write(mock_device, 1, 50) + } + } + } +) + +test.register_message_test( + "Test fan mode matter handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.types.FanModeEnum.OFF) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("off")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.types.FanModeEnum.LOW) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("low")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.types.FanModeEnum.MEDIUM) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("medium")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.types.FanModeEnum.HIGH) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("high")) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:build_test_report_data(mock_device, 1, clusters.FanControl.types.FanModeEnum.AUTO) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.fanMode("auto")) + } + } +) + +test.register_message_test( + "Test fan mode capability handler", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanMode", component = "main", command = "setFanMode", args = { capabilities.fanMode.fanMode.off.NAME } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.OFF) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanMode", component = "main", command = "setFanMode", args = { capabilities.fanMode.fanMode.low.NAME } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.LOW) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanMode", component = "main", command = "setFanMode", args = { capabilities.fanMode.fanMode.medium.NAME } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.MEDIUM) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanMode", component = "main", command = "setFanMode", args = { capabilities.fanMode.fanMode.high.NAME } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.HIGH) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "fanMode", component = "main", command = "setFanMode", args = { capabilities.fanMode.fanMode.auto.NAME } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.FanMode:write(mock_device, 1, clusters.FanControl.types.FanModeEnum.AUTO) + } + } + } +) +test.register_message_test( + "Test setting fan mode sequence", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.medium.NAME, + capabilities.fanMode.fanMode.high.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.high.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_LOW_MED_HIGH_AUTO) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.medium.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_LOW_HIGH_AUTO) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.low.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_HIGH_AUTO) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.high.NAME, + capabilities.fanMode.fanMode.auto.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.FanModeSequence:build_test_report_data(mock_device, 1, clusters.FanControl.attributes.FanModeSequence.OFF_HIGH) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.fanMode.supportedFanModes({ + capabilities.fanMode.fanMode.off.NAME, + capabilities.fanMode.fanMode.high.NAME + }, {visibility={displayed=false}})) + } + } +) + +test.register_message_test( + "Test wind mode matter handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSupport:build_test_report_data(mock_device, 1, 0x02) -- NoWind and NaturalWind + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windMode.supportedWindModes({ + capabilities.windMode.windMode.noWind.NAME, + capabilities.windMode.windMode.naturalWind.NAME + }, {visibility={displayed=false}})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSetting:build_test_report_data(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windMode.windMode.naturalWind()) + } + } +) + +test.register_message_test( + "Test wind mode capability handler", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "windMode", component = "main", command = "setWindMode", args = { "sleepWind" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.SLEEP_WIND) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "windMode", component = "main", command = "setWindMode", args = { "naturalWind" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.FanControl.attributes.WindSetting:write(mock_device, 1, clusters.FanControl.types.WindSettingMask.NATURAL_WIND) + } + } + } +) + +test.register_message_test( + "Test HEPA filter handlers", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.Condition:build_test_report_data(mock_device, 1, 3) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterState.filterLifeRemaining(3)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.HepaFilterMonitoring.attributes.ChangeIndication.OK) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.HepaFilterMonitoring.attributes.ChangeIndication.WARNING) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.HepaFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.HepaFilterMonitoring.attributes.ChangeIndication.CRITICAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("hepaFilter", capabilities.filterStatus.filterStatus.replace()) + }, + } +) + +test.register_message_test( + "Test Activated Carbon filter handlers", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.Condition:build_test_report_data(mock_device, 1, 5) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterState.filterLifeRemaining(5)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.OK) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.WARNING) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.normal()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication:build_test_report_data(mock_device, 1, clusters.ActivatedCarbonFilterMonitoring.attributes.ChangeIndication.CRITICAL) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("activatedCarbonFilter", capabilities.filterStatus.filterStatus.replace()) + }, + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua new file mode 100644 index 0000000000..0f364e4994 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/test/test_microwave_oven.lua @@ -0,0 +1,569 @@ +-- Copyright 2024 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 t_utils = require "integration_test.utils" +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" + +clusters.OperationalState = require "OperationalState" +clusters.MicrowaveOvenControl = require "MicrowaveOvenControl" +clusters.MicrowaveOvenMode = require "MicrowaveOvenMode" + +local APPLICATION_ENDPOINT = 1 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("microwave-oven.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.OperationalState.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.MicrowaveOvenControl.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.MicrowaveOvenMode.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0079, device_type_revision = 1 } -- Microwave Oven + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.OperationalState.attributes.OperationalState, + clusters.OperationalState.attributes.OperationalError, + clusters.OperationalState.attributes.AcceptedCommandList, + clusters.MicrowaveOvenMode.attributes.SupportedModes, + clusters.MicrowaveOvenMode.attributes.CurrentMode, + clusters.MicrowaveOvenControl.attributes.MaxCookTime, + clusters.MicrowaveOvenControl.attributes.CookTime + } + 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.socket.matter:__expect_send({ mock_device.id, clusters.MicrowaveOvenControl.attributes.MaxCookTime:read( + mock_device, APPLICATION_ENDPOINT) }) + test.mock_device.add_test_device(mock_device) +end + +local function init_supported_microwave_oven_modes() + test.socket.matter:__queue_receive({ + mock_device.id, + clusters.MicrowaveOvenMode.attributes.SupportedModes:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + { + clusters.MicrowaveOvenMode.types.ModeOptionStruct({ + ["label"] = "Grill", + ["mode"] = 0, + ["mode_tags"] = { + clusters.MicrowaveOvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.MicrowaveOvenMode.types.ModeOptionStruct({ + ["label"] = "Pre Heat", + ["mode"] = 1, + ["mode_tags"] = { + clusters.MicrowaveOvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + } + ) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.supportedModes({ "Grill", "Pre Heat" }, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.mode.supportedArguments({ "Grill", "Pre Heat" }, {visibility={displayed=false}}))) +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Setting operationalState command to 'start' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "start", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Start(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.RUNNING) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.running()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({},{visibility={displayed=false}})) + -- Prevent user from changing modes in between an operation. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({},{visibility={displayed=false}})) + -- Prevent user from changing modes in between an operation. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "Setting operationalState command to 'stop' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "stop", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Stop(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.STOPPED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.stopped()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({ "Grill", "Pre Heat" },{visibility={displayed=false}})) + --When operation is stopped, enable mode options for user to choose. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({ "Grill", "Pre Heat" },{visibility={displayed=false}})) + --When operation is stopped, enable mode options for user to choose. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + }, + { + test_init = function() + test_init() + init_supported_microwave_oven_modes() + end + } +) + +test.register_message_test( + "Setting operationalState command to 'pause' should send appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "operationalState", component = "main", command = "pause", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.server.commands.Pause(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:read(mock_device, APPLICATION_ENDPOINT) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalState:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.OperationalStateEnum.PAUSED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.operationalState.operationalState.paused()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({},{visibility={displayed=false}})) + -- Prevent user from changing modes in between an operation. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({},{visibility={displayed=false}})) + -- Prevent user from changing modes in between an operation. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "On receiving OperationalError, the appropriate operationalState event must be emitted", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_START_OR_RESUME, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.unableToStartOrResume()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.UNABLE_TO_COMPLETE_OPERATION, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.unableToCompleteOperation()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.COMMAND_INVALID_IN_STATE, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.operationalState.operationalState.commandInvalidInCurrentState()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({},{visibility={displayed=false}})) + -- Prevent user from changing mode in event of error. + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OperationalState.attributes.OperationalError:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + clusters.OperationalState.types.ErrorStateStruct({ + ["error_state_id"] = clusters.OperationalState.types.ErrorStateEnum.NO_ERROR, + ["error_state_label"] = "", + ["error_state_details"] = "" + })) + } + }, -- on receiving NO ERROR we don't do anything. + } +) + +test.register_message_test( + "The cookTimeRange value should be set on receiving MaxCookTime", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.MicrowaveOvenControl.attributes.MaxCookTime:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + 900) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.cookTime.cookTimeRange({ + minimum = 1, --minimum should be 1. + maximum = 900 + },{visibility={displayed=false}})) + }, + } +) + +test.register_message_test( + "This test case checks for the following events:\n1. Report cookTime value of 30 seconds.\n2. MicrowaveOven supportedModes must be registered.\n3. Setting oven mode and cookTime should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.MicrowaveOvenControl.attributes.CookTime:build_test_report_data(mock_device, APPLICATION_ENDPOINT, 30) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.cookTime.cookTime(30)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.MicrowaveOvenMode.attributes.SupportedModes:build_test_report_data(mock_device, APPLICATION_ENDPOINT, + { + clusters.MicrowaveOvenMode.types.ModeOptionStruct({ + ["label"] = "Grill", + ["mode"] = 0, + ["mode_tags"] = { + clusters.MicrowaveOvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.MicrowaveOvenMode.types.ModeOptionStruct({ + ["label"] = "Pre Heat", + ["mode"] = 1, + ["mode_tags"] = { + clusters.MicrowaveOvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + } + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedModes({ "Grill", "Pre Heat" }, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.mode.supportedArguments({ "Grill", "Pre Heat" }, {visibility={displayed=false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = { "Grill" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.MicrowaveOvenControl.commands.SetCookingParameters(mock_device, APPLICATION_ENDPOINT, + 0, --Index where Grill is stored + 30) --30 since that was the last received cookTime. + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "cookTime", component = "main", command = "setCookTime", args = { 300 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.MicrowaveOvenControl.commands.SetCookingParameters(mock_device, APPLICATION_ENDPOINT, + 0, --> Grill, as this was the last set microwave oven mode. + 300) + } + }, + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-appliance/src/test/test_oven.lua b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua new file mode 100644 index 0000000000..e57e149c68 --- /dev/null +++ b/drivers/SmartThings/matter-appliance/src/test/test_oven.lua @@ -0,0 +1,528 @@ +-- Copyright 2024 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local OVEN_ENDPOINT = 1 +local OVEN_TCC_ONE_ENDPOINT = 2 +local OVEN_TCC_TWO_ENDPOINT = 3 +local COOK_TOP_ENDPOINT = 4 +local COOK_SURFACE_ONE_ENDPOINT = 5 +local COOK_SURFACE_TWO_ENDPOINT = 6 + +clusters.OvenMode = require "OvenMode" +clusters.TemperatureControl = require "TemperatureControl" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("oven-cabinet-one-tn-cabinet-two-tl-cook-top-cook-surface-one-tl-cook-surface-two-tl.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = OVEN_ENDPOINT, + clusters = {}, + device_types = { + { device_type_id = 0x007B, device_type_revision = 1 } -- Oven + } + }, + { + endpoint_id = OVEN_TCC_ONE_ENDPOINT, + clusters = { + { cluster_id = clusters.OvenMode.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 1 }, --Temperature Number + }, + device_types = { + { device_type_id = 0x0071, device_type_revision = 1 } -- Oven TCC + } + }, + { + endpoint_id = OVEN_TCC_TWO_ENDPOINT, + clusters = { + { cluster_id = clusters.OvenMode.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 2 }, --Temperature Level + }, + device_types = { + { device_type_id = 0x0071, device_type_revision = 1 } -- Oven TCC + } + }, + { + endpoint_id = COOK_TOP_ENDPOINT, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", feature_map = 4 }, --OffOnly feature + }, + device_types = { + { device_type_id = 0x0078, device_type_revision = 1 } -- Cook Top + } + }, + { + endpoint_id = COOK_SURFACE_ONE_ENDPOINT, + clusters = { + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 2 }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0077, device_type_revision = 1 } -- Cook Surface + } + }, + { + endpoint_id = COOK_SURFACE_TWO_ENDPOINT, + clusters = { + { cluster_id = clusters.TemperatureControl.ID, cluster_type = "SERVER", feature_map = 2 }, + { cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0077, device_type_revision = 1 } -- Cook Surface + } + } + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.TemperatureMeasurement.attributes.MeasuredValue, + clusters.TemperatureControl.attributes.TemperatureSetpoint, + clusters.TemperatureControl.attributes.MaxTemperature, + clusters.TemperatureControl.attributes.MinTemperature, + clusters.TemperatureControl.attributes.SelectedTemperatureLevel, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels, + clusters.OvenMode.attributes.CurrentMode, + clusters.OvenMode.attributes.SupportedModes, + } + 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.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.set_rpc_version(5) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Assert component to endpoint map", + function() + local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") + assert(component_to_endpoint_map["tccOne"] == OVEN_TCC_ONE_ENDPOINT, "Oven TCC One Endpoint must be 2") + assert(component_to_endpoint_map["tccTwo"] == OVEN_TCC_TWO_ENDPOINT, "Oven TCC Two Endpoint must be 3") + assert(component_to_endpoint_map["cookTop"] == COOK_TOP_ENDPOINT, "Cook Top Endpoint must be 4") + assert(component_to_endpoint_map["cookSurfaceOne"] == COOK_SURFACE_ONE_ENDPOINT, + "Cook Surface One Endpoint must be 5") + assert(component_to_endpoint_map["cookSurfaceTwo"] == COOK_SURFACE_TWO_ENDPOINT, + "Cook Surface Two Endpoint must be 6") + end +) + + +test.register_message_test( + "Oven TCC One: This test case checks for the following events:\n1. Oven supportedModes must be registered.\n2. Setting Oven mode should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OvenMode.attributes.SupportedModes:build_test_report_data(mock_device, OVEN_TCC_ONE_ENDPOINT, + { + clusters.OvenMode.types.ModeOptionStruct({ + ["label"] = "Grill", + ["mode"] = 0, + ["mode_tags"] = { + clusters.OvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.OvenMode.types.ModeOptionStruct({ + ["label"] = "Pre Heat", + ["mode"] = 1, + ["mode_tags"] = { + clusters.OvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + } + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccOne", + capabilities.mode.supportedModes({ "Grill", "Pre Heat" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccOne", + capabilities.mode.supportedArguments({ "Grill", "Pre Heat" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "tccOne", command = "setMode", args = { "Grill" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OvenMode.commands.ChangeToMode(mock_device, OVEN_TCC_ONE_ENDPOINT, 0) --Index where Grill is stored) + } + } + } +) + +test.register_message_test( + "First Oven TCC: MeasuredValue of TemperatureMeasurement clusters should be reported correctly.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, OVEN_TCC_ONE_ENDPOINT, 40*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccOne", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) + } + } +) + +test.register_message_test( + "First Oven TCC: Verify temperatureSetpoint command sends the appropriate commands.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.MinTemperature:build_test_report_data(mock_device, OVEN_TCC_ONE_ENDPOINT, 12800) --128*C + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.MaxTemperature:build_test_report_data(mock_device, OVEN_TCC_ONE_ENDPOINT, 20000) --200*C + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.TemperatureSetpoint:build_test_report_data(mock_device, OVEN_TCC_ONE_ENDPOINT, 13000) --130*C + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccOne", capabilities.temperatureSetpoint.temperatureSetpointRange({value = {minimum=128.0,maximum=200.0}, unit = "C"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccOne", capabilities.temperatureSetpoint.temperatureSetpoint({value = 130.0, unit = "C"})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureSetpoint", component = "tccOne", command = "setTemperatureSetpoint", args = {130.0}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.commands.SetTemperature(mock_device, OVEN_TCC_ONE_ENDPOINT, 130 * 100, nil) + } + }, + } +) + +test.register_message_test( + "Oven TCC Two: This test case checks for the following events:\n1. Oven supportedModes must be registered.\n2. Setting Oven mode should send appropriate commands", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OvenMode.attributes.SupportedModes:build_test_report_data(mock_device, OVEN_TCC_TWO_ENDPOINT, + { + clusters.OvenMode.types.ModeOptionStruct({ + ["label"] = "Grill", + ["mode"] = 0, + ["mode_tags"] = { + clusters.OvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.OvenMode.types.ModeOptionStruct({ + ["label"] = "Pre Heat", + ["mode"] = 1, + ["mode_tags"] = { + clusters.OvenMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + } + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccTwo", + capabilities.mode.supportedModes({ "Grill", "Pre Heat" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccTwo", + capabilities.mode.supportedArguments({ "Grill", "Pre Heat" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "tccTwo", command = "setMode", args = { "Pre Heat" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OvenMode.commands.ChangeToMode(mock_device, OVEN_TCC_TWO_ENDPOINT, 1) --Index where Pre Heat is stored + } + } + } +) + +test.register_message_test( + "Oven TCC Two: MeasuredValue of TemperatureMeasurement clusters should be reported correctly.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, OVEN_TCC_TWO_ENDPOINT, 50*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccTwo", capabilities.temperatureMeasurement.temperature({ value = 50.0, unit = "C" })) + } + } +) + +local utf1 = require "st.matter.data_types.UTF8String1" + +test.register_message_test( + "Second Oven TCC: TemperatureControl Supported Levels must be registered and setTemperatureLevel level command should send appropriate command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels:build_test_report_data(mock_device, OVEN_TCC_TWO_ENDPOINT, {utf1("Level 1"), utf1("Level 2"), utf1("Level 3")}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("tccTwo", capabilities.temperatureLevel.supportedTemperatureLevels({"Level 1", "Level 2", "Level 3"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureLevel", component = "tccTwo", command = "setTemperatureLevel", args = {"Level 1"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.server.commands.SetTemperature(mock_device, OVEN_TCC_TWO_ENDPOINT, nil, 0) --0 is the index where Level1 is stored. + } + }, + } +) + +test.register_message_test( + "Cook Top: Off command should send appropriate commands", + -- we do not test "on" command, as cook-top is supposed to have offOnly feature. + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "cookTop", command = "off", args = {} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, COOK_TOP_ENDPOINT) + } + } + } +) + +test.register_message_test( + "Cook Surface One: TemperatureControl Supported Levels must be registered and setTemperatureLevel level command should send appropriate command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels:build_test_report_data(mock_device, COOK_SURFACE_ONE_ENDPOINT, {utf1("Level 2"), utf1("Level 4"), utf1("Level 5")}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceOne", capabilities.temperatureLevel.supportedTemperatureLevels({"Level 2", "Level 4", "Level 5"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureLevel", component = "cookSurfaceOne", command = "setTemperatureLevel", args = {"Level 5"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_ONE_ENDPOINT, nil, 2) -- 2 is the index where Level 5 is stored. + } + }, + } +) + +test.register_message_test( + "Cook Surface Two: TemperatureControl Supported Levels must be registered and setTemperatureLevel level command should send appropriate command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureControl.attributes.SupportedTemperatureLevels:build_test_report_data(mock_device, COOK_SURFACE_TWO_ENDPOINT, {utf1("Level 3"), utf1("Level 4"), utf1("Level 5")}) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureLevel.supportedTemperatureLevels({"Level 3", "Level 4", "Level 5"}, {visibility = {displayed = false}})) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "temperatureLevel", component = "cookSurfaceTwo", command = "setTemperatureLevel", args = {"Level 4"}} + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.TemperatureControl.server.commands.SetTemperature(mock_device, COOK_SURFACE_TWO_ENDPOINT, nil, 1) -- 1 is the index where Level 4 is stored. + } + }, + } +) + +test.register_message_test( + "Cook Surface One: MeasuredValue of TemperatureMeasurement clusters should be reported correctly.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, COOK_SURFACE_ONE_ENDPOINT, 40*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceOne", capabilities.temperatureMeasurement.temperature({ value = 40.0, unit = "C" })) + } + } +) + +test.register_message_test( + "Cook Surface Two: MeasuredValue of TemperatureMeasurement clusters should be reported correctly.", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.TemperatureMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, COOK_SURFACE_TWO_ENDPOINT, 20*100) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("cookSurfaceTwo", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) + } + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-evse/config.yml b/drivers/SmartThings/matter-evse/config.yml new file mode 100644 index 0000000000..611e385a09 --- /dev/null +++ b/drivers/SmartThings/matter-evse/config.yml @@ -0,0 +1,6 @@ +name: 'Matter Evse' +packageKey: 'matter-evse' +permissions: + matter: {} +description: "SmartThings driver for Matter EVSE devices" +vendorSupportInformation: "https://support.smartthings.com" diff --git a/drivers/SmartThings/matter-evse/fingerprints.yml b/drivers/SmartThings/matter-evse/fingerprints.yml new file mode 100644 index 0000000000..6da5bcfe7c --- /dev/null +++ b/drivers/SmartThings/matter-evse/fingerprints.yml @@ -0,0 +1,7 @@ +matterGeneric: + - id: "matter/evse" + deviceLabel: Matter EVSE + deviceTypes: + - id: 0x0510 + - id: 0x050C + deviceProfileName: evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml new file mode 100644 index 0000000000..8270dd9b3a --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-energy-mgmt-mode.yml @@ -0,0 +1,27 @@ +#EVSE with Electrical Energy Measurement Cluster in Electrical Sensor DT and Device Energy Manage Mode in Energy Management DT +name: evse-energy-meas-energy-mgmt-mode +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +- id: deviceEnergyManagement + label: Energy Manager + capabilities: + - id: mode + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml new file mode 100644 index 0000000000..49a6ce9814 --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml @@ -0,0 +1,32 @@ +#EVSE with Electrical Power Measurement & Electrical Energy Measurement Cluster in Electrical Sensor DT and Device Energy Management mode in Energy Management DT +name: evse-energy-meas-power-meas-energy-mgmt-mode +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +- id: electricalSensor + label: Electrical Sensor + capabilities: + - id: powerSource + version: 1 +- id: deviceEnergyManagement + label: Energy Manager + capabilities: + - id: mode + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml new file mode 100644 index 0000000000..3cb332168c --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse-energy-meas.yml @@ -0,0 +1,22 @@ +#EVSE with Electrical Energy Measurement Cluster in Electrical Sensor +name: evse-energy-meas +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml new file mode 100644 index 0000000000..e389346d82 --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse-power-meas-energy-mgmt-mode.yml @@ -0,0 +1,30 @@ +#EVSE with Electrical Power Measurement Cluster in Electrical Sensor DT and Device Energy Management mode in Energy Management DT +name: evse-power-meas-energy-mgmt-mode +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +- id: electricalSensor + label: Electrical Sensor + capabilities: + - id: powerSource + version: 1 +- id: deviceEnergyManagement + label: Energy Manager + capabilities: + - id: mode + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml b/drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml new file mode 100644 index 0000000000..9ec54d4140 --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse-power-meas.yml @@ -0,0 +1,25 @@ +#EVSE with Electrical Power Measurement Cluster in Electrical Sensor DT +name: evse-power-meas +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +- id: electricalSensor + label: Electrical Sensor + capabilities: + - id: powerSource + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/profiles/evse.yml b/drivers/SmartThings/matter-evse/profiles/evse.yml new file mode 100644 index 0000000000..e20aed42c9 --- /dev/null +++ b/drivers/SmartThings/matter-evse/profiles/evse.yml @@ -0,0 +1,19 @@ +name: evse +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua new file mode 100644 index 0000000000..2893282bb2 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/init.lua @@ -0,0 +1,86 @@ +local cluster_base = require "st.matter.cluster_base" +local DeviceEnergyManagementModeServerAttributes = require "DeviceEnergyManagementMode.server.attributes" +local DeviceEnergyManagementModeServerCommands = require "DeviceEnergyManagementMode.server.commands" +local DeviceEnergyManagementModeTypes = require "DeviceEnergyManagementMode.types" + + +local DeviceEnergyManagementMode = {} + +DeviceEnergyManagementMode.ID = 0x009F +DeviceEnergyManagementMode.NAME = "DeviceEnergyManagementMode" +DeviceEnergyManagementMode.server = {} +DeviceEnergyManagementMode.server.attributes = DeviceEnergyManagementModeServerAttributes:set_parent_cluster(DeviceEnergyManagementMode) +DeviceEnergyManagementMode.server.commands = DeviceEnergyManagementModeServerCommands:set_parent_cluster(DeviceEnergyManagementMode) +DeviceEnergyManagementMode.types = DeviceEnergyManagementModeTypes +DeviceEnergyManagementMode.FeatureMap = DeviceEnergyManagementMode.types.Feature + +function DeviceEnergyManagementMode.are_features_supported(feature, feature_map) + if (DeviceEnergyManagementMode.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +function DeviceEnergyManagementMode:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SupportedModes", + [0x0001] = "CurrentMode", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function DeviceEnergyManagementMode:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ChangeToMode", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +-- Attribute Mapping +DeviceEnergyManagementMode.attribute_direction_map = { + ["SupportedModes"] = "server", + ["CurrentMode"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +-- Command Mapping +DeviceEnergyManagementMode.command_direction_map = { + ["ChangeToMode"] = "server", +} + +-- Cluster Completion +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = DeviceEnergyManagementMode.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, DeviceEnergyManagementMode.NAME)) + end + return DeviceEnergyManagementMode[direction].attributes[key] +end +DeviceEnergyManagementMode.attributes = {} +setmetatable(DeviceEnergyManagementMode.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = DeviceEnergyManagementMode.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, DeviceEnergyManagementMode.NAME)) + end + return DeviceEnergyManagementMode[direction].commands[key] +end +DeviceEnergyManagementMode.commands = {} +setmetatable(DeviceEnergyManagementMode.commands, command_helper_mt) + +setmetatable(DeviceEnergyManagementMode, {__index = cluster_base}) + +return DeviceEnergyManagementMode \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..ad407e23bf --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,81 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AcceptedCommandList.enum_fields = {} + +function AcceptedCommandList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AcceptedCommandList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +return AcceptedCommandList + diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..a441e90e0b --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/AttributeList.lua @@ -0,0 +1,81 @@ + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AttributeList.enum_fields = {} + +function AttributeList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AttributeList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) +end + +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value}) +return AttributeList diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua new file mode 100644 index 0000000000..f2987e294d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/CurrentMode.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentMode = { + ID = 0x0001, + NAME = "CurrentMode", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentMode:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentMode:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentMode, {__call = CurrentMode.new_value, __index = CurrentMode.base_type}) +return CurrentMode diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua new file mode 100644 index 0000000000..69b7e2969a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/SupportedModes.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupportedModes = { + ID = 0x0000, + NAME = "SupportedModes", + base_type = require "st.matter.data_types.Array", + element_type = require "DeviceEnergyManagementMode.types.ModeOptionStruct", +} + +function SupportedModes:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, SupportedModes.element_type) + end +end + +function SupportedModes:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SupportedModes:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupportedModes:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupportedModes:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SupportedModes, {__call = SupportedModes.new_value, __index = SupportedModes.base_type}) +return SupportedModes diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua new file mode 100644 index 0000000000..e814310f7e --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("DeviceEnergyManagementMode.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local DeviceEnergyManagementModeServerAttributes = {} + +function DeviceEnergyManagementModeServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(DeviceEnergyManagementModeServerAttributes, attr_mt) + +return DeviceEnergyManagementModeServerAttributes + diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua new file mode 100644 index 0000000000..488443c91c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/ChangeToMode.lua @@ -0,0 +1,79 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ChangeToMode = {} + +ChangeToMode.NAME = "ChangeToMode" +ChangeToMode.ID = 0x0000 +ChangeToMode.field_defs = { + { + name = "new_mode", + field_id = 0, + optional = false, + nullable = false, + data_type = data_types.Uint8, + }, +} + +function ChangeToMode:init(device, endpoint_id, new_mode) + local out = {} + local args = {new_mode} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ChangeToMode, + __tostring = ChangeToMode.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ChangeToMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ChangeToMode:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function ChangeToMode:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(ChangeToMode, {__call = ChangeToMode.init}) + +return ChangeToMode \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua new file mode 100644 index 0000000000..0ab178d9aa --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("DeviceEnergyManagementMode.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local DeviceEnergyManagementModeServerCommands = {} + +function DeviceEnergyManagementModeServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(DeviceEnergyManagementModeServerCommands, command_mt) + +return DeviceEnergyManagementModeServerCommands + diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua new file mode 100644 index 0000000000..4feed93356 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/Feature.lua @@ -0,0 +1,54 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.ON_OFF = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + ON_OFF = 0x0001, +} + +Feature.is_on_off_set = function(self) + return (self.value & self.ON_OFF) ~= 0 +end + +Feature.set_on_off = function(self) + if self.value ~= nil then + self.value = self.value | self.ON_OFF + else + self.value = self.ON_OFF + end +end + +Feature.unset_on_off = function(self) + self.value = self.value & (~self.ON_OFF & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.ON_OFF + + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_on_off_set = Feature.is_on_off_set, + set_on_off = Feature.set_on_off, + unset_on_off = Feature.unset_on_off, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua new file mode 100644 index 0000000000..caa7734d7c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeOptionStruct.lua @@ -0,0 +1,85 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeOptionStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeOptionStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeOptionStruct.field_defs = { + { + name = "label", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.UTF8String1", + }, + { + name = "mode", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "mode_tags", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Array", + element_type = require "DeviceEnergyManagementMode.types.ModeTagStruct", + }, +} + +ModeOptionStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeOptionStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeOptionStruct.init +new_mt.__index.serialize = ModeOptionStruct.serialize + +ModeOptionStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeOptionStruct, new_mt) + +return ModeOptionStruct diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua new file mode 100644 index 0000000000..1f6d83578a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTag.lua @@ -0,0 +1,33 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ModeTag = {} +local new_mt = UintABC.new_mt({NAME = "ModeTag", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.NO_OPTIMIZATION] = "NO_OPTIMIZATION", + [self.DEVICE_OPTIMIZATION] = "DEVICE_OPTIMIZATION", + [self.LOCAL_OPTIMIZATION] = "LOCAL_OPTIMIZATION", + [self.GRID_OPTIMIZATION] = "GRID_OPTIMIZATION", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.NO_OPTIMIZATION = 0x4000 +new_mt.__index.DEVICE_OPTIMIZATION = 0x4001 +new_mt.__index.LOCAL_OPTIMIZATION = 0x4002 +new_mt.__index.GRID_OPTIMIZATION = 0x4003 + +ModeTag.NO_OPTIMIZATION = 0x4000 +ModeTag.DEVICE_OPTIMIZATION = 0x4001 +ModeTag.LOCAL_OPTIMIZATION = 0x4002 +ModeTag.GRID_OPTIMIZATION = 0x4003 + +ModeTag.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ModeTag, new_mt) + +return ModeTag diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua new file mode 100644 index 0000000000..610fb90ad7 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/ModeTagStruct.lua @@ -0,0 +1,77 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "value", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, +} + +ModeTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeTagStruct.init +new_mt.__index.serialize = ModeTagStruct.serialize + +ModeTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeTagStruct, new_mt) + +return ModeTagStruct diff --git a/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua new file mode 100644 index 0000000000..5f2578800c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/DeviceEnergyManagementMode/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("DeviceEnergyManagementMode.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local DeviceEnergyManagementModeTypes = {} + +setmetatable(DeviceEnergyManagementModeTypes, types_mt) + +return DeviceEnergyManagementModeTypes + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua new file mode 100644 index 0000000000..e50259f69c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/init.lua @@ -0,0 +1,60 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalEnergyMeasurementServerAttributes = require "ElectricalEnergyMeasurement.server.attributes" +local ElectricalEnergyMeasurementTypes = require "ElectricalEnergyMeasurement.types" + +local ElectricalEnergyMeasurement = {} + +ElectricalEnergyMeasurement.ID = 0x0091 +ElectricalEnergyMeasurement.NAME = "ElectricalEnergyMeasurement" +ElectricalEnergyMeasurement.server = {} +ElectricalEnergyMeasurement.server.attributes = ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(ElectricalEnergyMeasurement) +ElectricalEnergyMeasurement.types = ElectricalEnergyMeasurementTypes +ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature + +function ElectricalEnergyMeasurement.are_features_supported(feature, feature_map) + if (ElectricalEnergyMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0002] = "CumulativeEnergyExported", + [0x0004] = "PeriodicEnergyExported", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +-- Attribute Mapping +ElectricalEnergyMeasurement.attribute_direction_map = { + ["CumulativeEnergyImported"] = "server", + ["PeriodicEnergyImported"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +-- Command Mapping +ElectricalEnergyMeasurement.command_direction_map = {} + +-- Cluster Completion +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalEnergyMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalEnergyMeasurement.NAME)) + end + return ElectricalEnergyMeasurement[direction].attributes[key] +end +ElectricalEnergyMeasurement.attributes = {} +setmetatable(ElectricalEnergyMeasurement.attributes, attribute_helper_mt) + +setmetatable(ElectricalEnergyMeasurement, {__index = cluster_base}) + +return ElectricalEnergyMeasurement \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..4c51c12e6a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AcceptedCommandList.enum_fields = {} + +function AcceptedCommandList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AcceptedCommandList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +return AcceptedCommandList + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..e3a71bab0d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/AttributeList.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AttributeList.enum_fields = {} + +function AttributeList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AttributeList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) +end +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value}) +return AttributeList + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua new file mode 100644 index 0000000000..db834c3424 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyImported.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CumulativeEnergyImported = { + ID = 0x0001, + NAME = "CumulativeEnergyImported", + base_type = data_types.Structure, +} + +CumulativeEnergyImported.enum_fields = {} + +function CumulativeEnergyImported:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function CumulativeEnergyImported.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, CumulativeEnergyImported.enum_fields[value_obj.value]) +end +function CumulativeEnergyImported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CumulativeEnergyImported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CumulativeEnergyImported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CumulativeEnergyImported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CumulativeEnergyImported:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CumulativeEnergyImported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CumulativeEnergyImported, {__call = CumulativeEnergyImported.new_value}) +return CumulativeEnergyImported + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua new file mode 100644 index 0000000000..9a70554a7d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyImported.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PeriodicEnergyImported = { + ID = 0x0003, + NAME = "PeriodicEnergyImported", + base_type = data_types.Structure, +} + +PeriodicEnergyImported.enum_fields = {} + +function PeriodicEnergyImported:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function PeriodicEnergyImported.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, PeriodicEnergyImported.enum_fields[value_obj.value]) +end + +function PeriodicEnergyImported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PeriodicEnergyImported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function PeriodicEnergyImported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function PeriodicEnergyImported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PeriodicEnergyImported:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PeriodicEnergyImported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PeriodicEnergyImported, {__call = PeriodicEnergyImported.new_value}) +return PeriodicEnergyImported diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..adfdf42bbf --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ElectricalEnergyMeasurement.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ElectricalEnergyMeasurementServerAttributes = {} + +function ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalEnergyMeasurementServerAttributes, attr_mt) + +return ElectricalEnergyMeasurementServerAttributes + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua new file mode 100644 index 0000000000..99dd2dd52c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -0,0 +1,93 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local energy_mwh = require "st.matter.data_types.Int64" + + +local systime_ms = require "st.matter.data_types.Uint64" + +local EnergyMeasurementStruct = {} +local new_mt = StructureABC.new_mt({NAME = "EnergyMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) + +EnergyMeasurementStruct.field_defs = { + { + data_type = energy_mwh, + field_id = 0, + name = "energy", + }, + { + data_type = data_types.Uint32, + field_id = 1, + name = "start_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 2, + name = "end_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = systime_ms, + field_id = 3, + name = "start_systime", + }, + { + data_type = systime_ms, + field_id = 4, + name = "end_systime", + }, +} + +EnergyMeasurementStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +EnergyMeasurementStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = EnergyMeasurementStruct.init +new_mt.__index.serialize = EnergyMeasurementStruct.serialize + +EnergyMeasurementStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(EnergyMeasurementStruct, new_mt) + +return EnergyMeasurementStruct + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua new file mode 100644 index 0000000000..f8fa8cb516 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/Feature.lua @@ -0,0 +1,103 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.IMPORTED_ENERGY = 0x0001 +Feature.EXPORTED_ENERGY = 0x0002 +Feature.CUMULATIVE_ENERGY = 0x0004 +Feature.PERIODIC_ENERGY = 0x0008 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + IMPORTED_ENERGY = 0x0001, + EXPORTED_ENERGY = 0x0002, + CUMULATIVE_ENERGY = 0x0004, + PERIODIC_ENERGY = 0x0008, +} + +Feature.is_imported_energy_set = function(self) + return (self.value & self.IMPORTED_ENERGY) ~= 0 +end + +Feature.set_imported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.IMPORTED_ENERGY + else + self.value = self.IMPORTED_ENERGY + end +end +Feature.unset_imported_energy = function(self) + self.value = self.value & (~self.IMPORTED_ENERGY & self.BASE_MASK) +end + +Feature.is_exported_energy_set = function(self) + return (self.value & self.EXPORTED_ENERGY) ~= 0 +end + +Feature.set_exported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.EXPORTED_ENERGY + else + self.value = self.EXPORTED_ENERGY + end +end +Feature.unset_exported_energy = function(self) + self.value = self.value & (~self.EXPORTED_ENERGY & self.BASE_MASK) +end + +Feature.is_cumulative_energy_set = function(self) + return (self.value & self.CUMULATIVE_ENERGY) ~= 0 +end + +Feature.set_cumulative_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.CUMULATIVE_ENERGY + else + self.value = self.CUMULATIVE_ENERGY + end +end + +Feature.unset_cumulative_energy = function(self) + self.value = self.value & (~self.CUMULATIVE_ENERGY & self.BASE_MASK) +end + +Feature.is_periodic_energy_set = function(self) + return (self.value & self.PERIODIC_ENERGY) ~= 0 +end + +Feature.set_periodic_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.PERIODIC_ENERGY + else + self.value = self.PERIODIC_ENERGY + end +end +Feature.unset_periodic_energy = function(self) + self.value = self.value & (~self.PERIODIC_ENERGY & self.BASE_MASK) +end + +Feature.mask_methods = { + is_imported_energy_set = Feature.is_imported_energy_set, + set_imported_energy = Feature.set_imported_energy, + unset_imported_energy = Feature.unset_imported_energy, + is_exported_energy_set = Feature.is_exported_energy_set, + set_exported_energy = Feature.set_exported_energy, + unset_exported_energy = Feature.unset_exported_energy, + is_cumulative_energy_set = Feature.is_cumulative_energy_set, + set_cumulative_energy = Feature.set_cumulative_energy, + unset_cumulative_energy = Feature.unset_cumulative_energy, + is_periodic_energy_set = Feature.is_periodic_energy_set, + set_periodic_energy = Feature.set_periodic_energy, + unset_periodic_energy = Feature.unset_periodic_energy, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua new file mode 100644 index 0000000000..984df9c286 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/MeasurementTypeEnum.lua @@ -0,0 +1,51 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local MeasurementTypeEnum = {} +local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.UNSPECIFIED] = "UNSPECIFIED", + [self.VOLTAGE] = "VOLTAGE", + [self.ACTIVE_CURRENT] = "ACTIVE_CURRENT", + [self.REACTIVE_CURRENT] = "REACTIVE_CURRENT", + [self.APPARENT_CURRENT] = "APPARENT_CURRENT", + [self.ACTIVE_POWER] = "ACTIVE_POWER", + [self.REACTIVE_POWER] = "REACTIVE_POWER", + [self.APPARENT_POWER] = "APPARENT_POWER", + [self.RMS_VOLTAGE] = "RMS_VOLTAGE", + [self.RMS_CURRENT] = "RMS_CURRENT", + [self.RMS_POWER] = "RMS_POWER", + [self.FREQUENCY] = "FREQUENCY", + [self.POWER_FACTOR] = "POWER_FACTOR", + [self.NEUTRAL_CURRENT] = "NEUTRAL_CURRENT", + [self.ELECTRICAL_ENERGY] = "ELECTRICAL_ENERGY", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.UNSPECIFIED = 0x00 +new_mt.__index.VOLTAGE = 0x01 +new_mt.__index.ACTIVE_CURRENT = 0x02 +new_mt.__index.REACTIVE_CURRENT = 0x03 +new_mt.__index.APPARENT_CURRENT = 0x04 +new_mt.__index.ACTIVE_POWER = 0x05 +new_mt.__index.REACTIVE_POWER = 0x06 +new_mt.__index.APPARENT_POWER = 0x07 +new_mt.__index.RMS_VOLTAGE = 0x08 +new_mt.__index.RMS_CURRENT = 0x09 +new_mt.__index.RMS_POWER = 0x0A +new_mt.__index.FREQUENCY = 0x0B +new_mt.__index.POWER_FACTOR = 0x0C +new_mt.__index.NEUTRAL_CURRENT = 0x0D +new_mt.__index.ELECTRICAL_ENERGY = 0x0E + +MeasurementTypeEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(MeasurementTypeEnum, new_mt) + +return MeasurementTypeEnum + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua new file mode 100644 index 0000000000..db0e4230bf --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalEnergyMeasurement/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("ElectricalEnergyMeasurement.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local ElectricalEnergyMeasurementTypes = {} + +setmetatable(ElectricalEnergyMeasurementTypes, types_mt) + +return ElectricalEnergyMeasurementTypes + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua new file mode 100644 index 0000000000..cd28c586e2 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/init.lua @@ -0,0 +1,95 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalPowerMeasurementServerAttributes = require "ElectricalPowerMeasurement.server.attributes" +local ElectricalPowerMeasurementServerCommands = require "ElectricalPowerMeasurement.server.commands" +local ElectricalPowerMeasurementTypes = require "ElectricalPowerMeasurement.types" + +local ElectricalPowerMeasurement = {} + +ElectricalPowerMeasurement.ID = 0x0090 +ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" +ElectricalPowerMeasurement.server = {} +ElectricalPowerMeasurement.client = {} +ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) +ElectricalPowerMeasurement.server.commands = ElectricalPowerMeasurementServerCommands:set_parent_cluster(ElectricalPowerMeasurement) +ElectricalPowerMeasurement.types = ElectricalPowerMeasurementTypes +ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature + +function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) + if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "PowerMode", + [0x0008] = "ActivePower", + [0x000A] = "ApparentPower", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function ElectricalPowerMeasurement:get_server_command_by_id(command_id) + local server_id_map = { + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +function ElectricalPowerMeasurement:get_event_by_id(event_id) + local event_id_map = { + [0x0000] = "MeasurementPeriodRanges", + } + if event_id_map[event_id] ~= nil then + return self.server.events[event_id_map[event_id]] + end + return nil +end +-- Attribute Mapping +ElectricalPowerMeasurement.attribute_direction_map = { + ["PowerMode"] = "server", + ["ActivePower"] = "server", + ["ApparentPower"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +-- Command Mapping +ElectricalPowerMeasurement.command_direction_map = { +} + +-- Cluster Completion +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalPowerMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) + end + return ElectricalPowerMeasurement[direction].attributes[key] +end +ElectricalPowerMeasurement.attributes = {} +setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = ElectricalPowerMeasurement.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) + end + return ElectricalPowerMeasurement[direction].commands[key] +end +ElectricalPowerMeasurement.commands = {} +setmetatable(ElectricalPowerMeasurement.commands, command_helper_mt) + +setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) + +return ElectricalPowerMeasurement \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..fd4855a236 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,79 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AcceptedCommandList.enum_fields = {} + +function AcceptedCommandList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AcceptedCommandList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +return AcceptedCommandList diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua new file mode 100644 index 0000000000..16774ae1d6 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local ActivePower = { + ID = 0x0008, + NAME = "ActivePower", + base_type = data_types.Int64, +} + +ActivePower.enum_fields = { +} + +function ActivePower:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function ActivePower.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, ActivePower.enum_fields[value_obj.value]) +end + +function ActivePower:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function ActivePower:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ActivePower:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ActivePower:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ActivePower:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ActivePower:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ActivePower, {__call = ActivePower.new_value}) +return ActivePower diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua new file mode 100644 index 0000000000..40cbcef7eb --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/ApparentPower.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ApparentPower = { + ID = 0x000A, + NAME = "ApparentPower", + base_type = data_types.Int64, +} + +ApparentPower.enum_fields = {} + +function ApparentPower:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function ApparentPower.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, ApparentPower.enum_fields[value_obj.value]) +end + +function ApparentPower:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function ApparentPower:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ApparentPower:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ApparentPower:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ApparentPower:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ApparentPower:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(ApparentPower, {__call = ApparentPower.new_value}) +return ApparentPower diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..eea5f89c63 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/AttributeList.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AttributeList.enum_fields = { +} + +function AttributeList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AttributeList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) +end + +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value}) +return AttributeList diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua new file mode 100644 index 0000000000..4d96c91bd8 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/PowerMode.lua @@ -0,0 +1,86 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PowerMode = { + ID = 0x0000, + NAME = "PowerMode", + base_type = data_types.Uint8, +} +PowerMode.UNKNOWN = 0x00 +PowerMode.DC = 0x01 +PowerMode.AC = 0x02 + +PowerMode.enum_fields = { + [PowerMode.UNKNOWN] = "UNKNOWN", + [PowerMode.DC] = "DC", + [PowerMode.AC] = "AC", +} + +function PowerMode:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function PowerMode.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, PowerMode.enum_fields[value_obj.value]) +end + +function PowerMode:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PowerMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function PowerMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function PowerMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PowerMode:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PowerMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PowerMode, {__call = PowerMode.new_value}) +return PowerMode + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..0c30fa8dd4 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ElectricalPowerMeasurement.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ElectricalPowerMeasurementServerAttributes = {} + +function ElectricalPowerMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalPowerMeasurementServerAttributes, attr_mt) + +return ElectricalPowerMeasurementServerAttributes + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua new file mode 100644 index 0000000000..087bb47a9e --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("ElectricalPowerMeasurement.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local ElectricalPowerMeasurementServerCommands = {} + +function ElectricalPowerMeasurementServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalPowerMeasurementServerCommands, command_mt) + +return ElectricalPowerMeasurementServerCommands + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua new file mode 100644 index 0000000000..414f64e3e8 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/MeasurementPeriodRanges.lua @@ -0,0 +1,98 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local MeasurementRangeStruct = require "ElectricalPowerMeasurement.types.MeasurementRangeStruct" + +local MeasurementPeriodRanges = { + ID = 0x0000, + NAME = "MeasurementPeriodRanges", + base_type = data_types.Structure, +} + +MeasurementPeriodRanges.field_defs = { + { + data_type = MeasurementRangeStruct, + field_id = 0, + is_array = true, + name = "ranges", + is_nullable = false, + is_optional = false, + }, +} + +function MeasurementPeriodRanges:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function MeasurementPeriodRanges:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function MeasurementPeriodRanges:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function MeasurementPeriodRanges:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "MeasurementPeriodRangesEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasurementPeriodRanges:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function MeasurementPeriodRanges:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return MeasurementPeriodRanges diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua new file mode 100644 index 0000000000..eeec54b6f3 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/server/events/init.lua @@ -0,0 +1,24 @@ +local event_mt = {} +event_mt.__event_cache = {} +event_mt.__index = function(self, key) + if event_mt.__event_cache[key] == nil then + local req_loc = string.format("ElectricalPowerMeasurement.server.events.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + event_mt.__event_cache[key] = raw_def + end + return event_mt.__event_cache[key] +end + +local ElectricalPowerMeasurementEvents = {} + +function ElectricalPowerMeasurementEvents:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalPowerMeasurementEvents, event_mt) + +return ElectricalPowerMeasurementEvents + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua new file mode 100644 index 0000000000..2229aebe0c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/Feature.lua @@ -0,0 +1,138 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.DIRECT_CURRENT = 0x0001 +Feature.ALTERNATING_CURRENT = 0x0002 +Feature.POLYPHASE_POWER = 0x0004 +Feature.HARMONICS = 0x0008 +Feature.POWER_QUALITY = 0x0010 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + DIRECT_CURRENT = 0x0001, + ALTERNATING_CURRENT = 0x0002, + POLYPHASE_POWER = 0x0004, + HARMONICS = 0x0008, + POWER_QUALITY = 0x0010, +} + +Feature.is_direct_current_set = function(self) + return (self.value & self.DIRECT_CURRENT) ~= 0 +end + +Feature.set_direct_current = function(self) + if self.value ~= nil then + self.value = self.value | self.DIRECT_CURRENT + else + self.value = self.DIRECT_CURRENT + end +end + +Feature.unset_direct_current = function(self) + self.value = self.value & (~self.DIRECT_CURRENT & self.BASE_MASK) +end +Feature.is_alternating_current_set = function(self) + return (self.value & self.ALTERNATING_CURRENT) ~= 0 +end + +Feature.set_alternating_current = function(self) + if self.value ~= nil then + self.value = self.value | self.ALTERNATING_CURRENT + else + self.value = self.ALTERNATING_CURRENT + end +end + +Feature.unset_alternating_current = function(self) + self.value = self.value & (~self.ALTERNATING_CURRENT & self.BASE_MASK) +end +Feature.is_polyphase_power_set = function(self) + return (self.value & self.POLYPHASE_POWER) ~= 0 +end + +Feature.set_polyphase_power = function(self) + if self.value ~= nil then + self.value = self.value | self.POLYPHASE_POWER + else + self.value = self.POLYPHASE_POWER + end +end + +Feature.unset_polyphase_power = function(self) + self.value = self.value & (~self.POLYPHASE_POWER & self.BASE_MASK) +end +Feature.is_harmonics_set = function(self) + return (self.value & self.HARMONICS) ~= 0 +end + +Feature.set_harmonics = function(self) + if self.value ~= nil then + self.value = self.value | self.HARMONICS + else + self.value = self.HARMONICS + end +end + +Feature.unset_harmonics = function(self) + self.value = self.value & (~self.HARMONICS & self.BASE_MASK) +end +Feature.is_power_quality_set = function(self) + return (self.value & self.POWER_QUALITY) ~= 0 +end + +Feature.set_power_quality = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_QUALITY + else + self.value = self.POWER_QUALITY + end +end + +Feature.unset_power_quality = function(self) + self.value = self.value & (~self.POWER_QUALITY & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.DIRECT_CURRENT | + Feature.ALTERNATING_CURRENT | + Feature.POLYPHASE_POWER | + Feature.HARMONICS | + Feature.POWER_QUALITY + + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_direct_current_set = Feature.is_direct_current_set, + set_direct_current = Feature.set_direct_current, + unset_direct_current = Feature.unset_direct_current, + is_alternating_current_set = Feature.is_alternating_current_set, + set_alternating_current = Feature.set_alternating_current, + unset_alternating_current = Feature.unset_alternating_current, + is_polyphase_power_set = Feature.is_polyphase_power_set, + set_polyphase_power = Feature.set_polyphase_power, + unset_polyphase_power = Feature.unset_polyphase_power, + is_harmonics_set = Feature.is_harmonics_set, + set_harmonics = Feature.set_harmonics, + unset_harmonics = Feature.unset_harmonics, + is_power_quality_set = Feature.is_power_quality_set, + set_power_quality = Feature.set_power_quality, + unset_power_quality = Feature.unset_power_quality, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua new file mode 100644 index 0000000000..1398736a19 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/HarmonicMeasurementStruct.lua @@ -0,0 +1,73 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local HarmonicMeasurementStruct = {} +local new_mt = StructureABC.new_mt({NAME = "HarmonicMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) + +HarmonicMeasurementStruct.field_defs = { + { + data_type = data_types.Uint8, + field_id = 0, + name = "order", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 1, + name = "measurement", + is_nullable = true, + is_optional = true, + }, +} + +HarmonicMeasurementStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +HarmonicMeasurementStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = HarmonicMeasurementStruct.init +new_mt.__index.serialize = HarmonicMeasurementStruct.serialize + +HarmonicMeasurementStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(HarmonicMeasurementStruct, new_mt) + +return HarmonicMeasurementStruct diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua new file mode 100644 index 0000000000..ba03a8b793 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyRangeStruct.lua @@ -0,0 +1,115 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local MeasurementAccuracyRangeStruct = {} +local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyRangeStruct", ID = data_types.name_to_id_map["Structure"]}) + +MeasurementAccuracyRangeStruct.field_defs = { + { + data_type = data_types.Int64, + field_id = 0, + name = "range_min", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 1, + name = "range_max", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint16, + field_id = 2, + name = "percent_max", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint16, + field_id = 3, + name = "percent_min", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint16, + field_id = 4, + name = "percent_typical", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint64, + field_id = 5, + name = "fixed_max", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint64, + field_id = 6, + name = "fixed_min", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint64, + field_id = 7, + name = "fixed_typical", + is_nullable = false, + is_optional = false, + }, +} + +MeasurementAccuracyRangeStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +MeasurementAccuracyRangeStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = MeasurementAccuracyRangeStruct.init +new_mt.__index.serialize = MeasurementAccuracyRangeStruct.serialize + +MeasurementAccuracyRangeStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(MeasurementAccuracyRangeStruct, new_mt) + +return MeasurementAccuracyRangeStruct diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua new file mode 100644 index 0000000000..be596893f6 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementAccuracyStruct.lua @@ -0,0 +1,91 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local MeasurementTypeEnum = require "ElectricalPowerMeasurement.types.MeasurementTypeEnum" +local MeasurementAccuracyRangeStruct = require "ElectricalPowerMeasurement.types.MeasurementAccuracyRangeStruct" +local MeasurementAccuracyStruct = {} +local new_mt = StructureABC.new_mt({NAME = "MeasurementAccuracyStruct", ID = data_types.name_to_id_map["Structure"]}) + +MeasurementAccuracyStruct.field_defs = { + { + data_type = MeasurementTypeEnum, + field_id = 0, + name = "measurement_type", + }, + { + data_type = data_types.Boolean, + field_id = 1, + name = "measured", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 2, + name = "min_measured_value", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 3, + name = "max_measured_value", + is_nullable = false, + is_optional = false, + }, + { + data_type = MeasurementAccuracyRangeStruct, + field_id = 4, + name = "accuracy_ranges", + }, +} + +MeasurementAccuracyStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +MeasurementAccuracyStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = MeasurementAccuracyStruct.init +new_mt.__index.serialize = MeasurementAccuracyStruct.serialize + +MeasurementAccuracyStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(MeasurementAccuracyStruct, new_mt) + +return MeasurementAccuracyStruct diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua new file mode 100644 index 0000000000..d1d7cfd371 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementRangeStruct.lua @@ -0,0 +1,126 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local MeasurementTypeEnum = require "ElectricalPowerMeasurement.types.MeasurementTypeEnum" +local MeasurementRangeStruct = {} +local new_mt = StructureABC.new_mt({NAME = "MeasurementRangeStruct", ID = data_types.name_to_id_map["Structure"]}) + +MeasurementRangeStruct.field_defs = { + { + data_type = MeasurementTypeEnum, + field_id = 0, + name = "measurement_type", + }, + { + data_type = data_types.Int64, + field_id = 1, + name = "min", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 2, + name = "max", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 3, + name = "start_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 4, + name = "end_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 5, + name = "min_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 6, + name = "max_timestamp", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint64, + field_id = 7, + name = "start_systime", + }, + { + data_type = data_types.Uint64, + field_id = 8, + name = "end_systime", + }, + { + data_type = data_types.Uint64, + field_id = 9, + name = "min_systime", + }, + { + data_type = data_types.Uint64, + field_id = 10, + name = "max_systime", + }, +} + +MeasurementRangeStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +MeasurementRangeStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = MeasurementRangeStruct.init +new_mt.__index.serialize = MeasurementRangeStruct.serialize + +MeasurementRangeStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(MeasurementRangeStruct, new_mt) + +return MeasurementRangeStruct \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua new file mode 100644 index 0000000000..984df9c286 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/MeasurementTypeEnum.lua @@ -0,0 +1,51 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local MeasurementTypeEnum = {} +local new_mt = UintABC.new_mt({NAME = "MeasurementTypeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.UNSPECIFIED] = "UNSPECIFIED", + [self.VOLTAGE] = "VOLTAGE", + [self.ACTIVE_CURRENT] = "ACTIVE_CURRENT", + [self.REACTIVE_CURRENT] = "REACTIVE_CURRENT", + [self.APPARENT_CURRENT] = "APPARENT_CURRENT", + [self.ACTIVE_POWER] = "ACTIVE_POWER", + [self.REACTIVE_POWER] = "REACTIVE_POWER", + [self.APPARENT_POWER] = "APPARENT_POWER", + [self.RMS_VOLTAGE] = "RMS_VOLTAGE", + [self.RMS_CURRENT] = "RMS_CURRENT", + [self.RMS_POWER] = "RMS_POWER", + [self.FREQUENCY] = "FREQUENCY", + [self.POWER_FACTOR] = "POWER_FACTOR", + [self.NEUTRAL_CURRENT] = "NEUTRAL_CURRENT", + [self.ELECTRICAL_ENERGY] = "ELECTRICAL_ENERGY", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.UNSPECIFIED = 0x00 +new_mt.__index.VOLTAGE = 0x01 +new_mt.__index.ACTIVE_CURRENT = 0x02 +new_mt.__index.REACTIVE_CURRENT = 0x03 +new_mt.__index.APPARENT_CURRENT = 0x04 +new_mt.__index.ACTIVE_POWER = 0x05 +new_mt.__index.REACTIVE_POWER = 0x06 +new_mt.__index.APPARENT_POWER = 0x07 +new_mt.__index.RMS_VOLTAGE = 0x08 +new_mt.__index.RMS_CURRENT = 0x09 +new_mt.__index.RMS_POWER = 0x0A +new_mt.__index.FREQUENCY = 0x0B +new_mt.__index.POWER_FACTOR = 0x0C +new_mt.__index.NEUTRAL_CURRENT = 0x0D +new_mt.__index.ELECTRICAL_ENERGY = 0x0E + +MeasurementTypeEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(MeasurementTypeEnum, new_mt) + +return MeasurementTypeEnum + diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua new file mode 100644 index 0000000000..f76370acd7 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/PowerModeEnum.lua @@ -0,0 +1,27 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + + +local PowerModeEnum = {} +local new_mt = UintABC.new_mt({NAME = "PowerModeEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.UNKNOWN] = "UNKNOWN", + [self.DC] = "DC", + [self.AC] = "AC", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.UNKNOWN = 0x00 +new_mt.__index.DC = 0x01 +new_mt.__index.AC = 0x02 + +PowerModeEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(PowerModeEnum, new_mt) + +return PowerModeEnum diff --git a/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua new file mode 100644 index 0000000000..658256b316 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/ElectricalPowerMeasurement/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("ElectricalPowerMeasurement.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local ElectricalPowerMeasurementTypes = {} + +setmetatable(ElectricalPowerMeasurementTypes, types_mt) + +return ElectricalPowerMeasurementTypes + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua new file mode 100644 index 0000000000..85e3ddced2 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/init.lua @@ -0,0 +1,162 @@ +local cluster_base = require "st.matter.cluster_base" +local EnergyEvseServerAttributes = require "EnergyEvse.server.attributes" +local EnergyEvseServerCommands = require "EnergyEvse.server.commands" +local EnergyEvseEvents = require "EnergyEvse.server.events" +local EnergyEvseTypes = require "EnergyEvse.types" + +local EnergyEvse = {} + +EnergyEvse.ID = 0x0099 +EnergyEvse.NAME = "EnergyEvse" +EnergyEvse.server = {} +EnergyEvse.client = {} +EnergyEvse.server.attributes = EnergyEvseServerAttributes:set_parent_cluster(EnergyEvse) +EnergyEvse.server.commands = EnergyEvseServerCommands:set_parent_cluster(EnergyEvse) +EnergyEvse.server.events = EnergyEvseEvents:set_parent_cluster(EnergyEvse) +EnergyEvse.types = EnergyEvseTypes +EnergyEvse.FeatureMap = EnergyEvse.types.Feature + +function EnergyEvse.are_features_supported(feature, feature_map) + if (EnergyEvse.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +function EnergyEvse:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "State", + [0x0001] = "SupplyState", + [0x0002] = "FaultState", + [0x0003] = "ChargingEnabledUntil", + [0x0004] = "DischargingEnabledUntil", + [0x0005] = "CircuitCapacity", + [0x0006] = "MinimumChargeCurrent", + [0x0007] = "MaximumChargeCurrent", + [0x0008] = "MaximumDischargeCurrent", + [0x0009] = "UserMaximumChargeCurrent", + [0x000A] = "RandomizationDelayWindow", + [0x0023] = "NextChargeStartTime", + [0x0024] = "NextChargeTargetTime", + [0x0025] = "NextChargeRequiredEnergy", + [0x0026] = "NextChargeTargetSoC", + [0x0027] = "ApproximateEVEfficiency", + [0x0030] = "StateOfCharge", + [0x0031] = "BatteryCapacity", + [0x0032] = "VehicleID", + [0x0040] = "SessionID", + [0x0041] = "SessionDuration", + [0x0042] = "SessionEnergyCharged", + [0x0043] = "SessionEnergyDischarged", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function EnergyEvse:get_server_command_by_id(command_id) + local server_id_map = { + [0x0001] = "Disable", + [0x0002] = "EnableCharging", + [0x0003] = "EnableDischarging", + [0x0004] = "StartDiagnostics", + [0x0005] = "SetTargets", + [0x0006] = "GetTargets", + [0x0007] = "ClearTargets", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +function EnergyEvse:get_event_by_id(event_id) + local event_id_map = { + [0x0000] = "EVConnected", + [0x0001] = "EVNotDetected", + [0x0002] = "EnergyTransferStarted", + [0x0003] = "EnergyTransferStopped", + [0x0004] = "Fault", + [0x0005] = "Rfid", + } + if event_id_map[event_id] ~= nil then + return self.server.events[event_id_map[event_id]] + end + return nil +end +EnergyEvse.attribute_direction_map = { + ["State"] = "server", + ["SupplyState"] = "server", + ["FaultState"] = "server", + ["ChargingEnabledUntil"] = "server", + ["DischargingEnabledUntil"] = "server", + ["CircuitCapacity"] = "server", + ["MinimumChargeCurrent"] = "server", + ["MaximumChargeCurrent"] = "server", + ["MaximumDischargeCurrent"] = "server", + ["UserMaximumChargeCurrent"] = "server", + ["RandomizationDelayWindow"] = "server", + ["NextChargeStartTime"] = "server", + ["NextChargeTargetTime"] = "server", + ["NextChargeRequiredEnergy"] = "server", + ["NextChargeTargetSoC"] = "server", + ["ApproximateEVEfficiency"] = "server", + ["StateOfCharge"] = "server", + ["BatteryCapacity"] = "server", + ["VehicleID"] = "server", + ["SessionID"] = "server", + ["SessionDuration"] = "server", + ["SessionEnergyCharged"] = "server", + ["SessionEnergyDischarged"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +EnergyEvse.command_direction_map = { + ["Disable"] = "server", + ["EnableCharging"] = "server", + ["EnableDischarging"] = "server", + ["StartDiagnostics"] = "server", + ["SetTargets"] = "server", + ["GetTargets"] = "server", + ["ClearTargets"] = "server", +} + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = EnergyEvse.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, EnergyEvse.NAME)) + end + return EnergyEvse[direction].attributes[key] +end +EnergyEvse.attributes = {} +setmetatable(EnergyEvse.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = EnergyEvse.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, EnergyEvse.NAME)) + end + return EnergyEvse[direction].commands[key] +end +EnergyEvse.commands = {} +setmetatable(EnergyEvse.commands, command_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return EnergyEvse.server.events[key] +end +EnergyEvse.events = {} +setmetatable(EnergyEvse.events, event_helper_mt) + +setmetatable(EnergyEvse, {__index = cluster_base}) + +return EnergyEvse \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..2d1bdd9f6a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,79 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32" +} + +AcceptedCommandList.enum_fields = {} + +function AcceptedCommandList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AcceptedCommandList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +return AcceptedCommandList diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua new file mode 100644 index 0000000000..878ebd222b --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ApproximateEVEfficiency.lua @@ -0,0 +1,76 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ApproximateEVEfficiency = { + ID = 0x0027, + NAME = "ApproximateEVEfficiency", + base_type = data_types.Uint16, +} + +function ApproximateEVEfficiency:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function ApproximateEVEfficiency:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ApproximateEVEfficiency:write(device, endpoint_id, value) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.write( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --event_id + data + ) +end + +function ApproximateEVEfficiency:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ApproximateEVEfficiency:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ApproximateEVEfficiency:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ApproximateEVEfficiency:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(ApproximateEVEfficiency, {__call = ApproximateEVEfficiency.new_value}) +return ApproximateEVEfficiency \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..0f66af7c79 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/AttributeList.lua @@ -0,0 +1,81 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32" +} + +AttributeList.enum_fields = { +} + +function AttributeList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AttributeList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) +end + +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value}) +return AttributeList + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua new file mode 100644 index 0000000000..e9bb2c905b --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/BatteryCapacity.lua @@ -0,0 +1,79 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local BatteryCapacity = { + ID = 0x0031, + NAME = "BatteryCapacity", + base_type = data_types.Int64, +} + +BatteryCapacity.enum_fields = { +} + +function BatteryCapacity:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function BatteryCapacity.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, BatteryCapacity.enum_fields[value_obj.value]) +end + +function BatteryCapacity:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function BatteryCapacity:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function BatteryCapacity:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function BatteryCapacity:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function BatteryCapacity:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function BatteryCapacity:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(BatteryCapacity, {__call = BatteryCapacity.new_value}) +return BatteryCapacity diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua new file mode 100644 index 0000000000..30288fba35 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/ChargingEnabledUntil.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ChargingEnabledUntil = { + ID = 0x0003, + NAME = "ChargingEnabledUntil", + base_type = data_types.Uint32, +} + +function ChargingEnabledUntil:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function ChargingEnabledUntil:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ChargingEnabledUntil:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ChargingEnabledUntil:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ChargingEnabledUntil:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ChargingEnabledUntil:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(ChargingEnabledUntil, {__call = ChargingEnabledUntil.new_value}) +return ChargingEnabledUntil diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua new file mode 100644 index 0000000000..58cc7cc911 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/CircuitCapacity.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CircuitCapacity = { + ID = 0x0005, + NAME = "CircuitCapacity", + base_type = data_types.Int64, +} + +CircuitCapacity.enum_fields = { +} + +function CircuitCapacity:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function CircuitCapacity.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, CircuitCapacity.enum_fields[value_obj.value]) +end + +function CircuitCapacity:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CircuitCapacity:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CircuitCapacity:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CircuitCapacity:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CircuitCapacity:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CircuitCapacity:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CircuitCapacity, {__call = CircuitCapacity.new_value}) +return CircuitCapacity + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua new file mode 100644 index 0000000000..502dd8b1fb --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/DischargingEnabledUntil.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local DischargingEnabledUntil = { + ID = 0x0004, + NAME = "DischargingEnabledUntil", + base_type = data_types.Uint32, +} + +function DischargingEnabledUntil:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function DischargingEnabledUntil:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function DischargingEnabledUntil:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function DischargingEnabledUntil:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function DischargingEnabledUntil:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function DischargingEnabledUntil:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(DischargingEnabledUntil, {__call = DischargingEnabledUntil.new_value}) +return DischargingEnabledUntil \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua new file mode 100644 index 0000000000..ec5a5c5426 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/EventList.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local EventList = { + ID = 0xFFFA, + NAME = "EventList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32" +} + +EventList.enum_fields = { +} + +function EventList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function EventList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, EventList.enum_fields[value_obj.value]) +end + +function EventList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function EventList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function EventList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function EventList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function EventList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EventList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(EventList, {__call = EventList.new_value}) +return EventList diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua new file mode 100644 index 0000000000..c0b4db8f5a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/FaultState.lua @@ -0,0 +1,114 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local FaultState = { + ID = 0x0002, + NAME = "FaultState", + base_type = data_types.Uint8, +} +FaultState.NO_ERROR = 0x00 +FaultState.METER_FAILURE = 0x01 +FaultState.OVER_VOLTAGE = 0x02 +FaultState.UNDER_VOLTAGE = 0x03 +FaultState.OVER_CURRENT = 0x04 +FaultState.CONTACT_WET_FAILURE = 0x05 +FaultState.CONTACT_DRY_FAILURE = 0x06 +FaultState.GROUND_FAULT = 0x07 +FaultState.POWER_LOSS = 0x08 +FaultState.POWER_QUALITY = 0x09 +FaultState.PILOT_SHORT_CIRCUIT = 0x0A +FaultState.EMERGENCY_STOP = 0x0B +FaultState.EV_DISCONNECTED = 0x0C +FaultState.WRONG_POWER_SUPPLY = 0x0D +FaultState.LIVE_NEUTRAL_SWAP = 0x0E +FaultState.OVER_TEMPERATURE = 0x0F +FaultState.OTHER = 0xFF + +FaultState.enum_fields = { + [FaultState.NO_ERROR] = "NO_ERROR", + [FaultState.METER_FAILURE] = "METER_FAILURE", + [FaultState.OVER_VOLTAGE] = "OVER_VOLTAGE", + [FaultState.UNDER_VOLTAGE] = "UNDER_VOLTAGE", + [FaultState.OVER_CURRENT] = "OVER_CURRENT", + [FaultState.CONTACT_WET_FAILURE] = "CONTACT_WET_FAILURE", + [FaultState.CONTACT_DRY_FAILURE] = "CONTACT_DRY_FAILURE", + [FaultState.GROUND_FAULT] = "GROUND_FAULT", + [FaultState.POWER_LOSS] = "POWER_LOSS", + [FaultState.POWER_QUALITY] = "POWER_QUALITY", + [FaultState.PILOT_SHORT_CIRCUIT] = "PILOT_SHORT_CIRCUIT", + [FaultState.EMERGENCY_STOP] = "EMERGENCY_STOP", + [FaultState.EV_DISCONNECTED] = "EV_DISCONNECTED", + [FaultState.WRONG_POWER_SUPPLY] = "WRONG_POWER_SUPPLY", + [FaultState.LIVE_NEUTRAL_SWAP] = "LIVE_NEUTRAL_SWAP", + [FaultState.OVER_TEMPERATURE] = "OVER_TEMPERATURE", + [FaultState.OTHER] = "OTHER", +} + +function FaultState:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function FaultState.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, FaultState.enum_fields[value_obj.value]) +end + +function FaultState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function FaultState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function FaultState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function FaultState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function FaultState:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function FaultState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(FaultState, {__call = FaultState.new_value}) +return FaultState + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua new file mode 100644 index 0000000000..cbfe635d66 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumChargeCurrent.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local MaximumChargeCurrent = { + ID = 0x0007, + NAME = "MaximumChargeCurrent", + base_type = data_types.Int64, +} + +MaximumChargeCurrent.enum_fields = { +} + +function MaximumChargeCurrent:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function MaximumChargeCurrent.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, MaximumChargeCurrent.enum_fields[value_obj.value]) +end + +function MaximumChargeCurrent:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function MaximumChargeCurrent:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaximumChargeCurrent:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaximumChargeCurrent:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MaximumChargeCurrent:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MaximumChargeCurrent:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MaximumChargeCurrent, {__call = MaximumChargeCurrent.new_value}) +return MaximumChargeCurrent + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua new file mode 100644 index 0000000000..c3866fa9d5 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MaximumDischargeCurrent.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local MaximumDischargeCurrent = { + ID = 0x0008, + NAME = "MaximumDischargeCurrent", + base_type = data_types.Int64, +} + +MaximumDischargeCurrent.enum_fields = { +} + +function MaximumDischargeCurrent:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function MaximumDischargeCurrent.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, MaximumDischargeCurrent.enum_fields[value_obj.value]) +end + +function MaximumDischargeCurrent:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function MaximumDischargeCurrent:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaximumDischargeCurrent:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaximumDischargeCurrent:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MaximumDischargeCurrent:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MaximumDischargeCurrent:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MaximumDischargeCurrent, {__call = MaximumDischargeCurrent.new_value}) +return MaximumDischargeCurrent + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua new file mode 100644 index 0000000000..ed9572388e --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/MinimumChargeCurrent.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local MinimumChargeCurrent = { + ID = 0x0006, + NAME = "MinimumChargeCurrent", + base_type = data_types.Int64, +} + +MinimumChargeCurrent.enum_fields = {} + +function MinimumChargeCurrent:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function MinimumChargeCurrent.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, MinimumChargeCurrent.enum_fields[value_obj.value]) +end + +function MinimumChargeCurrent:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function MinimumChargeCurrent:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MinimumChargeCurrent:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MinimumChargeCurrent:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function MinimumChargeCurrent:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MinimumChargeCurrent:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(MinimumChargeCurrent, {__call = MinimumChargeCurrent.new_value}) +return MinimumChargeCurrent + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua new file mode 100644 index 0000000000..61a1a664f1 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeRequiredEnergy.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local NextChargeRequiredEnergy = { + ID = 0x0025, + NAME = "NextChargeRequiredEnergy", + base_type = data_types.Uint32, +} + +NextChargeRequiredEnergy.enum_fields = {} + +function NextChargeRequiredEnergy:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function NextChargeRequiredEnergy.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, NextChargeRequiredEnergy.enum_fields[value_obj.value]) +end + +function NextChargeRequiredEnergy:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function NextChargeRequiredEnergy:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeRequiredEnergy:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeRequiredEnergy:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function NextChargeRequiredEnergy:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function NextChargeRequiredEnergy:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(NextChargeRequiredEnergy, {__call = NextChargeRequiredEnergy.new_value}) +return NextChargeRequiredEnergy diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua new file mode 100644 index 0000000000..0125d1e4f0 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeStartTime.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local NextChargeStartTime = { + ID = 0x0023, + NAME = "NextChargeStartTime", + base_type = data_types.Uint32, +} + +function NextChargeStartTime:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function NextChargeStartTime:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeStartTime:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeStartTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function NextChargeStartTime:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function NextChargeStartTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(NextChargeStartTime, {__call = NextChargeStartTime.new_value}) +return NextChargeStartTime diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua new file mode 100644 index 0000000000..50cf6db6e0 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetSoC.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local NextChargeTargetSoC = { + ID = 0x0026, + NAME = "NextChargeTargetSoC", + base_type = data_types.Uint8, +} + +function NextChargeTargetSoC:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function NextChargeTargetSoC:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeTargetSoC:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeTargetSoC:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function NextChargeTargetSoC:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function NextChargeTargetSoC:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(NextChargeTargetSoC, {__call = NextChargeTargetSoC.new_value}) +return NextChargeTargetSoC \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua new file mode 100644 index 0000000000..6462d42978 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/NextChargeTargetTime.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local NextChargeTargetTime = { + ID = 0x0024, + NAME = "NextChargeTargetTime", + base_type = data_types.Uint32, +} + +function NextChargeTargetTime:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function NextChargeTargetTime:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeTargetTime:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function NextChargeTargetTime:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function NextChargeTargetTime:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function NextChargeTargetTime:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(NextChargeTargetTime, {__call = NextChargeTargetTime.new_value}) +return NextChargeTargetTime \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua new file mode 100644 index 0000000000..d9036e9a29 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/RandomizationDelayWindow.lua @@ -0,0 +1,91 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local RandomizationDelayWindow = { + ID = 0x000A, + NAME = "RandomizationDelayWindow", + base_type = data_types.Uint32, +} + +RandomizationDelayWindow.enum_fields = {} + +function RandomizationDelayWindow:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function RandomizationDelayWindow.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, RandomizationDelayWindow.enum_fields[value_obj.value]) +end + +function RandomizationDelayWindow:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function RandomizationDelayWindow:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function RandomizationDelayWindow:write(device, endpoint_id, value) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.write( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --event_id + data + ) +end + +function RandomizationDelayWindow:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function RandomizationDelayWindow:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function RandomizationDelayWindow:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function RandomizationDelayWindow:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(RandomizationDelayWindow, {__call = RandomizationDelayWindow.new_value}) +return RandomizationDelayWindow diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua new file mode 100644 index 0000000000..f87e0d59ad --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionDuration.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SessionDuration = { + ID = 0x0041, + NAME = "SessionDuration", + base_type = data_types.Uint32, +} + +SessionDuration.enum_fields = {} + +function SessionDuration:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function SessionDuration.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, SessionDuration.enum_fields[value_obj.value]) +end + +function SessionDuration:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SessionDuration:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionDuration:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionDuration:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SessionDuration:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SessionDuration:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SessionDuration, {__call = SessionDuration.new_value}) +return SessionDuration diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua new file mode 100644 index 0000000000..b7c4275961 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyCharged.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SessionEnergyCharged = { + ID = 0x0042, + NAME = "SessionEnergyCharged", + base_type = data_types.Int64, +} + +SessionEnergyCharged.enum_fields = {} + +function SessionEnergyCharged:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function SessionEnergyCharged.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, SessionEnergyCharged.enum_fields[value_obj.value]) +end + +function SessionEnergyCharged:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SessionEnergyCharged:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionEnergyCharged:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionEnergyCharged:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SessionEnergyCharged:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SessionEnergyCharged:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SessionEnergyCharged, {__call = SessionEnergyCharged.new_value}) +return SessionEnergyCharged diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua new file mode 100644 index 0000000000..cfcee83351 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionEnergyDischarged.lua @@ -0,0 +1,78 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SessionEnergyDischarged = { + ID = 0x0043, + NAME = "SessionEnergyDischarged", + base_type = data_types.Int64, +} + +SessionEnergyDischarged.enum_fields = {} + +function SessionEnergyDischarged:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function SessionEnergyDischarged.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, SessionEnergyDischarged.enum_fields[value_obj.value]) +end + +function SessionEnergyDischarged:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SessionEnergyDischarged:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionEnergyDischarged:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionEnergyDischarged:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SessionEnergyDischarged:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SessionEnergyDischarged:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SessionEnergyDischarged, {__call = SessionEnergyDischarged.new_value}) +return SessionEnergyDischarged diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua new file mode 100644 index 0000000000..f8de5a2a9c --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SessionID.lua @@ -0,0 +1,65 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SessionID = { + ID = 0x0040, + NAME = "SessionID", + base_type = data_types.Uint32, +} + +function SessionID:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function SessionID:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionID:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SessionID:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SessionID:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SessionID:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(SessionID, {__call = SessionID.new_value}) +return SessionID + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua new file mode 100644 index 0000000000..d063ed4e0d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/State.lua @@ -0,0 +1,93 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local State = { + ID = 0x0000, + NAME = "State", + base_type = data_types.Uint8, +} +State.NOT_PLUGGED_IN = 0x00 +State.PLUGGED_IN_NO_DEMAND = 0x01 +State.PLUGGED_IN_DEMAND = 0x02 +State.PLUGGED_IN_CHARGING = 0x03 +State.PLUGGED_IN_DISCHARGING = 0x04 +State.SESSION_ENDING = 0x05 +State.FAULT = 0x06 + +State.enum_fields = { + [State.NOT_PLUGGED_IN] = "NOT_PLUGGED_IN", + [State.PLUGGED_IN_NO_DEMAND] = "PLUGGED_IN_NO_DEMAND", + [State.PLUGGED_IN_DEMAND] = "PLUGGED_IN_DEMAND", + [State.PLUGGED_IN_CHARGING] = "PLUGGED_IN_CHARGING", + [State.PLUGGED_IN_DISCHARGING] = "PLUGGED_IN_DISCHARGING", + [State.SESSION_ENDING] = "SESSION_ENDING", + [State.FAULT] = "FAULT", +} + +function State:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function State.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, State.enum_fields[value_obj.value]) +end + +function State:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function State:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function State:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function State:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function State:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function State:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(State, {__call = State.new_value}) +return State diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua new file mode 100644 index 0000000000..6ca2b9d789 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/StateOfCharge.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local StateOfCharge = { + ID = 0x0030, + NAME = "StateOfCharge", + base_type = data_types.Uint8, +} + +function StateOfCharge:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function StateOfCharge:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function StateOfCharge:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function StateOfCharge:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function StateOfCharge:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function StateOfCharge:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(StateOfCharge, {__call = StateOfCharge.new_value}) +return StateOfCharge \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua new file mode 100644 index 0000000000..8b86968725 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/SupplyState.lua @@ -0,0 +1,89 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupplyState = { + ID = 0x0001, + NAME = "SupplyState", + base_type = data_types.Uint8, +} +SupplyState.DISABLED = 0x00 +SupplyState.CHARGING_ENABLED = 0x01 +SupplyState.DISCHARGING_ENABLED = 0x02 +SupplyState.DISABLED_ERROR = 0x03 +SupplyState.DISABLED_DIAGNOSTICS = 0x04 + +SupplyState.enum_fields = { + [SupplyState.DISABLED] = "DISABLED", + [SupplyState.CHARGING_ENABLED] = "CHARGING_ENABLED", + [SupplyState.DISCHARGING_ENABLED] = "DISCHARGING_ENABLED", + [SupplyState.DISABLED_ERROR] = "DISABLED_ERROR", + [SupplyState.DISABLED_DIAGNOSTICS] = "DISABLED_DIAGNOSTICS", +} + +function SupplyState:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function SupplyState.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, SupplyState.enum_fields[value_obj.value]) +end + +function SupplyState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SupplyState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupplyState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupplyState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupplyState:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupplyState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SupplyState, {__call = SupplyState.new_value}) +return SupplyState diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua new file mode 100644 index 0000000000..9241457b2d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/UserMaximumChargeCurrent.lua @@ -0,0 +1,93 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local UserMaximumChargeCurrent = { + ID = 0x0009, + NAME = "UserMaximumChargeCurrent", + base_type = data_types.Int64, +} + +UserMaximumChargeCurrent.enum_fields = { +} + +function UserMaximumChargeCurrent:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function UserMaximumChargeCurrent.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, UserMaximumChargeCurrent.enum_fields[value_obj.value]) +end + +function UserMaximumChargeCurrent:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function UserMaximumChargeCurrent:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function UserMaximumChargeCurrent:write(device, endpoint_id, value) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.write( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --event_id + data + ) +end + +function UserMaximumChargeCurrent:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function UserMaximumChargeCurrent:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function UserMaximumChargeCurrent:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function UserMaximumChargeCurrent:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(UserMaximumChargeCurrent, {__call = UserMaximumChargeCurrent.new_value}) +return UserMaximumChargeCurrent + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua new file mode 100644 index 0000000000..9e6c7652f0 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/VehicleID.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local VehicleID = { + ID = 0x0032, + NAME = "VehicleID", + base_type = data_types.UTF8String1, +} + +function VehicleID:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function VehicleID:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function VehicleID:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function VehicleID:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function VehicleID:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function VehicleID:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(VehicleID, {__call = VehicleID.new_value}) +return VehicleID \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua new file mode 100644 index 0000000000..859f762e0e --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("EnergyEvse.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local EnergyEvseServerAttributes = {} + +function EnergyEvseServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(EnergyEvseServerAttributes, attr_mt) + +return EnergyEvseServerAttributes + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua new file mode 100644 index 0000000000..c6ee9f6fd4 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/ClearTargets.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ClearTargets = {} + +ClearTargets.NAME = "ClearTargets" +ClearTargets.ID = 0x0007 +ClearTargets.field_defs = { +} + +function ClearTargets:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function ClearTargets:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ClearTargets, + __tostring = ClearTargets.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function ClearTargets:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ClearTargets:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function ClearTargets:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(ClearTargets, {__call = ClearTargets.init}) + +return ClearTargets \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua new file mode 100644 index 0000000000..bbbdc44735 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/Disable.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local Disable = {} + +Disable.NAME = "Disable" +Disable.ID = 0x0001 +Disable.field_defs = { +} + +function Disable:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function Disable:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Disable, + __tostring = Disable.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function Disable:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Disable:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function Disable:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(Disable, {__call = Disable.init}) + +return Disable \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua new file mode 100644 index 0000000000..328dd34692 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableCharging.lua @@ -0,0 +1,106 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local amperage_maType = require "st.matter.data_types.Int64" + +local EnableCharging = {} + +EnableCharging.NAME = "EnableCharging" +EnableCharging.ID = 0x0002 +EnableCharging.field_defs = { + { + name = "charging_enabled_until", + field_id = 0, + optional = false, + nullable = true, + data_type = data_types.Uint32, + }, + { + name = "minimum_charge_current", + field_id = 1, + optional = false, + nullable = false, + data_type = amperage_maType, + }, + { + name = "maximum_charge_current", + field_id = 2, + optional = false, + nullable = false, + data_type = amperage_maType, + }, +} + +function EnableCharging:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function EnableCharging:init(device, endpoint_id, charging_enabled_until, minimum_charge_current, maximum_charge_current) + local out = {} + local args = {charging_enabled_until, minimum_charge_current, maximum_charge_current} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = EnableCharging, + __tostring = EnableCharging.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function EnableCharging:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function EnableCharging:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function EnableCharging:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(EnableCharging, {__call = EnableCharging.init}) + +return EnableCharging \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua new file mode 100644 index 0000000000..74c4f3f30a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/EnableDischarging.lua @@ -0,0 +1,99 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local amperage_maType = require "st.matter.data_types.Int64" + +local EnableDischarging = {} + +EnableDischarging.NAME = "EnableDischarging" +EnableDischarging.ID = 0x0003 +EnableDischarging.field_defs = { + { + name = "discharging_enabled_until", + field_id = 0, + optional = false, + nullable = true, + data_type = data_types.Uint32, + }, + { + name = "maximum_discharge_current", + field_id = 1, + optional = false, + nullable = false, + data_type = amperage_maType, + }, +} + +function EnableDischarging:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function EnableDischarging:init(device, endpoint_id, discharging_enabled_until, maximum_discharge_current) + local out = {} + local args = {discharging_enabled_until, maximum_discharge_current} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = EnableDischarging, + __tostring = EnableDischarging.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function EnableDischarging:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function EnableDischarging:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function EnableDischarging:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(EnableDischarging, {__call = EnableDischarging.init}) + +return EnableDischarging \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua new file mode 100644 index 0000000000..44c9085f88 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/GetTargets.lua @@ -0,0 +1,72 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local GetTargets = {} + +GetTargets.NAME = "GetTargets" +GetTargets.ID = 0x0006 +GetTargets.field_defs = {} + +function GetTargets:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = GetTargets, + __tostring = GetTargets.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function GetTargets:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function GetTargets:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function GetTargets:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(GetTargets, {__call = GetTargets.init}) + +return GetTargets \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua new file mode 100644 index 0000000000..65895b5053 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/SetTargets.lua @@ -0,0 +1,93 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local ChargingTargetScheduleStructType = require "EnergyEvsetypes.ChargingTargetScheduleStruct" + +local SetTargets = {} + +SetTargets.NAME = "SetTargets" +SetTargets.ID = 0x0005 +SetTargets.field_defs = { + { + name = "charging_target_schedules", + field_id = 0, + optional = false, + nullable = false, + data_type = data_types.Array, + array_type = ChargingTargetScheduleStructType, + }, +} + +function SetTargets:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function SetTargets:init(device, endpoint_id, charging_target_schedules) + local out = {} + local args = {charging_target_schedules} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = SetTargets, + __tostring = SetTargets.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function SetTargets:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SetTargets:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function SetTargets:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(SetTargets, {__call = SetTargets.init}) + +return SetTargets \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua new file mode 100644 index 0000000000..e8c6bd2c5e --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/StartDiagnostics.lua @@ -0,0 +1,84 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local StartDiagnostics = {} + +StartDiagnostics.NAME = "StartDiagnostics" +StartDiagnostics.ID = 0x0004 +StartDiagnostics.field_defs = { +} + +function StartDiagnostics:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --tlv + status + ) +end + +function StartDiagnostics:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = StartDiagnostics, + __tostring = StartDiagnostics.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID, + true + ) +end + +function StartDiagnostics:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function StartDiagnostics:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function StartDiagnostics:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(StartDiagnostics, {__call = StartDiagnostics.init}) + +return StartDiagnostics \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua new file mode 100644 index 0000000000..dbe3f22200 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("EnergyEvse.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local EnergyEvseServerCommands = {} + +function EnergyEvseServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(EnergyEvseServerCommands, command_mt) + +return EnergyEvseServerCommands + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua new file mode 100644 index 0000000000..3790cec4a3 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVConnected.lua @@ -0,0 +1,97 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local EVConnected = { + ID = 0x0000, + NAME = "EVConnected", + base_type = data_types.Structure, +} + +EVConnected.field_defs = { + { + data_type = data_types.Uint32, + field_id = 0, + is_array = false, + name = "session_id", + is_nullable = false, + is_optional = false, + }, +} + +function EVConnected:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function EVConnected:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EVConnected:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EVConnected:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "EVConnectedEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EVConnected:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function EVConnected:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return EVConnected diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua new file mode 100644 index 0000000000..d4796814cd --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EVNotDetected.lua @@ -0,0 +1,129 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local StateEnum = require "EnergyEvsetypes.StateEnum" +local EVNotDetected = { + ID = 0x0001, + NAME = "EVNotDetected", + base_type = data_types.Structure, +} + +EVNotDetected.field_defs = { + { + data_type = data_types.Uint32, + field_id = 0, + is_array = false, + name = "session_id", + is_nullable = false, + is_optional = false, + }, + { + data_type = StateEnum, + field_id = 1, + is_array = false, + name = "state", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint32, + field_id = 2, + is_array = false, + name = "session_duration", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 3, + is_array = false, + name = "session_energy_charged", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 4, + is_array = false, + name = "session_energy_discharged", + is_nullable = false, + is_optional = false, + }, +} + +function EVNotDetected:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function EVNotDetected:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EVNotDetected:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EVNotDetected:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "EVNotDetectedEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EVNotDetected:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function EVNotDetected:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return EVNotDetected diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua new file mode 100644 index 0000000000..dc39ca0ca8 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStarted.lua @@ -0,0 +1,114 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local StateEnum = require "EnergyEvsetypes.StateEnum" + +local EnergyTransferStarted = { + ID = 0x0002, + NAME = "EnergyTransferStarted", + base_type = data_types.Structure, +} + +EnergyTransferStarted.field_defs = { + { + data_type = data_types.Uint32, + field_id = 0, + is_array = false, + name = "session_id", + is_nullable = false, + is_optional = false, + }, + { + data_type = StateEnum, + field_id = 1, + is_array = false, + name = "state", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 2, + is_array = false, + name = "maximum_current", + is_nullable = false, + is_optional = false, + }, +} + +function EnergyTransferStarted:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function EnergyTransferStarted:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EnergyTransferStarted:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EnergyTransferStarted:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "EnergyTransferStartedEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EnergyTransferStarted:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function EnergyTransferStarted:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return EnergyTransferStarted \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua new file mode 100644 index 0000000000..884dc90e51 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/EnergyTransferStopped.lua @@ -0,0 +1,124 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local StateEnum = require "EnergyEvsetypes.StateEnum" +local EnergyTransferStoppedReasonEnum = require "EnergyEvsetypes.EnergyTransferStoppedReasonEnum" + +local EnergyTransferStopped = { + ID = 0x0003, + NAME = "EnergyTransferStopped", + base_type = data_types.Structure, +} + +EnergyTransferStopped.field_defs = { + { + data_type = data_types.Uint32, + field_id = 0, + is_array = false, + name = "session_id", + is_nullable = false, + is_optional = false, + }, + { + data_type = StateEnum, + field_id = 1, + is_array = false, + name = "state", + is_nullable = false, + is_optional = false, + }, + { + data_type = EnergyTransferStoppedReasonEnum, + field_id = 2, + is_array = false, + name = "reason", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 4, + is_array = false, + name = "energy_transferred", + is_nullable = false, + is_optional = false, + }, +} + +function EnergyTransferStopped:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function EnergyTransferStopped:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EnergyTransferStopped:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function EnergyTransferStopped:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "EnergyTransferStoppedEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EnergyTransferStopped:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function EnergyTransferStopped:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return EnergyTransferStopped + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua new file mode 100644 index 0000000000..86fb42e3f0 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Fault.lua @@ -0,0 +1,123 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local StateEnum = require "EnergyEvsetypes.StateEnum" +local FaultStateEnum = require "EnergyEvsetypes.FaultStateEnum" + +local Fault = { + ID = 0x0004, + NAME = "Fault", + base_type = data_types.Structure, +} + +Fault.field_defs = { + { + data_type = data_types.Uint32, + field_id = 0, + is_array = false, + name = "session_id", + is_nullable = true, + is_optional = true, + }, + { + data_type = StateEnum, + field_id = 1, + is_array = false, + name = "state", + is_nullable = false, + is_optional = false, + }, + { + data_type = FaultStateEnum, + field_id = 2, + is_array = false, + name = "fault_state_previous_state", + is_nullable = false, + is_optional = false, + }, + { + data_type = FaultStateEnum, + field_id = 4, + is_array = false, + name = "fault_state_current_state", + is_nullable = false, + is_optional = false, + }, +} + +function Fault:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function Fault:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function Fault:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function Fault:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "FaultEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Fault:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function Fault:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return Fault \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua new file mode 100644 index 0000000000..e5bfb998fd --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/Rfid.lua @@ -0,0 +1,98 @@ +local data_types = require "st.matter.data_types" +local cluster_base = require "st.matter.cluster_base" +local TLVParser = require "st.matter.TLV.TLVParser" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local Rfid = { + ID = 0x0005, + NAME = "Rfid", + base_type = data_types.Structure, +} + +Rfid.field_defs = { + { + data_type = data_types.OctetString1, + field_id = 0, + is_array = false, + name = "uid", + is_nullable = false, + is_optional = false, + }, +} + +function Rfid:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and not + ((field_def.is_nullable or field_def.is_optional) and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + elems[field_def.name].field_name = field_def.name + end + end + end + base_type_obj.elements = elems +end + +function Rfid:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function Rfid:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + nil, --attribute_id + self.ID + ) +end + +function Rfid:build_test_event_report( + device, + endpoint_id, + fields, + status +) + local data = {} + data.elements = {} + data.num_elements = 0 + setmetatable(data, StructureABC.new_mt({NAME = "RfidEventData", ID = 0x15})) + for idx, field_def in ipairs(self.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not fields[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + elseif fields[field_def.name] then + data.elements[field_def.name] = data_types.validate_or_build_type(fields[field_def.name], field_def.data_type, field_def.name) + data.elements[field_def.name].field_id = field_def.field_id + data.num_elements = data.num_elements + 1 + end + end + return cluster_base.build_test_event_report( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Rfid:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +function Rfid:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +return Rfid + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua new file mode 100644 index 0000000000..12268886f1 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/server/events/init.lua @@ -0,0 +1,24 @@ +local event_mt = {} +event_mt.__event_cache = {} +event_mt.__index = function(self, key) + if event_mt.__event_cache[key] == nil then + local req_loc = string.format("EnergyEvse.server.events.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + event_mt.__event_cache[key] = raw_def + end + return event_mt.__event_cache[key] +end + +local EnergyEvseEvents = {} + +function EnergyEvseEvents:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(EnergyEvseEvents, event_mt) + +return EnergyEvseEvents + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua new file mode 100644 index 0000000000..86a641e1b8 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetScheduleStruct.lua @@ -0,0 +1,71 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local TargetDayOfWeekBitmap = require "EnergyEvsetypes.TargetDayOfWeekBitmap" +local ChargingTargetStruct = require "EnergyEvsetypes.ChargingTargetStruct" + +local ChargingTargetScheduleStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ChargingTargetScheduleStruct", ID = data_types.name_to_id_map["Structure"]}) + +ChargingTargetScheduleStruct.field_defs = { + { + data_type = TargetDayOfWeekBitmap, + field_id = 0, + name = "day_of_week_for_sequence", + }, + { + data_type = ChargingTargetStruct, + field_id = 1, + name = "charging_targets", + }, +} + +ChargingTargetScheduleStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ChargingTargetScheduleStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ChargingTargetScheduleStruct.init +new_mt.__index.serialize = ChargingTargetScheduleStruct.serialize + +ChargingTargetScheduleStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(ChargingTargetScheduleStruct, new_mt) + +return ChargingTargetScheduleStruct \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua new file mode 100644 index 0000000000..f9c6f7e8c8 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/ChargingTargetStruct.lua @@ -0,0 +1,79 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ChargingTargetStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ChargingTargetStruct", ID = data_types.name_to_id_map["Structure"]}) + +ChargingTargetStruct.field_defs = { + { + data_type = data_types.Uint16, + field_id = 0, + name = "target_time_minutes_past_midnight", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Uint8, + field_id = 1, + name = "target_so_c", + is_nullable = false, + is_optional = false, + }, + { + data_type = data_types.Int64, + field_id = 2, + name = "added_energy", + }, +} + +ChargingTargetStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do --Note: idx is 1 when field_id is 0 + if (not field_def.is_optional or not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ChargingTargetStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ChargingTargetStruct.init +new_mt.__index.serialize = ChargingTargetStruct.serialize + +ChargingTargetStruct.augment_type = function(self, val) + local elems = {} + for _, v in ipairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.array_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.array_type) + end + end + end + end + end + val.elements = elems + setmetatable(val, new_mt) +end + +setmetatable(ChargingTargetStruct, new_mt) + +return ChargingTargetStruct + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua new file mode 100644 index 0000000000..76e0958be3 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/EnergyTransferStoppedReasonEnum.lua @@ -0,0 +1,26 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local EnergyTransferStoppedReasonEnum = {} +local new_mt = UintABC.new_mt({NAME = "EnergyTransferStoppedReasonEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.EV_STOPPED] = "EV_STOPPED", + [self.EVSE_STOPPED] = "EVSE_STOPPED", + [self.OTHER] = "OTHER", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.EV_STOPPED = 0x00 +new_mt.__index.EVSE_STOPPED = 0x01 +new_mt.__index.OTHER = 0x02 + +EnergyTransferStoppedReasonEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(EnergyTransferStoppedReasonEnum, new_mt) + +return EnergyTransferStoppedReasonEnum diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua new file mode 100644 index 0000000000..6013c0a251 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/FaultStateEnum.lua @@ -0,0 +1,54 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local FaultStateEnum = {} +local new_mt = UintABC.new_mt({NAME = "FaultStateEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.NO_ERROR] = "NO_ERROR", + [self.METER_FAILURE] = "METER_FAILURE", + [self.OVER_VOLTAGE] = "OVER_VOLTAGE", + [self.UNDER_VOLTAGE] = "UNDER_VOLTAGE", + [self.OVER_CURRENT] = "OVER_CURRENT", + [self.CONTACT_WET_FAILURE] = "CONTACT_WET_FAILURE", + [self.CONTACT_DRY_FAILURE] = "CONTACT_DRY_FAILURE", + [self.GROUND_FAULT] = "GROUND_FAULT", + [self.POWER_LOSS] = "POWER_LOSS", + [self.POWER_QUALITY] = "POWER_QUALITY", + [self.PILOT_SHORT_CIRCUIT] = "PILOT_SHORT_CIRCUIT", + [self.EMERGENCY_STOP] = "EMERGENCY_STOP", + [self.EV_DISCONNECTED] = "EV_DISCONNECTED", + [self.WRONG_POWER_SUPPLY] = "WRONG_POWER_SUPPLY", + [self.LIVE_NEUTRAL_SWAP] = "LIVE_NEUTRAL_SWAP", + [self.OVER_TEMPERATURE] = "OVER_TEMPERATURE", + [self.OTHER] = "OTHER", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.NO_ERROR = 0x00 +new_mt.__index.METER_FAILURE = 0x01 +new_mt.__index.OVER_VOLTAGE = 0x02 +new_mt.__index.UNDER_VOLTAGE = 0x03 +new_mt.__index.OVER_CURRENT = 0x04 +new_mt.__index.CONTACT_WET_FAILURE = 0x05 +new_mt.__index.CONTACT_DRY_FAILURE = 0x06 +new_mt.__index.GROUND_FAULT = 0x07 +new_mt.__index.POWER_LOSS = 0x08 +new_mt.__index.POWER_QUALITY = 0x09 +new_mt.__index.PILOT_SHORT_CIRCUIT = 0x0A +new_mt.__index.EMERGENCY_STOP = 0x0B +new_mt.__index.EV_DISCONNECTED = 0x0C +new_mt.__index.WRONG_POWER_SUPPLY = 0x0D +new_mt.__index.LIVE_NEUTRAL_SWAP = 0x0E +new_mt.__index.OVER_TEMPERATURE = 0x0F +new_mt.__index.OTHER = 0xFF + +FaultStateEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(FaultStateEnum, new_mt) + +return FaultStateEnum diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua new file mode 100644 index 0000000000..9e3e10b643 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/Feature.lua @@ -0,0 +1,138 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.CHARGING_PREFERENCES = 0x0001 +Feature.SOC_REPORTING = 0x0002 +Feature.PLUG_AND_CHARGE = 0x0004 +Feature.RFID = 0x0008 +Feature.V2X = 0x0010 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + CHARGING_PREFERENCES = 0x0001, + SOC_REPORTING = 0x0002, + PLUG_AND_CHARGE = 0x0004, + RFID = 0x0008, + V2X = 0x0010, +} + +Feature.is_charging_preferences_set = function(self) + return (self.value & self.CHARGING_PREFERENCES) ~= 0 +end + +Feature.set_charging_preferences = function(self) + if self.value ~= nil then + self.value = self.value | self.CHARGING_PREFERENCES + else + self.value = self.CHARGING_PREFERENCES + end +end + +Feature.unset_charging_preferences = function(self) + self.value = self.value & (~self.CHARGING_PREFERENCES & self.BASE_MASK) +end + +Feature.is_soc_reporting_set = function(self) + return (self.value & self.SOC_REPORTING) ~= 0 +end + +Feature.set_soc_reporting = function(self) + if self.value ~= nil then + self.value = self.value | self.SOC_REPORTING + else + self.value = self.SOC_REPORTING + end +end + +Feature.unset_soc_reporting = function(self) + self.value = self.value & (~self.SOC_REPORTING & self.BASE_MASK) +end + +Feature.is_plug_and_charge_set = function(self) + return (self.value & self.PLUG_AND_CHARGE) ~= 0 +end + +Feature.set_plug_and_charge = function(self) + if self.value ~= nil then + self.value = self.value | self.PLUG_AND_CHARGE + else + self.value = self.PLUG_AND_CHARGE + end +end + +Feature.unset_plug_and_charge = function(self) + self.value = self.value & (~self.PLUG_AND_CHARGE & self.BASE_MASK) +end + +Feature.is_rfid_set = function(self) + return (self.value & self.RFID) ~= 0 +end + +Feature.set_rfid = function(self) + if self.value ~= nil then + self.value = self.value | self.RFID + else + self.value = self.RFID + end +end + +Feature.unset_rfid = function(self) + self.value = self.value & (~self.RFID & self.BASE_MASK) +end + +Feature.is_v2x_set = function(self) + return (self.value & self.V2X) ~= 0 +end + +Feature.set_v2x = function(self) + if self.value ~= nil then + self.value = self.value | self.V2X + else + self.value = self.V2X + end +end + +Feature.unset_v2x = function(self) + self.value = self.value & (~self.V2X & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.CHARGING_PREFERENCES | Feature.SOC_REPORTING | Feature.PLUG_AND_CHARGE | Feature.RFID | Feature.V2X + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_charging_preferences_set = Feature.is_charging_preferences_set, + set_charging_preferences = Feature.set_charging_preferences, + unset_charging_preferences = Feature.unset_charging_preferences, + is_soc_reporting_set = Feature.is_soc_reporting_set, + set_soc_reporting = Feature.set_soc_reporting, + unset_soc_reporting = Feature.unset_soc_reporting, + is_plug_and_charge_set = Feature.is_plug_and_charge_set, + set_plug_and_charge = Feature.set_plug_and_charge, + unset_plug_and_charge = Feature.unset_plug_and_charge, + is_rfid_set = Feature.is_rfid_set, + set_rfid = Feature.set_rfid, + unset_rfid = Feature.unset_rfid, + is_v2x_set = Feature.is_v2x_set, + set_v2x = Feature.set_v2x, + unset_v2x = Feature.unset_v2x, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua new file mode 100644 index 0000000000..3f01167718 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/StateEnum.lua @@ -0,0 +1,34 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local StateEnum = {} +local new_mt = UintABC.new_mt({NAME = "StateEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.NOT_PLUGGED_IN] = "NOT_PLUGGED_IN", + [self.PLUGGED_IN_NO_DEMAND] = "PLUGGED_IN_NO_DEMAND", + [self.PLUGGED_IN_DEMAND] = "PLUGGED_IN_DEMAND", + [self.PLUGGED_IN_CHARGING] = "PLUGGED_IN_CHARGING", + [self.PLUGGED_IN_DISCHARGING] = "PLUGGED_IN_DISCHARGING", + [self.SESSION_ENDING] = "SESSION_ENDING", + [self.FAULT] = "FAULT", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.NOT_PLUGGED_IN = 0x00 +new_mt.__index.PLUGGED_IN_NO_DEMAND = 0x01 +new_mt.__index.PLUGGED_IN_DEMAND = 0x02 +new_mt.__index.PLUGGED_IN_CHARGING = 0x03 +new_mt.__index.PLUGGED_IN_DISCHARGING = 0x04 +new_mt.__index.SESSION_ENDING = 0x05 +new_mt.__index.FAULT = 0x06 + +StateEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(StateEnum, new_mt) + +return StateEnum diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua new file mode 100644 index 0000000000..cf7764f8cc --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/SupplyStateEnum.lua @@ -0,0 +1,30 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local SupplyStateEnum = {} +local new_mt = UintABC.new_mt({NAME = "SupplyStateEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.DISABLED] = "DISABLED", + [self.CHARGING_ENABLED] = "CHARGING_ENABLED", + [self.DISCHARGING_ENABLED] = "DISCHARGING_ENABLED", + [self.DISABLED_ERROR] = "DISABLED_ERROR", + [self.DISABLED_DIAGNOSTICS] = "DISABLED_DIAGNOSTICS", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.DISABLED = 0x00 +new_mt.__index.CHARGING_ENABLED = 0x01 +new_mt.__index.DISCHARGING_ENABLED = 0x02 +new_mt.__index.DISABLED_ERROR = 0x03 +new_mt.__index.DISABLED_DIAGNOSTICS = 0x04 + +SupplyStateEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(SupplyStateEnum, new_mt) + +return SupplyStateEnum diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua new file mode 100644 index 0000000000..4a874f721b --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/TargetDayOfWeekBitmap.lua @@ -0,0 +1,171 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local TargetDayOfWeekBitmap = {} +local new_mt = UintABC.new_mt({NAME = "TargetDayOfWeekBitmap", ID = data_types.name_to_id_map["Uint8"]}, 1) + +TargetDayOfWeekBitmap.BASE_MASK = 0xFFFF +TargetDayOfWeekBitmap.SUNDAY = 0x0001 +TargetDayOfWeekBitmap.MONDAY = 0x0002 +TargetDayOfWeekBitmap.TUESDAY = 0x0004 +TargetDayOfWeekBitmap.WEDNESDAY = 0x0008 +TargetDayOfWeekBitmap.THURSDAY = 0x0010 +TargetDayOfWeekBitmap.FRIDAY = 0x0020 +TargetDayOfWeekBitmap.SATURDAY = 0x0040 + +TargetDayOfWeekBitmap.mask_fields = { + BASE_MASK = 0xFFFF, + SUNDAY = 0x0001, + MONDAY = 0x0002, + TUESDAY = 0x0004, + WEDNESDAY = 0x0008, + THURSDAY = 0x0010, + FRIDAY = 0x0020, + SATURDAY = 0x0040, +} + +TargetDayOfWeekBitmap.is_sunday_set = function(self) + return (self.value & self.SUNDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_sunday = function(self) + if self.value ~= nil then + self.value = self.value | self.SUNDAY + else + self.value = self.SUNDAY + end +end + +TargetDayOfWeekBitmap.unset_sunday = function(self) + self.value = self.value & (~self.SUNDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_monday_set = function(self) + return (self.value & self.MONDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_monday = function(self) + if self.value ~= nil then + self.value = self.value | self.MONDAY + else + self.value = self.MONDAY + end +end + +TargetDayOfWeekBitmap.unset_monday = function(self) + self.value = self.value & (~self.MONDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_tuesday_set = function(self) + return (self.value & self.TUESDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_tuesday = function(self) + if self.value ~= nil then + self.value = self.value | self.TUESDAY + else + self.value = self.TUESDAY + end +end + +TargetDayOfWeekBitmap.unset_tuesday = function(self) + self.value = self.value & (~self.TUESDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_wednesday_set = function(self) + return (self.value & self.WEDNESDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_wednesday = function(self) + if self.value ~= nil then + self.value = self.value | self.WEDNESDAY + else + self.value = self.WEDNESDAY + end +end + +TargetDayOfWeekBitmap.unset_wednesday = function(self) + self.value = self.value & (~self.WEDNESDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_thursday_set = function(self) + return (self.value & self.THURSDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_thursday = function(self) + if self.value ~= nil then + self.value = self.value | self.THURSDAY + else + self.value = self.THURSDAY + end +end + +TargetDayOfWeekBitmap.unset_thursday = function(self) + self.value = self.value & (~self.THURSDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_friday_set = function(self) + return (self.value & self.FRIDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_friday = function(self) + if self.value ~= nil then + self.value = self.value | self.FRIDAY + else + self.value = self.FRIDAY + end +end + +TargetDayOfWeekBitmap.unset_friday = function(self) + self.value = self.value & (~self.FRIDAY & self.BASE_MASK) +end + +TargetDayOfWeekBitmap.is_saturday_set = function(self) + return (self.value & self.SATURDAY) ~= 0 +end + +TargetDayOfWeekBitmap.set_saturday = function(self) + if self.value ~= nil then + self.value = self.value | self.SATURDAY + else + self.value = self.SATURDAY + end +end + +TargetDayOfWeekBitmap.unset_saturday = function(self) + self.value = self.value & (~self.SATURDAY & self.BASE_MASK) +end + + +TargetDayOfWeekBitmap.mask_methods = { + is_sunday_set = TargetDayOfWeekBitmap.is_sunday_set, + set_sunday = TargetDayOfWeekBitmap.set_sunday, + unset_sunday = TargetDayOfWeekBitmap.unset_sunday, + is_monday_set = TargetDayOfWeekBitmap.is_monday_set, + set_monday = TargetDayOfWeekBitmap.set_monday, + unset_monday = TargetDayOfWeekBitmap.unset_monday, + is_tuesday_set = TargetDayOfWeekBitmap.is_tuesday_set, + set_tuesday = TargetDayOfWeekBitmap.set_tuesday, + unset_tuesday = TargetDayOfWeekBitmap.unset_tuesday, + is_wednesday_set = TargetDayOfWeekBitmap.is_wednesday_set, + set_wednesday = TargetDayOfWeekBitmap.set_wednesday, + unset_wednesday = TargetDayOfWeekBitmap.unset_wednesday, + is_thursday_set = TargetDayOfWeekBitmap.is_thursday_set, + set_thursday = TargetDayOfWeekBitmap.set_thursday, + unset_thursday = TargetDayOfWeekBitmap.unset_thursday, + is_friday_set = TargetDayOfWeekBitmap.is_friday_set, + set_friday = TargetDayOfWeekBitmap.set_friday, + unset_friday = TargetDayOfWeekBitmap.unset_friday, + is_saturday_set = TargetDayOfWeekBitmap.is_saturday_set, + set_saturday = TargetDayOfWeekBitmap.set_saturday, + unset_saturday = TargetDayOfWeekBitmap.unset_saturday, +} + +TargetDayOfWeekBitmap.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(TargetDayOfWeekBitmap, new_mt) + +return TargetDayOfWeekBitmap + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua new file mode 100644 index 0000000000..495c5fa250 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvse/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("EnergyEvse.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local EnergyEvseTypes = {} + +setmetatable(EnergyEvseTypes, types_mt) + +return EnergyEvseTypes + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua new file mode 100644 index 0000000000..e40b17452a --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/init.lua @@ -0,0 +1,96 @@ +local cluster_base = require "st.matter.cluster_base" +local EnergyEvseModeServerAttributes = require "EnergyEvseMode.server.attributes" +local EnergyEvseModeServerCommands = require "EnergyEvseMode.server.commands" +local EnergyEvseModeTypes = require "EnergyEvseMode.types" + +local EnergyEvseMode = {} + +EnergyEvseMode.ID = 0x009D +EnergyEvseMode.NAME = "EnergyEvseMode" +EnergyEvseMode.server = {} +EnergyEvseMode.server.attributes = EnergyEvseModeServerAttributes:set_parent_cluster(EnergyEvseMode) +EnergyEvseMode.server.commands = EnergyEvseModeServerCommands:set_parent_cluster(EnergyEvseMode) +EnergyEvseMode.types = EnergyEvseModeTypes +EnergyEvseMode.FeatureMap = EnergyEvseMode.types.Feature + +function EnergyEvseMode.are_features_supported(feature, feature_map) + if (EnergyEvseMode.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +function EnergyEvseMode:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "SupportedModes", + [0x0001] = "CurrentMode", + [0xFFF9] = "AcceptedCommandList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function EnergyEvseMode:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "ChangeToMode", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +function EnergyEvseMode:get_client_command_by_id(command_id) + local client_id_map = { + [0x0001] = "ChangeToModeResponse", + } + if client_id_map[command_id] ~= nil then + return self.client.commands[client_id_map[command_id]] + end + return nil +end + +-- Attribute Mapping +EnergyEvseMode.attribute_direction_map = { + ["SupportedModes"] = "server", + ["CurrentMode"] = "server", + ["AcceptedCommandList"] = "server", + ["AttributeList"] = "server", +} + +-- Command Mapping +EnergyEvseMode.command_direction_map = { + ["ChangeToMode"] = "server", + ["ChangeToModeResponse"] = "client", +} + +-- Cluster Completion +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = EnergyEvseMode.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, EnergyEvseMode.NAME)) + end + return EnergyEvseMode[direction].attributes[key] +end +EnergyEvseMode.attributes = {} +setmetatable(EnergyEvseMode.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = EnergyEvseMode.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, EnergyEvseMode.NAME)) + end + return EnergyEvseMode[direction].commands[key] +end +EnergyEvseMode.commands = {} +setmetatable(EnergyEvseMode.commands, command_helper_mt) + +setmetatable(EnergyEvseMode, {__index = cluster_base}) + +return EnergyEvseMode \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..ad407e23bf --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,81 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AcceptedCommandList.enum_fields = {} + +function AcceptedCommandList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AcceptedCommandList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AcceptedCommandList.enum_fields[value_obj.value]) +end + +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value}) +return AcceptedCommandList + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..c747c15169 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/AttributeList.lua @@ -0,0 +1,81 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +AttributeList.enum_fields = {} + +function AttributeList:augment_type(base_type_obj) + base_type_obj.field_name = self.NAME + base_type_obj.pretty_print = self.pretty_print +end + +function AttributeList.pretty_print(value_obj) + return string.format("%s.%s", value_obj.field_name or value_obj.NAME, AttributeList.enum_fields[value_obj.value]) +end + +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value}) +return AttributeList + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua new file mode 100644 index 0000000000..e7b5a885b6 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/CurrentMode.lua @@ -0,0 +1,64 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentMode = { + ID = 0x0001, + NAME = "CurrentMode", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentMode:new_value(...) + local o = self.base_type(table.unpack({...})) + return o +end + +function CurrentMode:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentMode:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentMode:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + return data +end + +setmetatable(CurrentMode, {__call = CurrentMode.new_value, __index = CurrentMode.base_type}) +return CurrentMode diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua new file mode 100644 index 0000000000..d217cdf658 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/SupportedModes.lua @@ -0,0 +1,74 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupportedModes = { + ID = 0x0000, + NAME = "SupportedModes", + base_type = require "st.matter.data_types.Array", + element_type = require "EnergyEvseMode.types.ModeOptionStruct", +} + +function SupportedModes:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, SupportedModes.element_type) + end +end + +function SupportedModes:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SupportedModes:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SupportedModes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupportedModes:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupportedModes:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SupportedModes, {__call = SupportedModes.new_value, __index = SupportedModes.base_type}) +return SupportedModes diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua new file mode 100644 index 0000000000..167eda541d --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("EnergyEvseMode.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local EnergyEvseModeServerAttributes = {} + +function EnergyEvseModeServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(EnergyEvseModeServerAttributes, attr_mt) + +return EnergyEvseModeServerAttributes + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua new file mode 100644 index 0000000000..7e6ee5f9f5 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/ChangeToMode.lua @@ -0,0 +1,79 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ChangeToMode = {} + +ChangeToMode.NAME = "ChangeToMode" +ChangeToMode.ID = 0x0000 +ChangeToMode.field_defs = { + { + name = "new_mode", + field_id = 0, + optional = false, + nullable = false, + data_type = data_types.Uint8, + }, +} + +function ChangeToMode:init(device, endpoint_id, new_mode) + local out = {} + local args = {new_mode} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.optional and args[i] == nil then + out[v.name] = nil + elseif v.nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = ChangeToMode, + __tostring = ChangeToMode.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function ChangeToMode:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ChangeToMode:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + end + end + end + base_type_obj.elements = elems +end + +function ChangeToMode:deserialize(tlv_buf) + return TLVParser.decode_tlv(tlv_buf) +end + +setmetatable(ChangeToMode, {__call = ChangeToMode.init}) + +return ChangeToMode diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua new file mode 100644 index 0000000000..bea9b81a30 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/server/commands/init.lua @@ -0,0 +1,23 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("EnergyEvseMode.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local EnergyEvseModeServerCommands = {} + +function EnergyEvseModeServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(EnergyEvseModeServerCommands, command_mt) + +return EnergyEvseModeServerCommands + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua new file mode 100644 index 0000000000..b542363cf9 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/Feature.lua @@ -0,0 +1,56 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.ON_OFF = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + ON_OFF = 0x0001, +} + +Feature.is_on_off_set = function(self) + return (self.value & self.ON_OFF) ~= 0 +end + +Feature.set_on_off = function(self) + if self.value ~= nil then + self.value = self.value | self.ON_OFF + else + self.value = self.ON_OFF + end +end + +Feature.unset_on_off = function(self) + self.value = self.value & (~self.ON_OFF & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.ON_OFF + + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_on_off_set = Feature.is_on_off_set, + set_on_off = Feature.set_on_off, + unset_on_off = Feature.unset_on_off, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua new file mode 100644 index 0000000000..72465b9893 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeOptionStruct.lua @@ -0,0 +1,85 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeOptionStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeOptionStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeOptionStruct.field_defs = { + { + name = "label", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.UTF8String1", + }, + { + name = "mode", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint8", + }, + { + name = "mode_tags", + field_id = 2, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Array", + element_type = require "EnergyEvseMode.types.ModeTagStruct", + }, +} + +ModeOptionStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeOptionStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeOptionStruct.init +new_mt.__index.serialize = ModeOptionStruct.serialize + +ModeOptionStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeOptionStruct, new_mt) + +return ModeOptionStruct diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua new file mode 100644 index 0000000000..71b2c43960 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTag.lua @@ -0,0 +1,30 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local ModeTag = {} +local new_mt = UintABC.new_mt({NAME = "ModeTag", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.MANUAL] = "MANUAL", + [self.TIME_OF_USE] = "TIME_OF_USE", + [self.SOLAR_CHARGING] = "SOLAR_CHARGING", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.MANUAL = 0x4000 +new_mt.__index.TIME_OF_USE = 0x4001 +new_mt.__index.SOLAR_CHARGING = 0x4002 + +ModeTag.MANUAL = 0x4000 +ModeTag.TIME_OF_USE = 0x4001 +ModeTag.SOLAR_CHARGING = 0x4002 + +ModeTag.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ModeTag, new_mt) + +return ModeTag diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua new file mode 100644 index 0000000000..610fb90ad7 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/ModeTagStruct.lua @@ -0,0 +1,77 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" + +local ModeTagStruct = {} +local new_mt = StructureABC.new_mt({NAME = "ModeTagStruct", ID = data_types.name_to_id_map["Structure"]}) + +ModeTagStruct.field_defs = { + { + name = "mfg_code", + field_id = 0, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint16", + }, + { + name = "value", + field_id = 1, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Uint16", + }, +} + +ModeTagStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +ModeTagStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = ModeTagStruct.init +new_mt.__index.serialize = ModeTagStruct.serialize + +ModeTagStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(ModeTagStruct, new_mt) + +return ModeTagStruct diff --git a/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua new file mode 100644 index 0000000000..4197eaa9a6 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/EnergyEvseMode/types/init.lua @@ -0,0 +1,17 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + local req_loc = string.format("EnergyEvseMode.types.%s", key) + local cluster_type = require(req_loc) + types_mt.__types_cache[key] = cluster_type + end + return types_mt.__types_cache[key] +end + +local EnergyEvseModeTypes = {} + +setmetatable(EnergyEvseModeTypes, types_mt) + +return EnergyEvseModeTypes + diff --git a/drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua b/drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua new file mode 100644 index 0000000000..afad08d086 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/embedded_cluster_utils.lua @@ -0,0 +1,59 @@ +local clusters = require "st.matter.clusters" +local utils = require "st.utils" +local version = require "version" + +if version.api < 11 then + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.EnergyEvse = require "EnergyEvse" + clusters.EnergyEvseMode = require "EnergyEvseMode" +end + +--this cluster is not supported in any releases of the lua libs +clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" + +local embedded_cluster_utils = {} + +local embedded_clusters = { + [clusters.ElectricalPowerMeasurement.ID] = clusters.ElectricalPowerMeasurement, + [clusters.EnergyEvse.ID] = clusters.EnergyEvse, + [clusters.DeviceEnergyManagementMode.ID] = clusters.DeviceEnergyManagementMode, + [clusters.ElectricalEnergyMeasurement.ID] = clusters.ElectricalEnergyMeasurement, + [clusters.EnergyEvseMode.ID] = clusters.EnergyEvseMode, +} + +function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) + if embedded_clusters[cluster_id] ~= nil then + local embedded_cluster = embedded_clusters[cluster_id] + local opts = opts or {} + if utils.table_size(opts) > 1 then + device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") + return + end + local clus_has_features = function(clus, feature_bitmap) + if not feature_bitmap or not clus then return false end + return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) + end + local eps = {} + for _, ep in ipairs(device.endpoints) do + for _, clus in ipairs(ep.clusters) do + if ((clus.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) + and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") + or (opts.cluster_type == clus.cluster_type)) + or (cluster_id == nil)) then + table.insert(eps, ep.endpoint_id) + if cluster_id == nil then break end + end + end + end + table.sort(eps) + return eps + else + local eps = device:get_endpoints(cluster_id, opts) + table.sort(eps) + return eps + end + end + + return embedded_cluster_utils \ No newline at end of file diff --git a/drivers/SmartThings/matter-evse/src/init.lua b/drivers/SmartThings/matter-evse/src/init.lua new file mode 100644 index 0000000000..04b3b6a64f --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/init.lua @@ -0,0 +1,699 @@ +-- Copyright 2024 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 capabilities = require "st.capabilities" +local MatterDriver = require "st.matter.driver" +local clusters = require "st.matter.clusters" +local log = require "log" +local utils = require "st.utils" +local matter_driver_template = {} +local embedded_cluster_utils = require "embedded_cluster_utils" + +local version = require "version" + +if version.api < 11 then + clusters.EnergyEvse = require "EnergyEvse" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.EnergyEvseMode = require "EnergyEvseMode" +end + +--this cluster is not supported in any releases of the lua libs +clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" + +local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map" +local SUPPORTED_EVSE_MODES_MAP = "__supported_evse_modes_map" +local SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP = "__supported_device_energy_management_modes_map" +local RECURRING_REPORT_POLL_TIMER = "__recurring_report_poll_timer" +local RECURRING_POLL_TIMER = "__recurring_poll_timer" +local LAST_REPORTED_TIME = "__last_reported_time" +local POWER_CONSUMPTION_REPORT_TIME_INTERVAL = "__pcr_time_interval" +local DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED = "__timer_interval_considered" +-- total in case there are multiple electrical sensors +local TOTAL_CUMULATIVE_ENERGY_IMPORTED = "__total_cumulative_energy_imported" + +local TIMER_REPEAT = (1 * 60) -- 1 minute +local REPORT_TIMEOUT = (15 * 60) -- Report the value each 15 minutes +local MAX_CHARGING_CURRENT_CONSTRAINT = 80000 -- In v1.3 release of stack, this check for 80 A is performed. + +local ELECTRICAL_SENSOR_DEVICE_ID = 0x0510 +local DEVICE_ENERGY_MANAGEMENT_DEVICE_ID = 0x050D + +local function endpoint_to_component(device, ep) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + for component, endpoint in pairs(map) do + if endpoint == ep then + return component + end + end + return "main" +end + +local function component_to_endpoint(device, component) + local map = device:get_field(COMPONENT_TO_ENDPOINT_MAP) or {} + if map[component] then + return map[component] + else + return device.MATTER_DEFAULT_ENDPOINT + end +end + +local function get_endpoints_for_dt(device, device_type) + local endpoints = {} + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == device_type then + table.insert(endpoints, ep.endpoint_id) + break + end + end + end + table.sort(endpoints) + return endpoints +end + +local function time_zone_offset() + return os.difftime(os.time(), os.time(os.date("!*t", os.time()))) +end + +local function iso8601_to_epoch(iso8061Timestamp) + local pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)" + local year, month, day, hour, mins, sec = iso8061Timestamp:match(pattern) + local time_tab = { + year = tonumber(year), + month = tonumber(month), + day = tonumber(day), + hour = tonumber(hour), + min = tonumber(mins), + sec = tonumber(sec), + isdst = false + } + return math.floor(os.time(time_tab) + time_zone_offset()) +end + +local function epoch_to_iso8601(time) + return os.date("!%Y-%m-%dT%H:%M:%SZ", time) +end + +local function tbl_contains(array, value) + for _, element in ipairs(array) do + if element == value then + return true + end + end + return false +end + +-- MAPS -- +local EVSE_STATE_ENUM_MAP = { + -- Since PLUGGED_IN_DISCHARGING is not to be supported, it is not checked. + [clusters.EnergyEvse.types.StateEnum.NOT_PLUGGED_IN] = capabilities.evseState.state.notPluggedIn, + [clusters.EnergyEvse.types.StateEnum.PLUGGED_IN_NO_DEMAND] = capabilities.evseState.state.pluggedInNoDemand, + [clusters.EnergyEvse.types.StateEnum.PLUGGED_IN_DEMAND] = capabilities.evseState.state.pluggedInDemand, + [clusters.EnergyEvse.types.StateEnum.PLUGGED_IN_CHARGING] = capabilities.evseState.state.pluggedInCharging, + [clusters.EnergyEvse.types.StateEnum.SESSION_ENDING] = capabilities.evseState.state.sessionEnding, + [clusters.EnergyEvse.types.StateEnum.FAULT] = capabilities.evseState.state.fault, +} + +local EVSE_SUPPLY_STATE_ENUM_MAP = { + [clusters.EnergyEvse.types.SupplyStateEnum.DISABLED] = capabilities.evseState.supplyState.disabled, + [clusters.EnergyEvse.types.SupplyStateEnum.CHARGING_ENABLED] = capabilities.evseState.supplyState.chargingEnabled, + [clusters.EnergyEvse.types.SupplyStateEnum.DISCHARGING_ENABLED] = capabilities.evseState.supplyState.dischargingEnabled, + [clusters.EnergyEvse.types.SupplyStateEnum.DISABLED_ERROR] = capabilities.evseState.supplyState.disabledError, + [clusters.EnergyEvse.types.SupplyStateEnum.DISABLED_DIAGNOSTICS] = capabilities.evseState.supplyState.disabledDiagnostics, +} + +local EVSE_FAULT_STATE_ENUM_MAP = { + [clusters.EnergyEvse.types.FaultStateEnum.NO_ERROR] = capabilities.evseState.faultState.noError, + [clusters.EnergyEvse.types.FaultStateEnum.METER_FAILURE] = capabilities.evseState.faultState.meterFailure, + [clusters.EnergyEvse.types.FaultStateEnum.OVER_VOLTAGE] = capabilities.evseState.faultState.overVoltage, + [clusters.EnergyEvse.types.FaultStateEnum.UNDER_VOLTAGE] = capabilities.evseState.faultState.underVoltage, + [clusters.EnergyEvse.types.FaultStateEnum.OVER_CURRENT] = capabilities.evseState.faultState.overCurrent, + [clusters.EnergyEvse.types.FaultStateEnum.CONTACT_WET_FAILURE] = capabilities.evseState.faultState.contactWetFailure, + [clusters.EnergyEvse.types.FaultStateEnum.CONTACT_DRY_FAILURE] = capabilities.evseState.faultState.contactDryFailure, + [clusters.EnergyEvse.types.FaultStateEnum.GROUND_FAULT] = capabilities.evseState.faultState.groundFault, + [clusters.EnergyEvse.types.FaultStateEnum.POWER_LOSS] = capabilities.evseState.faultState.powerLoss, + [clusters.EnergyEvse.types.FaultStateEnum.POWER_QUALITY] = capabilities.evseState.faultState.powerQuality, + [clusters.EnergyEvse.types.FaultStateEnum.PILOT_SHORT_CIRCUIT] = capabilities.evseState.faultState.pilotShortCircuit, + [clusters.EnergyEvse.types.FaultStateEnum.EMERGENCY_STOP] = capabilities.evseState.faultState.emergencyStop, + [clusters.EnergyEvse.types.FaultStateEnum.EV_DISCONNECTED] = capabilities.evseState.faultState.eVDisconnected, + [clusters.EnergyEvse.types.FaultStateEnum.WRONG_POWER_SUPPLY] = capabilities.evseState.faultState.wrongPowerSupply, + [clusters.EnergyEvse.types.FaultStateEnum.LIVE_NEUTRAL_SWAP] = capabilities.evseState.faultState.liveNeutralSwap, + [clusters.EnergyEvse.types.FaultStateEnum.OVER_TEMPERATURE] = capabilities.evseState.faultState.overTemperature, + [clusters.EnergyEvse.types.FaultStateEnum.OTHER] = capabilities.evseState.faultState.other, +} + +local function read_cumulative_energy_imported(device) + local electrical_energy_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) + if electrical_energy_meas_eps and #electrical_energy_meas_eps > 0 then + local read_req = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device, electrical_energy_meas_eps[1]) + for i, ep in ipairs(electrical_energy_meas_eps) do + if i > 1 then + read_req:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read(device, ep)) + end + end + device:send(read_req) + end +end + +local function create_poll_schedule(device) + local poll_timer = device:get_field(RECURRING_POLL_TIMER) + if poll_timer ~= nil then + return + end + + -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + local timer = device.thread:call_on_schedule(TIMER_REPEAT, function() + read_cumulative_energy_imported(device) + end, "polling_schedule_timer") + + device:set_field(RECURRING_POLL_TIMER, timer) +end + +local function create_poll_report_schedule(device) + local polling_schedule_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) + if polling_schedule_timer ~= nil then + return + end + + -- The powerConsumption report needs to be updated at least every 15 minutes in order to be included in SmartThings Energy + local pcr_interval = device:get_field(POWER_CONSUMPTION_REPORT_TIME_INTERVAL) or REPORT_TIMEOUT + local timer = device.thread:call_on_schedule(pcr_interval, function() + local current_time = os.time() + local last_time = device:get_field(LAST_REPORTED_TIME) or 0 + local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} + local total_energy = 0 + + -- we sum up total cumulative energy across all electrical sensor endpoints + for _, energyWh in pairs(total_cumulative_energy_imported) do + total_energy = total_energy + energyWh + end + + device:set_field(LAST_REPORTED_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) or { energy = 0 } + + local deltaEnergyWh = math.max(total_energy - previousTotalConsumptionWh.energy, 0.0) + local startTime = epoch_to_iso8601(last_time) + local endTime = epoch_to_iso8601(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 = total_energy + })) + end, "polling_report_schedule_timer") + + device:set_field(RECURRING_REPORT_POLL_TIMER, timer) +end + +local function create_poll_schedules_for_cumulative_energy_imported(device) + if not device:supports_capability(capabilities.powerConsumptionReport) then + return + end + create_poll_schedule(device) + create_poll_report_schedule(device) +end + +local function delete_poll_schedules(device) + local poll_timer = device:get_field(RECURRING_POLL_TIMER) + local reporting_poll_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) + if poll_timer ~= nil then + device.thread:cancel_timer(poll_timer) + device:set_field(RECURRING_POLL_TIMER, nil) + end + if reporting_poll_timer ~= nil then + device.thread:cancel_timer(reporting_poll_timer) + device:set_field(RECURRING_REPORT_POLL_TIMER, nil) + end +end + +-- Lifecycle Handlers -- +local function device_init(driver, device) + device:subscribe() + device:set_endpoint_to_component_fn(endpoint_to_component) + device:set_component_to_endpoint_fn(component_to_endpoint) + create_poll_schedules_for_cumulative_energy_imported(device) + local current_time = os.time() + local current_time_iso8601 = epoch_to_iso8601(current_time) + -- emit current time by default + device:emit_event(capabilities.evseChargingSession.targetEndTime(current_time_iso8601)) +end + +local function device_added(driver, device) + local electrical_sensor_eps = get_endpoints_for_dt(device, ELECTRICAL_SENSOR_DEVICE_ID) or {} + local device_energy_mgmt_eps = get_endpoints_for_dt(device, DEVICE_ENERGY_MANAGEMENT_DEVICE_ID) or {} + local component_to_endpoint_map = { + ["main"] = device.MATTER_DEFAULT_ENDPOINT, + ["electricalSensor"] = electrical_sensor_eps[1], + ["deviceEnergyManagement"] = device_energy_mgmt_eps[1] + } + log.debug("component_to_endpoint_map " .. utils.stringify_table(component_to_endpoint_map)) + device:set_field(COMPONENT_TO_ENDPOINT_MAP, component_to_endpoint_map, { persist = true }) +end + +local function do_configure(driver, device) + local power_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID) or {} + local energy_meas_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) or {} + local device_energy_mgmt_eps = embedded_cluster_utils.get_endpoints(device, clusters.DeviceEnergyManagementMode) or {} + local profile_name = "evse" + + -- As per spec, at least one of the electrical energy measurement or electrical power measurement clusters are to be supported. + if #energy_meas_eps > 0 then + profile_name = profile_name .. "-energy-meas" + end + if #power_meas_eps > 0 then + profile_name = profile_name .. "-power-meas" + end + + if #device_energy_mgmt_eps > 0 then + profile_name = profile_name .. "-energy-mgmt-mode" + end + + device.log.info_with({ hub_logs = true }, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({ profile = profile_name }) +end + +local function info_changed(driver, device) + for cap_id, attributes in pairs(matter_driver_template.subscribed_attributes) do + if device:supports_capability_by_id(cap_id) then + for _, attr in ipairs(attributes) do + device:add_subscribed_attribute(attr) + end + end + end + device:subscribe() + create_poll_schedules_for_cumulative_energy_imported(device) +end + +local function device_removed(driver, device) + delete_poll_schedules(device) +end + +-- Matter Handlers -- +local function charging_readiness_state_handler(driver, device, evse_state, evse_supply_state) + local event = capabilities.evseChargingSession.chargingState.stopped({state_change = true}) + if evse_supply_state.NAME == capabilities.evseState.supplyState.disabledError.NAME or + evse_supply_state.NAME == capabilities.evseState.supplyState.disabledDiagnostics.NAME or + evse_state.NAME == capabilities.evseState.state.fault.NAME then + event = capabilities.evseChargingSession.chargingState.disabled({state_change = true}) + elseif evse_supply_state.NAME == capabilities.evseState.supplyState.chargingEnabled.NAME then + event = capabilities.evseChargingSession.chargingState.charging({state_change = true}) + end + device:emit_event(event) +end + +local function evse_state_handler(driver, device, ib, response) + local evse_state = ib.data.value + local latest_supply_state = device:get_latest_state( + "main", + capabilities.evseState.ID, + capabilities.evseState.supplyState.NAME + ) + local event = EVSE_STATE_ENUM_MAP[evse_state] + if event then + device:emit_event_for_endpoint(ib.endpoint_id, event()) + charging_readiness_state_handler(driver, device, event, {NAME=latest_supply_state}) + else + log.warn("evse_state_handler invalid EVSE State: " .. evse_state) + end +end + +local function evse_supply_state_handler(driver, device, ib, response) + local evse_supply_state = ib.data.value + + local latest_evse_state = device:get_latest_state( + device:endpoint_to_component(ib.endopint_id), + capabilities.evseState.ID, + capabilities.evseState.state.NAME + ) + local event = EVSE_SUPPLY_STATE_ENUM_MAP[evse_supply_state] + if event then + device:emit_event_for_endpoint(ib.endpoint_id, event()) + charging_readiness_state_handler(driver, device, {NAME=latest_evse_state}, event) + else + log.warn("evse_supply_state_handler invalid EVSE Supply State: " .. evse_supply_state) + end +end + +local function evse_fault_state_handler(driver, device, ib, response) + local evse_fault_state = ib.data.value + local event = EVSE_FAULT_STATE_ENUM_MAP[evse_fault_state] + if event then + device:emit_event_for_endpoint(ib.endpoint_id, event()) + return + end + log.warn("Invalid EVSE fault state received: " .. evse_fault_state) +end + +local function evse_charging_enabled_until_handler(driver, device, ib, response) + local ep = ib.endpoint_id + local targetEndTime = ib.data.value + if targetEndTime ~= nil then + if targetEndTime == 0 then --if we get 0 we update with current time. + targetEndTime = os.time() + end + targetEndTime = epoch_to_iso8601(targetEndTime) + device:emit_event_for_endpoint(ep, capabilities.evseChargingSession.targetEndTime(targetEndTime)) + return + end + log.warn("Charging enabled handler received an invalid target end time, not reporting") +end + +local function evse_current_limit_handler(event) + return function(driver, device, ib, response) + local data = ib.data.value + local ep = ib.endpoint_id + if data then + device:emit_event_for_endpoint(ep, event(data)) + return + end + log.warn("Failed to emit capability for " .. event.NAME) + end +end + +local function evse_session_duration_handler(driver, device, ib, response) + local session_duration = ib.data.value + local endpoint_id = ib.endpoint_id + if session_duration then + device:emit_event_for_endpoint(endpoint_id, capabilities.evseChargingSession.sessionTime(session_duration)) + return + end + log.warn("evse_session_duration_handler received invalid EVSE Session Duration") +end + +local function evse_session_energy_charged_handler(driver, device, ib, response) + local charged_energy = ib.data.value + local endpoint_id = ib.endpoint_id + if charged_energy then + device:emit_event_for_endpoint(endpoint_id, capabilities.evseChargingSession.energyDelivered(charged_energy)) + return + end + log.warn("evse_session_energy_charged_handler received invalid EVSE Session Energy Charged") +end + +local function power_mode_handler(driver, device, ib, response) + local power_mode = ib.data.value + local endpoint_id = ib.endpoint_id + + if power_mode == clusters.ElectricalPowerMeasurement.types.PowerModeEnum.AC then + device:emit_event_for_endpoint(endpoint_id, capabilities.powerSource.powerSource.mains()) + elseif power_mode == clusters.ElectricalPowerMeasurement.types.PowerModeEnum.DC then + device:emit_event_for_endpoint(endpoint_id, capabilities.powerSource.powerSource.dc()) + else + device:emit_event_for_endpoint(endpoint_id, capabilities.powerSource.powerSource.unknown()) + end +end + +local function energy_evse_supported_modes_attr_handler(driver, device, ib, response) + local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {} + local supportedEvseModes = {} + for _, mode in ipairs(ib.data.elements) do + clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode) + table.insert(supportedEvseModes, mode.elements.label.value) + end + supportedEvseModesMap[ib.endpoint_id] = supportedEvseModes + device:set_field(SUPPORTED_EVSE_MODES_MAP, supportedEvseModesMap, { persist = true }) + local event = capabilities.mode.supportedModes(supportedEvseModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportedEvseModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function energy_evse_mode_attr_handler(driver, device, ib, response) + device.log.info(string.format("energy_evse_modes_attr_handler currentMode: %s", ib.data.value)) + + local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {} + local supportedEvseModes = supportedEvseModesMap[ib.endpoint_id] or {} + local currentMode = ib.data.value + for i, mode in ipairs(supportedEvseModes) do + if i - 1 == currentMode then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode)) + break + end + end +end + +local function device_energy_mgmt_supported_modes_attr_handler(driver, device, ib, response) + local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {} + local supportedDeviceEnergyMgmtModes = {} + for _, mode in ipairs(ib.data.elements) do + clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode) + table.insert(supportedDeviceEnergyMgmtModes, mode.elements.label.value) + end + supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] = supportedDeviceEnergyMgmtModes + device:set_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP, supportedDeviceEnergyMgmtModesMap, { persist = true }) + local event = capabilities.mode.supportedModes(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) + event = capabilities.mode.supportedArguments(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } }) + device:emit_event_for_endpoint(ib.endpoint_id, event) +end + +local function device_energy_mgmt_mode_attr_handler(driver, device, ib, response) + device.log.info(string.format("device_energy_mgmt_mode_attr_handler currentMode: %s", ib.data.value)) + + local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {} + local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] or {} + local currentMode = ib.data.value + for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do + if i - 1 == currentMode then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.mode.mode(mode)) + break + end + end +end + +local function cumulative_energy_imported_handler(driver, device, ib, response) + if ib.data then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) + local cumulative_energy_imported = ib.data.elements.energy.value + local endpoint_id = string.format(ib.endpoint_id) + local cumulative_energy_imported_Wh = utils.round(cumulative_energy_imported / 1000) + local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} + + -- in case there are multiple electrical sensors store them in a table. + total_cumulative_energy_imported[endpoint_id] = cumulative_energy_imported_Wh + device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED, total_cumulative_energy_imported, { persist = true }) + end +end + +local function periodic_energy_imported_handler(driver, device, ib, response) + local endpoint_id = ib.endpoint_id + local cumul_eps = embedded_cluster_utils.get_endpoints(device, + clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) + + if ib.data then + if version.api < 11 then + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct:augment_type(ib.data) + end + + local start_timestamp = ib.data.elements.start_timestamp.value or 0 + local end_timestamp = ib.data.elements.end_timestamp.value or 0 + + local device_reporting_time_interval = end_timestamp - start_timestamp + if not device:get_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED) and device_reporting_time_interval > REPORT_TIMEOUT then + -- This is a one time setup in order to consider a larger time interval if the interval the device chooses to report is greater than 15 minutes. + device:set_field(DEVICE_REPORTED_TIME_INTERVAL_CONSIDERED, true, {persist=true}) + local polling_schedule_timer = device:get_field(RECURRING_REPORT_POLL_TIMER) + if polling_schedule_timer ~= nil then + device.thread:cancel_timer(polling_schedule_timer) + end + device:set_field(POWER_CONSUMPTION_REPORT_TIME_INTERVAL, device_reporting_time_interval, {persist = true}) + create_poll_report_schedule(device) + end + + if tbl_contains(cumul_eps, endpoint_id) then + -- Since cluster in this endpoint supports both CUME & PERE features, we will prefer + -- cumulative_energy_imported_handler to handle the energy report for this endpoint over PeriodicEnergyImported. + return + end + + local energy_imported = ib.data.elements.energy.value + endpoint_id = string.format(ib.endpoint_id) + local energy_imported_Wh = utils.round(energy_imported / 1000) + local total_cumulative_energy_imported = device:get_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED) or {} + + -- in case there are multiple electrical sensors store them in a table. + total_cumulative_energy_imported[endpoint_id] = total_cumulative_energy_imported[endpoint_id] or 0 + total_cumulative_energy_imported[endpoint_id] = total_cumulative_energy_imported[endpoint_id] + energy_imported_Wh + device:set_field(TOTAL_CUMULATIVE_ENERGY_IMPORTED, total_cumulative_energy_imported, { persist = true }) + end +end + +-- Capability Handlers -- +local function get_latest_charging_parameters(device) + local min_charging_current = device:get_latest_state("main", capabilities.evseChargingSession.ID, + capabilities.evseChargingSession.minCurrent.NAME) or 0 + local max_charging_current = device:get_latest_state("main", capabilities.evseChargingSession.ID, + capabilities.evseChargingSession.maxCurrent.NAME) + local target_end_time_iso8601 = device:get_latest_state("main", capabilities.evseChargingSession.ID, + capabilities.evseChargingSession.targetEndTime.NAME) + return min_charging_current, max_charging_current, target_end_time_iso8601 +end + +local function handle_enable_charging(driver, device, cmd) + local ep = component_to_endpoint(device, cmd.component) + local default_min_current, default_max_current, default_charging_enabled_until_iso8601 = get_latest_charging_parameters( + device) + local charging_enabled_until_iso8601 = cmd.args.time or default_charging_enabled_until_iso8601 + local minimum_current = cmd.args.minCurrent or default_min_current + local maximum_current = cmd.args.maxCurrent or default_max_current + local charging_enabled_until_epoch_s = iso8601_to_epoch(charging_enabled_until_iso8601) + device:send(clusters.EnergyEvse.commands.EnableCharging(device, ep, charging_enabled_until_epoch_s, minimum_current, + maximum_current)) +end + +local handle_set_charging_parameters = function(cap, arg) + return function(driver, device, cmd) + if arg == "maxCurrent" and cmd.args[arg] > MAX_CHARGING_CURRENT_CONSTRAINT then + cmd.args[arg] = MAX_CHARGING_CURRENT_CONSTRAINT + log.warn_with({hub_logs=true}, "Clipping Max Current as it cannot be greater than 80A") + end + local capability_event = cap(cmd.args[arg]) + log.info("Setting value " .. (cmd.args[arg]) .. " for " .. (cap.NAME)) + device:emit_event(capability_event) + end +end + +local function handle_disable_charging(driver, device, cmd) + local ep = component_to_endpoint(device, cmd.component) + device:send(clusters.EnergyEvse.commands.Disable(device, ep)) +end + +local function handle_set_mode_command(driver, device, cmd) + local set_mode_handlers = { + ["main"] = function( ... ) + local ep = component_to_endpoint(device, cmd.component) + local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) + local supportedEvseModes = supportedEvseModesMap[ep] or {} + for i, mode in ipairs(supportedEvseModes) do + if cmd.args.mode == mode then + device:send(clusters.EnergyEvseMode.commands.ChangeToMode(device, ep, i - 1)) + return + end + end + log.warn("Received request to set unsupported mode for EnergyEvseMode.") + end, + ["deviceEnergyManagement"] = function( ... ) + local ep = component_to_endpoint(device, cmd.component) + local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) + local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ep] or {} + for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do + if cmd.args.mode == mode then + device:send(clusters.DeviceEnergyManagementMode.commands.ChangeToMode(device, ep, i - 1)) + return + end + end + log.warn("Received request to set unsupported mode for DeviceEnergyManagementMode.") + end + } + set_mode_handlers[cmd.component]() +end + +matter_driver_template = { + NAME = "matter-evse", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + infoChanged = info_changed, + removed = device_removed + }, + matter_handlers = { + attr = { + [clusters.EnergyEvse.ID] = { + [clusters.EnergyEvse.attributes.State.ID] = evse_state_handler, + [clusters.EnergyEvse.attributes.SupplyState.ID] = evse_supply_state_handler, + [clusters.EnergyEvse.attributes.FaultState.ID] = evse_fault_state_handler, + [clusters.EnergyEvse.attributes.ChargingEnabledUntil.ID] = evse_charging_enabled_until_handler, + [clusters.EnergyEvse.attributes.MinimumChargeCurrent.ID] = evse_current_limit_handler(capabilities + .evseChargingSession.minCurrent), + [clusters.EnergyEvse.attributes.MaximumChargeCurrent.ID] = evse_current_limit_handler(capabilities + .evseChargingSession.maxCurrent), + [clusters.EnergyEvse.attributes.SessionDuration.ID] = evse_session_duration_handler, + [clusters.EnergyEvse.attributes.SessionEnergyCharged.ID] = evse_session_energy_charged_handler, + }, + [clusters.ElectricalPowerMeasurement.ID] = { + [clusters.ElectricalPowerMeasurement.attributes.PowerMode.ID] = power_mode_handler, + }, + [clusters.EnergyEvseMode.ID] = { + [clusters.EnergyEvseMode.attributes.SupportedModes.ID] = energy_evse_supported_modes_attr_handler, + [clusters.EnergyEvseMode.attributes.CurrentMode.ID] = energy_evse_mode_attr_handler, + }, + [clusters.DeviceEnergyManagementMode.ID] = { + [clusters.DeviceEnergyManagementMode.attributes.SupportedModes.ID] = device_energy_mgmt_supported_modes_attr_handler, + [clusters.DeviceEnergyManagementMode.attributes.CurrentMode.ID] = device_energy_mgmt_mode_attr_handler, + }, + [clusters.ElectricalEnergyMeasurement.ID] = { + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported.ID] = cumulative_energy_imported_handler, + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported.ID] = periodic_energy_imported_handler + }, + }, + }, + subscribed_attributes = { + [capabilities.evseState.ID] = { + clusters.EnergyEvse.attributes.State, + clusters.EnergyEvse.attributes.SupplyState, + clusters.EnergyEvse.attributes.FaultState, + }, + [capabilities.evseChargingSession.ID] = { + clusters.EnergyEvse.attributes.ChargingEnabledUntil, + clusters.EnergyEvse.attributes.MinimumChargeCurrent, + clusters.EnergyEvse.attributes.MaximumChargeCurrent, + clusters.EnergyEvse.attributes.SessionDuration, + clusters.EnergyEvse.attributes.SessionEnergyCharged, + }, + [capabilities.powerSource.ID] = { + clusters.ElectricalPowerMeasurement.attributes.PowerMode, + }, + [capabilities.powerConsumptionReport.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + }, + [capabilities.mode.ID] = { + clusters.EnergyEvseMode.attributes.SupportedModes, + clusters.EnergyEvseMode.attributes.CurrentMode, + clusters.DeviceEnergyManagementMode.attributes.CurrentMode, + clusters.DeviceEnergyManagementMode.attributes.SupportedModes + } + }, + capability_handlers = { + [capabilities.evseChargingSession.ID] = { + [capabilities.evseChargingSession.commands.enableCharging.NAME] = handle_enable_charging, + [capabilities.evseChargingSession.commands.disableCharging.NAME] = handle_disable_charging, + [capabilities.evseChargingSession.commands.setTargetEndTime.NAME] = handle_set_charging_parameters(capabilities.evseChargingSession.targetEndTime, "time"), + [capabilities.evseChargingSession.commands.setMinCurrent.NAME] = handle_set_charging_parameters(capabilities.evseChargingSession.minCurrent, "minCurrent"), + [capabilities.evseChargingSession.commands.setMaxCurrent.NAME] = handle_set_charging_parameters(capabilities.evseChargingSession.maxCurrent, "maxCurrent"), + }, + [capabilities.mode.ID] = { + [capabilities.mode.commands.setMode.NAME] = handle_set_mode_command, + }, + }, + supported_capabilities = { + capabilities.evseState, + capabilities.evseChargingSession, + capabilities.powerSource, + capabilities.powerConsumptionReport, + capabilities.mode + }, +} + +local matter_driver = MatterDriver("matter-evse", matter_driver_template) +log.info(string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) +matter_driver:run() diff --git a/drivers/SmartThings/matter-evse/src/test/test_evse.lua b/drivers/SmartThings/matter-evse/src/test/test_evse.lua new file mode 100644 index 0000000000..c4c3640917 --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/test/test_evse.lua @@ -0,0 +1,511 @@ +-- Copyright 2024 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" +local log = require "log" + +local EVSE_EP = 1 +local ELECTRICAL_SENSOR_EP = 2 +local DEVICE_ENERGY_MANAGEMENT_DEVICE_EP = 3 + +clusters.EnergyEvse = require "EnergyEvse" +clusters.EnergyEvseMode = require "EnergyEvseMode" +clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" +clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("evse-power-meas-energy-mgmt-mode.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = EVSE_EP, + clusters = { + { cluster_id = clusters.EnergyEvse.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.EnergyEvseMode.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x050C, device_type_revision = 1 } -- EVSE + } + }, + { + endpoint_id = ELECTRICAL_SENSOR_EP, + clusters = { + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor + } + }, + { + endpoint_id = DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, + clusters = { + { cluster_id = clusters.DeviceEnergyManagementMode.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x050D, device_type_revision = 1 } -- Device Energy Management + } + }, + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.EnergyEvse.attributes.State, + clusters.EnergyEvse.attributes.SupplyState, + clusters.EnergyEvse.attributes.FaultState, + clusters.EnergyEvse.attributes.ChargingEnabledUntil, + clusters.EnergyEvse.attributes.MinimumChargeCurrent, + clusters.EnergyEvse.attributes.MaximumChargeCurrent, + clusters.EnergyEvse.attributes.SessionDuration, + clusters.EnergyEvse.attributes.SessionEnergyCharged, + clusters.ElectricalPowerMeasurement.attributes.PowerMode, + clusters.EnergyEvseMode.attributes.SupportedModes, + clusters.EnergyEvseMode.attributes.CurrentMode, + clusters.DeviceEnergyManagementMode.attributes.CurrentMode, + clusters.DeviceEnergyManagementMode.attributes.SupportedModes, + } + log.info("In test init", os.time()) + 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.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.evseChargingSession.targetEndTime("1970-01-01T00:00:00Z"))) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Assert component to endpoint map", + function() + local component_to_endpoint_map = mock_device:get_field("__component_to_endpoint_map") + assert(component_to_endpoint_map["electricalSensor"] == ELECTRICAL_SENSOR_EP, "Electrical Sensor Endpoint must be 2") + assert(component_to_endpoint_map["deviceEnergyManagement"] == DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, + "Device Energy Management Endpoint must be 3") + end +) + +test.register_message_test( + "EnergyEvse Supply State must trigger appropriate evseState supplystate capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.SupplyState:build_test_report_data(mock_device, EVSE_EP, + clusters.EnergyEvse.attributes.SupplyState.CHARGING_ENABLED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseState.supplyState.chargingEnabled()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.chargingState.charging({state_change = true})) + } + } +) + +test.register_message_test( + "EnergyEvse State must trigger appropriate evseState EvseState capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.SupplyState:build_test_report_data(mock_device, EVSE_EP, + clusters.EnergyEvse.attributes.SupplyState.CHARGING_ENABLED) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseState.supplyState.chargingEnabled()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.chargingState.charging({state_change = true})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.State:build_test_report_data(mock_device, EVSE_EP, + clusters.EnergyEvse.attributes.State.PLUGGED_IN_DEMAND) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseState.state.pluggedInDemand()) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.chargingState.charging({state_change = true})) + }, + } +) + +test.register_message_test( + "EnergyEvse Fault State must trigger appropriate evseState faultState capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.FaultState:build_test_report_data(mock_device, EVSE_EP, + clusters.EnergyEvse.attributes.FaultState.GROUND_FAULT) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseState.faultState.groundFault()) + } + } +) + +test.register_message_test( + "EnergyEvse ChargingEnabledUntil in epoch must trigger appropriate evseChargingSession targetEndTime capability event in iso8601 format", --1724399242 in epoch to 2024-08-23T07:47:22Z in iso8601 + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.ChargingEnabledUntil:build_test_report_data(mock_device, EVSE_EP, 1724399242) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.targetEndTime("2024-08-23T07:47:22Z")) + } + } +) + +test.register_message_test( + "EnergyEvse MinimumChargeCurrent constraint must trigger appropriate evseChargingSession minCurrent capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.MinimumChargeCurrent:build_test_report_data(mock_device, EVSE_EP, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.minCurrent(0)) + } + } +) + +test.register_message_test( + "EnergyEvse MaximumChargeCurrent constraint must trigger appropriate evseChargingSession maxCurrent capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.MaximumChargeCurrent:build_test_report_data(mock_device, EVSE_EP, 10000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.maxCurrent(10000)) + } + } +) + +test.register_message_test( + "EnergyEvse SessionDuration must trigger appropriate evseChargingSession sessionTime capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.SessionDuration:build_test_report_data(mock_device, EVSE_EP, 9000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.sessionTime(9000)) + } + } +) + +test.register_message_test( + "EnergyEvse SessionEnergyCharged must trigger appropriate evseChargingSession energyDelivered capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvse.attributes.SessionEnergyCharged:build_test_report_data(mock_device, EVSE_EP, 900000) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.evseChargingSession.energyDelivered(900000)) + } + } +) + +test.register_message_test( + "ElectricalPowerMeasurement PowerMode must trigger appropriate powerSource capability event", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalPowerMeasurement.attributes.PowerMode:build_test_report_data(mock_device, ELECTRICAL_SENSOR_EP, + clusters.ElectricalPowerMeasurement.attributes.PowerMode.AC) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("electricalSensor", + capabilities.powerSource.powerSource.mains()) + } + } +) + +test.register_message_test( + "EnergyEvseMode SupportedModes must be registered.\n2.CurrentMode must trigger approriate mode capability event.\n3.Command to setMode should trigger appropriate changeToMode matter command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvseMode.attributes.SupportedModes:build_test_report_data(mock_device, EVSE_EP, { + clusters.EnergyEvseMode.types.ModeOptionStruct({ + ["label"] = "Auto-Scheduled", + ["mode"] = 0, + ["mode_tags"] = { + clusters.EnergyEvseMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.EnergyEvseMode.types.ModeOptionStruct({ + ["label"] = "Manual", + ["mode"] = 1, + ["mode_tags"] = { + clusters.EnergyEvseMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }) + }) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedModes({ "Auto-Scheduled", "Manual" }, { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.supportedArguments({ "Auto-Scheduled", "Manual" }, { visibility = { displayed = false } })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.EnergyEvseMode.attributes.CurrentMode:build_test_report_data(mock_device, EVSE_EP, 1) --1 is the index for Manual EnergyEvse mode + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.mode.mode("Manual")) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "main", command = "setMode", args = { "Auto-Scheduled" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.EnergyEvseMode.commands.ChangeToMode(mock_device, EVSE_EP, 0) --Index is Auto-Scheduled + } + } + } +) + +test.register_message_test( + "DeviceEnergyManagementMode SupportedModes must be registered.\n2.CurrentMode must trigger approriate mode capability event.\n3.Command to setMode should trigger appropriate changeToMode matter command", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.DeviceEnergyManagementMode.attributes.SupportedModes:build_test_report_data(mock_device, + DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, { + clusters.DeviceEnergyManagementMode.types.ModeOptionStruct({ + ["label"] = "Grid Energy Management", + ["mode"] = 0, + ["mode_tags"] = { + clusters.DeviceEnergyManagementMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 0 }) + } + }), + clusters.DeviceEnergyManagementMode.types.ModeOptionStruct({ + ["label"] = "Home Energy Management", + ["mode"] = 1, + ["mode_tags"] = { + clusters.DeviceEnergyManagementMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 1 }) + } + }), + clusters.DeviceEnergyManagementMode.types.ModeOptionStruct({ + ["label"] = "Full Energy Management", + ["mode"] = 2, + ["mode_tags"] = { + clusters.DeviceEnergyManagementMode.types.ModeTagStruct({ ["mfg_code"] = 256, ["value"] = 2 }) + } + }) + }) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("deviceEnergyManagement", + capabilities.mode.supportedModes( + { "Grid Energy Management", "Home Energy Management", "Full Energy Management" }, + { visibility = { displayed = false } })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("deviceEnergyManagement", + capabilities.mode.supportedArguments( + { "Grid Energy Management", "Home Energy Management", "Full Energy Management" }, + { visibility = { displayed = false } })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.DeviceEnergyManagementMode.attributes.CurrentMode:build_test_report_data(mock_device, + DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, 1) --1 is the index for Home Energy Management mode + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("deviceEnergyManagement", + capabilities.mode.mode("Home Energy Management")) + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "deviceEnergyManagement", command = "setMode", args = { "Full Energy Management" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.DeviceEnergyManagementMode.commands.ChangeToMode(mock_device, DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, 2) --Index is Full Energy Management + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "mode", component = "deviceEnergyManagement", command = "setMode", args = { "Grid Energy Management" } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.DeviceEnergyManagementMode.commands.ChangeToMode(mock_device, DEVICE_ENERGY_MANAGEMENT_DEVICE_EP, 0) --Index is Grid Energy Management + } + } + } +) + +--TODO: Include tests for evseChargingSession capability commands. This is anyhow tested with EVSE virtual device app. + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua b/drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua new file mode 100644 index 0000000000..b5b51236bd --- /dev/null +++ b/drivers/SmartThings/matter-evse/src/test/test_evse_energy_meas.lua @@ -0,0 +1,216 @@ +-- Copyright 2024 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 clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local EVSE_EP = 1 +local ELECTRICAL_SENSOR_EP_ONE = 2 +local ELECTRICAL_SENSOR_EP_TWO = 3 + +clusters.EnergyEvse = require "EnergyEvse" +clusters.EnergyEvseMode = require "EnergyEvseMode" +clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" +clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" +clusters.DeviceEnergyManagementMode = require "DeviceEnergyManagementMode" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("evse-energy-meas.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = EVSE_EP, + clusters = { + { cluster_id = clusters.EnergyEvse.ID, cluster_type = "SERVER" }, + { cluster_id = clusters.EnergyEvseMode.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x050C, device_type_revision = 1 } -- EVSE + } + }, + { + endpoint_id = ELECTRICAL_SENSOR_EP_ONE, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor + } + }, + { + endpoint_id = ELECTRICAL_SENSOR_EP_TWO, + clusters = { + { cluster_id = clusters.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER" }, + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor + } + }, + } +}) + +local function test_init() + local cluster_subscribe_list = { + clusters.EnergyEvse.attributes.State, + clusters.EnergyEvse.attributes.SupplyState, + clusters.EnergyEvse.attributes.FaultState, + clusters.EnergyEvse.attributes.ChargingEnabledUntil, + clusters.EnergyEvse.attributes.MinimumChargeCurrent, + clusters.EnergyEvse.attributes.MaximumChargeCurrent, + clusters.EnergyEvse.attributes.SessionDuration, + clusters.EnergyEvse.attributes.SessionEnergyCharged, + clusters.EnergyEvseMode.attributes.SupportedModes, + clusters.EnergyEvseMode.attributes.CurrentMode, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported, + } + 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.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.evseChargingSession.targetEndTime("1970-01-01T00:00:00Z"))) +end +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Assert profile applied over doConfigure", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + mock_device:expect_metadata_update({ profile = "evse-energy-meas" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Ensure timers are created for the device", + function() + local poll_timer = mock_device:get_field("__recurring_poll_timer") + assert(poll_timer ~= nil, "poll_timer should exist") + + local report_poll_timer = mock_device:get_field("__recurring_report_poll_timer") + assert(report_poll_timer ~= nil, "report_poll_timer should exist") + end +) + +test.register_coroutine_test( + "Ensure timers are created for the device", + function() + test.socket.matter:__set_channel_ordering("relaxed") + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "removed" }) + test.wait_for_events() + + local poll_timer = mock_device:get_field("__recurring_poll_timer") + assert(poll_timer == nil, "poll_timer should not exist") + + local report_poll_timer = mock_device:get_field("__recurring_report_poll_timer") + assert(report_poll_timer == nil, "report_poll_timer should not exist") + end +) + +test.register_coroutine_test( + "Ensure that every 60 seconds the driver reads the CumulativeEnergyImported attribute for both endpoints", + function() + test.mock_time.advance_time(60) + test.socket.matter:__set_channel_ordering("relaxed") + local CumulativeEnergyImportedReadReq = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported + :read(mock_device, ELECTRICAL_SENSOR_EP_ONE) + CumulativeEnergyImportedReadReq:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read( + mock_device, ELECTRICAL_SENSOR_EP_TWO)) + test.socket.matter:__expect_send({ + mock_device.id, + CumulativeEnergyImportedReadReq + }) + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.register_coroutine_test( + "Ensure the total accumulated powerConsumption for both endpoints is reported every 15 minutes", + function() + test.socket.matter:__set_channel_ordering("relaxed") + test.socket.capability:__set_channel_ordering("relaxed") + + local CumulativeEnergyImportedReadReq = clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported + :read(mock_device, ELECTRICAL_SENSOR_EP_ONE) + CumulativeEnergyImportedReadReq:merge(clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported:read( + mock_device, ELECTRICAL_SENSOR_EP_TWO)) + + test.socket.matter:__expect_send({ + mock_device.id, + CumulativeEnergyImportedReadReq + }) + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP_ONE, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 100000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --100Wh + + test.socket.matter:__queue_receive({ mock_device.id, clusters.ElectricalEnergyMeasurement.attributes + .CumulativeEnergyImported:build_test_report_data(mock_device, + ELECTRICAL_SENSOR_EP_TWO, + clusters.ElectricalEnergyMeasurement.types.EnergyMeasurementStruct({ energy = 150000, start_timestamp = 0, end_timestamp = 0, start_systime = 0, end_systime = 0 })) }) --150Wh + + test.wait_for_events() + test.mock_time.advance_time(60 * 15) + + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ + energy = 250, + deltaEnergy = 250, + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:59Z" + })) + ) + + test.wait_for_events() + end, + { + test_init = function() + test_init() + test.timer.__create_and_queue_test_time_advance_timer(60 * 15, "interval", "create_poll_report_schedule") + test.timer.__create_and_queue_test_time_advance_timer(60, "interval", "create_poll_schedule") + end + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index 27fa823d6d..75d091db8e 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -148,3 +148,18 @@ matterGeneric: deviceTypes: - id: 0x0076 deviceProfileName: smoke-co + - id: "matter/rain/sensor" + deviceLabel: Matter Rain Sensor + deviceTypes: + - id: 0x0044 + deviceProfileName: rain-battery + - id: "matter/freeze/detector" + deviceLabel: Matter Water Freeze Detector + deviceTypes: + - id: 0x0041 + deviceProfileName: freeze-battery + - id: "matter/leak/detector" + deviceLabel: Matter Water Leak Detector + deviceTypes: + - id: 0x0043 + deviceProfileName: leak-battery diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault-freezeSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault-freezeSensitivity.yml new file mode 100644 index 0000000000..d01dac215c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault-freezeSensitivity.yml @@ -0,0 +1,25 @@ +name: freeze-battery-fault-freezeSensitivity +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector +preferences: + - preferenceId: freezeSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault.yml new file mode 100644 index 0000000000..4c14b5e4d7 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-fault.yml @@ -0,0 +1,23 @@ +name: freeze-battery-fault +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector + diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-battery-freezeSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-freezeSensitivity.yml new file mode 100644 index 0000000000..5fb72e6cf7 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-battery-freezeSensitivity.yml @@ -0,0 +1,23 @@ +name: freeze-battery-freezeSensitivity +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector +preferences: + - preferenceId: freezeSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-battery.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-battery.yml new file mode 100644 index 0000000000..c0cea79d51 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-battery.yml @@ -0,0 +1,20 @@ +name: freeze-battery +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-fault-freezeSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-fault-freezeSensitivity.yml new file mode 100644 index 0000000000..2d270fc29e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-fault-freezeSensitivity.yml @@ -0,0 +1,23 @@ +name: freeze-fault-freezeSensitivity +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector +preferences: + - preferenceId: freezeSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-fault.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-fault.yml new file mode 100644 index 0000000000..de91fc5034 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-fault.yml @@ -0,0 +1,20 @@ +name: freeze-fault +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterFreezeDetector diff --git a/drivers/SmartThings/matter-sensor/profiles/freeze-leak-fault-freezeSensitivity-leakSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/freeze-leak-fault-freezeSensitivity-leakSensitivity.yml new file mode 100644 index 0000000000..7211714c1e --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/freeze-leak-fault-freezeSensitivity-leakSensitivity.yml @@ -0,0 +1,27 @@ +name: freeze-leak-fault-freezeSensitivity-leakSensitivity +components: +- id: main + capabilities: + - id: temperatureAlarm + version: 1 + config: + values: + - key: "temperatureAlarm.value" + enabledValues: + - cleared + - freeze + - id: waterSensor + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor +preferences: + - preferenceId: freezeSensitivity + explicit: true + - preferenceId: leakSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault-leakSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault-leakSensitivity.yml new file mode 100644 index 0000000000..0071ff9687 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault-leakSensitivity.yml @@ -0,0 +1,19 @@ +name: leak-battery-fault-leakSensitivity +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor +preferences: + - preferenceId: leakSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault.yml b/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault.yml new file mode 100644 index 0000000000..6fd911c331 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-battery-fault.yml @@ -0,0 +1,16 @@ +name: leak-battery-fault +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-battery-leakSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/leak-battery-leakSensitivity.yml new file mode 100644 index 0000000000..d7eec73cc7 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-battery-leakSensitivity.yml @@ -0,0 +1,17 @@ +name: leak-battery-leakSensitivity +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor +preferences: + - preferenceId: leakSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-battery.yml b/drivers/SmartThings/matter-sensor/profiles/leak-battery.yml new file mode 100644 index 0000000000..6b90147bea --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-battery.yml @@ -0,0 +1,14 @@ +name: leak-battery +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-fault-leakSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/leak-fault-leakSensitivity.yml new file mode 100644 index 0000000000..51697afe81 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-fault-leakSensitivity.yml @@ -0,0 +1,17 @@ +name: leak-fault-leakSensitivity +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor +preferences: + - preferenceId: leakSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/leak-fault.yml b/drivers/SmartThings/matter-sensor/profiles/leak-fault.yml new file mode 100644 index 0000000000..3b79709f3a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/leak-fault.yml @@ -0,0 +1,14 @@ +name: leak-fault +components: +- id: main + capabilities: + - id: waterSensor + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: LeakSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault-rainSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault-rainSensitivity.yml new file mode 100644 index 0000000000..9418abc845 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault-rainSensitivity.yml @@ -0,0 +1,19 @@ +name: rain-battery-fault-rainSensitivity +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor +preferences: + - preferenceId: rainSensitivity + explicit: true \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault.yml b/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault.yml new file mode 100644 index 0000000000..f1db9cafb8 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-battery-fault.yml @@ -0,0 +1,16 @@ +name: rain-battery-fault +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: battery + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-battery-rainSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/rain-battery-rainSensitivity.yml new file mode 100644 index 0000000000..ee3c7c1a65 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-battery-rainSensitivity.yml @@ -0,0 +1,17 @@ +name: rain-battery-rainSensitivity +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor +preferences: + - preferenceId: rainSensitivity + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-battery.yml b/drivers/SmartThings/matter-sensor/profiles/rain-battery.yml new file mode 100644 index 0000000000..ca794aeaaa --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-battery.yml @@ -0,0 +1,14 @@ +name: rain-battery +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-fault-rainSensitivity.yml b/drivers/SmartThings/matter-sensor/profiles/rain-fault-rainSensitivity.yml new file mode 100644 index 0000000000..9f080c12b6 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-fault-rainSensitivity.yml @@ -0,0 +1,17 @@ +name: rain-fault-rainSensitivity +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor +preferences: + - preferenceId: rainSensitivity + explicit: true \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/profiles/rain-fault.yml b/drivers/SmartThings/matter-sensor/profiles/rain-fault.yml new file mode 100644 index 0000000000..c5a3a8866a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/rain-fault.yml @@ -0,0 +1,14 @@ +name: rain-fault +components: +- id: main + capabilities: + - id: rainSensor + version: 1 + - id: hardwareFault + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: RainSensor diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua new file mode 100644 index 0000000000..f5258619fa --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/init.lua @@ -0,0 +1,81 @@ +local cluster_base = require "st.matter.cluster_base" +local BooleanStateConfigurationServerAttributes = require "BooleanStateConfiguration.server.attributes" +local BooleanStateConfigurationTypes = require "BooleanStateConfiguration.types" + +local BooleanStateConfiguration = {} + +BooleanStateConfiguration.ID = 0x0080 +BooleanStateConfiguration.NAME = "BooleanStateConfiguration" +BooleanStateConfiguration.server = {} +BooleanStateConfiguration.client = {} +BooleanStateConfiguration.server.attributes = BooleanStateConfigurationServerAttributes:set_parent_cluster(BooleanStateConfiguration) +BooleanStateConfiguration.types = BooleanStateConfigurationTypes + +function BooleanStateConfiguration:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "CurrentSensitivityLevel", + [0x0001] = "SupportedSensitivityLevels", + [0x0002] = "DefaultSensitivityLevel", + [0x0003] = "AlarmsActive", + [0x0004] = "AlarmsSuppressed", + [0x0005] = "AlarmsEnabled", + [0x0006] = "AlarmsSupported", + [0x0007] = "SensorFault", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +BooleanStateConfiguration.attribute_direction_map = { + ["CurrentSensitivityLevel"] = "server", + ["SupportedSensitivityLevels"] = "server", + ["DefaultSensitivityLevel"] = "server", + ["AlarmsActive"] = "server", + ["AlarmsSuppressed"] = "server", + ["AlarmsEnabled"] = "server", + ["AlarmsSupported"] = "server", + ["SensorFault"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +do + local has_aliases, aliases = pcall(require, "BooleanStateConfiguration.server.attributes") + if has_aliases then + for alias, _ in pairs(aliases) do + BooleanStateConfiguration.attribute_direction_map[alias] = "server" + end + end +end + +BooleanStateConfiguration.FeatureMap = BooleanStateConfiguration.types.Feature + +function BooleanStateConfiguration.are_features_supported(feature, feature_map) + if (BooleanStateConfiguration.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = BooleanStateConfiguration.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, BooleanStateConfiguration.NAME)) + end + return BooleanStateConfiguration[direction].attributes[key] +end +BooleanStateConfiguration.attributes = {} +setmetatable(BooleanStateConfiguration.attributes, attribute_helper_mt) + +setmetatable(BooleanStateConfiguration, {__index = cluster_base}) + +return BooleanStateConfiguration + diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua new file mode 100644 index 0000000000..ea28550422 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/CurrentSensitivityLevel.lua @@ -0,0 +1,80 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentSensitivityLevel = { + ID = 0x0000, + NAME = "CurrentSensitivityLevel", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentSensitivityLevel:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentSensitivityLevel:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CurrentSensitivityLevel:write(device, endpoint_id, value) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.write( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, --event_id + data + ) +end + +function CurrentSensitivityLevel:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function CurrentSensitivityLevel:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentSensitivityLevel:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentSensitivityLevel:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentSensitivityLevel, {__call = CurrentSensitivityLevel.new_value, __index = CurrentSensitivityLevel.base_type}) +return CurrentSensitivityLevel diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua new file mode 100644 index 0000000000..dc343735dc --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/DefaultSensitivityLevel.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local DefaultSensitivityLevel = { + ID = 0x0002, + NAME = "DefaultSensitivityLevel", + base_type = require "st.matter.data_types.Uint8", +} + +function DefaultSensitivityLevel:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function DefaultSensitivityLevel:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function DefaultSensitivityLevel:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function DefaultSensitivityLevel:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function DefaultSensitivityLevel:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function DefaultSensitivityLevel:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(DefaultSensitivityLevel, {__call = DefaultSensitivityLevel.new_value, __index = DefaultSensitivityLevel.base_type}) +return DefaultSensitivityLevel diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua new file mode 100644 index 0000000000..83db4bc404 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SensorFault.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SensorFault = { + ID = 0x0007, + NAME = "SensorFault", + base_type = require "BooleanStateConfiguration.types.SensorFaultBitmap", +} + +function SensorFault:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function SensorFault:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SensorFault:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function SensorFault:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SensorFault:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SensorFault:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(SensorFault, {__call = SensorFault.new_value, __index = SensorFault.base_type}) +return SensorFault \ No newline at end of file diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua new file mode 100644 index 0000000000..c80177d043 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/SupportedSensitivityLevels.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local SupportedSensitivityLevels = { + ID = 0x0001, + NAME = "SupportedSensitivityLevels", + base_type = require "st.matter.data_types.Uint8", +} + +function SupportedSensitivityLevels:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function SupportedSensitivityLevels:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupportedSensitivityLevels:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function SupportedSensitivityLevels:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function SupportedSensitivityLevels:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function SupportedSensitivityLevels:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(SupportedSensitivityLevels, {__call = SupportedSensitivityLevels.new_value, __index = SupportedSensitivityLevels.base_type}) +return SupportedSensitivityLevels diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua new file mode 100644 index 0000000000..47ef55963b --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/server/attributes/init.lua @@ -0,0 +1,25 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("BooleanStateConfiguration.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local BooleanStateConfigurationServerAttributes = {} + +function BooleanStateConfigurationServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(BooleanStateConfigurationServerAttributes, attr_mt) + +return BooleanStateConfigurationServerAttributes + + diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua new file mode 100644 index 0000000000..3a4cb77058 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/Feature.lua @@ -0,0 +1,119 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.VISUAL = 0x0001 +Feature.AUDIBLE = 0x0002 +Feature.ALARM_SUPPRESS = 0x0004 +Feature.SENSITIVITY_LEVEL = 0x0008 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + VISUAL = 0x0001, + AUDIBLE = 0x0002, + ALARM_SUPPRESS = 0x0004, + SENSITIVITY_LEVEL = 0x0008, +} + +Feature.is_visual_set = function(self) + return (self.value & self.VISUAL) ~= 0 +end + +Feature.set_visual = function(self) + if self.value ~= nil then + self.value = self.value | self.VISUAL + else + self.value = self.VISUAL + end +end + +Feature.unset_visual = function(self) + self.value = self.value & (~self.VISUAL & self.BASE_MASK) +end +Feature.is_audible_set = function(self) + return (self.value & self.AUDIBLE) ~= 0 +end + +Feature.set_audible = function(self) + if self.value ~= nil then + self.value = self.value | self.AUDIBLE + else + self.value = self.AUDIBLE + end +end + +Feature.unset_audible = function(self) + self.value = self.value & (~self.AUDIBLE & self.BASE_MASK) +end + +Feature.is_alarm_suppress_set = function(self) + return (self.value & self.ALARM_SUPPRESS) ~= 0 +end + +Feature.set_alarm_suppress = function(self) + if self.value ~= nil then + self.value = self.value | self.ALARM_SUPPRESS + else + self.value = self.ALARM_SUPPRESS + end +end + +Feature.unset_alarm_suppress = function(self) + self.value = self.value & (~self.ALARM_SUPPRESS & self.BASE_MASK) +end + +Feature.is_sensitivity_level_set = function(self) + return (self.value & self.SENSITIVITY_LEVEL) ~= 0 +end + +Feature.set_sensitivity_level = function(self) + if self.value ~= nil then + self.value = self.value | self.SENSITIVITY_LEVEL + else + self.value = self.SENSITIVITY_LEVEL + end +end + +Feature.unset_sensitivity_level = function(self) + self.value = self.value & (~self.SENSITIVITY_LEVEL & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.VISUAL | + Feature.AUDIBLE | + Feature.ALARM_SUPPRESS | + Feature.SENSITIVITY_LEVEL + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_visual_set = Feature.is_visual_set, + set_visual = Feature.set_visual, + unset_visual = Feature.unset_visual, + is_audible_set = Feature.is_audible_set, + set_audible = Feature.set_audible, + unset_audible = Feature.unset_audible, + is_alarm_suppress_set = Feature.is_alarm_suppress_set, + set_alarm_suppress = Feature.set_alarm_suppress, + unset_alarm_suppress = Feature.unset_alarm_suppress, + is_sensitivity_level_set = Feature.is_sensitivity_level_set, + set_sensitivity_level = Feature.set_sensitivity_level, + unset_sensitivity_level = Feature.unset_sensitivity_level, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua new file mode 100644 index 0000000000..c9399e50f1 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/SensorFaultBitmap.lua @@ -0,0 +1,44 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local SensorFaultBitmap = {} +local new_mt = UintABC.new_mt({NAME = "SensorFaultBitmap", ID = data_types.name_to_id_map["Uint16"]}, 2) + +SensorFaultBitmap.BASE_MASK = 0xFFFF +SensorFaultBitmap.GENERAL_FAULT = 0x0001 + +SensorFaultBitmap.mask_fields = { + BASE_MASK = 0xFFFF, + GENERAL_FAULT = 0x0001, +} + +SensorFaultBitmap.is_general_fault_set = function(self) + return (self.value & self.GENERAL_FAULT) ~= 0 +end + +SensorFaultBitmap.set_general_fault = function(self) + if self.value ~= nil then + self.value = self.value | self.GENERAL_FAULT + else + self.value = self.GENERAL_FAULT + end +end + +SensorFaultBitmap.unset_general_fault = function(self) + self.value = self.value & (~self.GENERAL_FAULT & self.BASE_MASK) +end + + +SensorFaultBitmap.mask_methods = { + is_general_fault_set = SensorFaultBitmap.is_general_fault_set, + set_general_fault = SensorFaultBitmap.set_general_fault, + unset_general_fault = SensorFaultBitmap.unset_general_fault, +} + +SensorFaultBitmap.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(SensorFaultBitmap, new_mt) + +return SensorFaultBitmap diff --git a/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua new file mode 100644 index 0000000000..79aae9c6e9 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/BooleanStateConfiguration/types/init.lua @@ -0,0 +1,14 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("BooleanStateConfiguration.types." .. key) + end + return types_mt.__types_cache[key] +end + +local BooleanStateConfigurationTypes = {} + +setmetatable(BooleanStateConfigurationTypes, types_mt) + +return BooleanStateConfigurationTypes diff --git a/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua index 3f6fab859d..366453629e 100644 --- a/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua +++ b/drivers/SmartThings/matter-sensor/src/embedded-cluster-utils.lua @@ -18,6 +18,10 @@ if version.api < 10 then clusters.SmokeCoAlarm = require "SmokeCoAlarm" end +if version.api < 11 then + clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" +end + local embedded_cluster_utils = {} local embedded_clusters = { diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index 67754c409f..bc4325e4db 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -17,6 +17,7 @@ local log = require "log" local clusters = require "st.matter.clusters" local MatterDriver = require "st.matter.driver" local utils = require "st.utils" +local embedded_cluster_utils = require "embedded-cluster-utils" -- This can be removed once LuaLibs supports the PressureMeasurement cluster if not pcall(function(cluster) return clusters[cluster] end, @@ -37,11 +38,15 @@ if version.api < 10 then clusters.Pm10ConcentrationMeasurement = require "Pm10ConcentrationMeasurement" clusters.Pm25ConcentrationMeasurement = require "Pm25ConcentrationMeasurement" clusters.RadonConcentrationMeasurement = require "RadonConcentrationMeasurement" - clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" clusters.SmokeCoAlarm = require "SmokeCoAlarm" + clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "TotalVolatileOrganicCompoundsConcentrationMeasurement" +end + +-- Include driver-side definitions when lua libs api version is < 11 +if version.api < 11 then + clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" end -local BATTERY_CHECKED = "__battery_checked" local TEMP_BOUND_RECEIVED = "__temp_bound_received" local TEMP_MIN = "__temp_min" local TEMP_MAX = "__temp_max" @@ -56,6 +61,33 @@ local function set_field_for_endpoint(device, field, endpoint, value, additional device:set_field(string.format("%s_%d", field, endpoint), value, additional_params) end +local BOOLEAN_DEVICE_TYPE_INFO = { + ["RAIN_SENSOR"] = { id = 0x0044, sensitivity_preference = "rainSensitivity", sensitivity_max = "rainMax" }, + ["WATER_FREEZE_DETECTOR"] = { id = 0x0041, sensitivity_preference = "freezeSensitivity", sensitivity_max = "freezeMax" }, + ["WATER_LEAK_DETECTOR"] = { id = 0x0043, sensitivity_preference = "leakSensitivity", sensitivity_max = "leakMax" }, + ["CONTACT_SENSOR"] = { id = 0x0015, sensitivity_preference = "N/A", sensitivity_max = "N/A" }, +} + +local ORDERED_DEVICE_TYPE_INFO = { + "RAIN_SENSOR", + "WATER_FREEZE_DETECTOR", + "WATER_LEAK_DETECTOR", + "CONTACT_SENSOR" +} + +local function set_boolean_device_type_per_endpoint(driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do + if dt.device_type_id == info.id then + device:set_field(dt_name, ep.endpoint_id) + device:send(clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(device, ep.endpoint_id)) + end + end + end + end +end + local function supports_battery_percentage_remaining(device) local battery_eps = device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) @@ -66,7 +98,27 @@ local function supports_battery_percentage_remaining(device) return false end -local function check_for_battery(device) +local function supports_sensitivity_preferences(device) + local preference_names = "" + local sensitivity_eps = embedded_cluster_utils.get_endpoints(device, clusters.BooleanStateConfiguration.ID, + {feature_bitmap = clusters.BooleanStateConfiguration.types.Feature.SENSITIVITY_LEVEL}) + if sensitivity_eps and #sensitivity_eps > 0 then + for _, dt_name in ipairs(ORDERED_DEVICE_TYPE_INFO) do + for _, sensitivity_ep in pairs(sensitivity_eps) do + if device:get_field(dt_name) == sensitivity_ep and BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference ~= "N/A" then + preference_names = preference_names .. "-" .. BOOLEAN_DEVICE_TYPE_INFO[dt_name].sensitivity_preference + end + end + end + end + return preference_names +end + +local function device_added(driver, device) + set_boolean_device_type_per_endpoint(driver, device) +end + +local function do_configure(driver, device) local profile_name = "" if device:supports_capability(capabilities.motionSensor) then @@ -93,28 +145,64 @@ local function check_for_battery(device) profile_name = profile_name .. "-pressure" end + if device:supports_capability(capabilities.rainSensor) then + profile_name = profile_name .. "-rain" + end + + if device:supports_capability(capabilities.temperatureAlarm) then + profile_name = profile_name .. "-freeze" + end + + if device:supports_capability(capabilities.waterSensor) then + profile_name = profile_name .. "-leak" + end + if supports_battery_percentage_remaining(device) then profile_name = profile_name .. "-battery" end + if device:supports_capability(capabilities.hardwareFault) then + profile_name = profile_name .. "-fault" + end + + local concatenated_preferences = supports_sensitivity_preferences(device) + profile_name = profile_name .. concatenated_preferences + -- remove leading "-" profile_name = string.sub(profile_name, 2) + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) device:try_update_metadata({profile = profile_name}) - device:set_field(BATTERY_CHECKED, 1, {persist = true}) end local function device_init(driver, device) log.info("device init") - if not device:get_field(BATTERY_CHECKED) then - check_for_battery(device) - end device:subscribe() end local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then device:subscribe() + set_boolean_device_type_per_endpoint(driver, device) + end + if not device.preferences then + return + end + for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do + local dt_ep = device:get_field(dt_name) + if dt_ep and info.sensitivity_preference and (device.preferences[info.sensitivity_preference] ~= args.old_st_store.preferences[info.sensitivity_preference]) then + local sensitivity_preference = device.preferences[info.sensitivity_preference] + if sensitivity_preference == "2" then -- high + local max_sensitivity_level = device:get_field(info.sensitivity_max) - 1 + device:send(clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(device, dt_ep, max_sensitivity_level)) + elseif sensitivity_preference == "1" then -- medium + local medium_sensitivity_level = math.floor((device:get_field(info.sensitivity_max) + 1) / 2) + device:send(clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(device, dt_ep, medium_sensitivity_level)) + elseif sensitivity_preference == "0" then -- low + local min_sensitivity_level = 0 + device:send(clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(device, dt_ep, min_sensitivity_level)) + end + end end end @@ -166,11 +254,57 @@ local function humidity_attr_handler(driver, device, ib, response) end end +local BOOLEAN_CAP_EVENT_MAP = { + [true] = { + ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.freeze(), + ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.wet(), + ["RAIN_SENSOR"] = capabilities.rainSensor.rain.detected(), + ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.closed(), + }, + [false] = { + ["WATER_FREEZE_DETECTOR"] = capabilities.temperatureAlarm.temperatureAlarm.cleared(), + ["WATER_LEAK_DETECTOR"] = capabilities.waterSensor.water.dry(), + ["RAIN_SENSOR"] = capabilities.rainSensor.rain.undetected(), + ["CONTACT_SENSOR"] = capabilities.contactSensor.contact.open(), + } +} + local function boolean_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.contactSensor.contact.closed()) + local name + for dt_name, _ in pairs(BOOLEAN_DEVICE_TYPE_INFO) do + local dt_ep_id = device:get_field(dt_name) + if ib.endpoint_id == dt_ep_id then + name = dt_name + break + end + end + if name then + device:emit_event_for_endpoint(ib.endpoint_id, BOOLEAN_CAP_EVENT_MAP[ib.data.value][name]) + elseif device:supports_capability(capabilities.contactSensor) then + -- The generic case where no device type has been specified but the profile uses this capability. + device:emit_event_for_endpoint(ib.endpoint_id, BOOLEAN_CAP_EVENT_MAP[ib.data.value]["CONTACT_SENSOR"]) else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.contactSensor.contact.open()) + log.error("No Boolean device type found on an endpoint, BooleanState handler aborted") + end +end + +local function supported_sensitivities_handler(driver, device, ib, response) + if not ib.data.value then + return + end + + for dt_name, info in pairs(BOOLEAN_DEVICE_TYPE_INFO) do + if device:get_field(dt_name) == ib.endpoint_id then + device:set_field(info.sensitivity_max, ib.data.value) + end + end +end + +local function sensor_fault_handler(driver, device, ib, response) + if ib.data.value > 0 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.detected()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.hardwareFault.hardwareFault.clear()) end end @@ -196,7 +330,9 @@ end local matter_driver_template = { lifecycle_handlers = { init = device_init, - infoChanged = info_changed + infoChanged = info_changed, + doConfigure = do_configure, + added = device_added, }, matter_handlers = { attr = { @@ -223,6 +359,10 @@ local matter_driver_template = { [clusters.PressureMeasurement.ID] = { [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = pressure_attr_handler, }, + [clusters.BooleanStateConfiguration.ID] = { + [clusters.BooleanStateConfiguration.attributes.SensorFault.ID] = sensor_fault_handler, + [clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels.ID] = supported_sensitivities_handler, + }, } }, -- TODO Once capabilities all have default handlers move this info there, and @@ -335,11 +475,21 @@ local matter_driver_template = { clusters.SmokeCoAlarm.attributes.TestInProgress, }, [capabilities.hardwareFault.ID] = { - clusters.SmokeCoAlarm.attributes.HardwareFaultAlert + clusters.SmokeCoAlarm.attributes.HardwareFaultAlert, + clusters.BooleanStateConfiguration.attributes.SensorFault, }, [capabilities.batteryLevel.ID] = { clusters.SmokeCoAlarm.attributes.BatteryAlert, }, + [capabilities.waterSensor.ID] = { + clusters.BooleanState.attributes.StateValue, + }, + [capabilities.temperatureAlarm.ID] = { + clusters.BooleanState.attributes.StateValue, + }, + [capabilities.rainSensor.ID] = { + clusters.BooleanState.attributes.StateValue, + }, }, capability_handlers = { }, @@ -351,6 +501,10 @@ local matter_driver_template = { capabilities.relativeHumidityMeasurement, capabilities.illuminanceMeasurement, capabilities.atmosphericPressureMeasurement, + capabilities.waterSensor, + capabilities.temperatureAlarm, + capabilities.rainSensor, + capabilities.hardwareFault }, sub_drivers = { require("air-quality-sensor"), diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua new file mode 100644 index 0000000000..49c846f4ec --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_freeze_leak_sensor.lua @@ -0,0 +1,252 @@ +-- Copyright 2024 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" +clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" + +local mock_device_freeze_leak = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("freeze-leak-fault-freezeSensitivity-leakSensitivity.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.BooleanState.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.BooleanStateConfiguration.ID, cluster_type = "SERVER", feature_map = 31}, + }, + device_types = { + {device_type_id = 0x0043, device_type_revision = 1} -- Water Leak Detector + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.BooleanState.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.BooleanStateConfiguration.ID, cluster_type = "SERVER", feature_map = 31}, + }, + device_types = { + {device_type_id = 0x0041, device_type_revision = 1} -- Water Freeze Detector + } + } + } +}) + +local subscribed_attributes = { + clusters.BooleanState.attributes.StateValue, + clusters.BooleanStateConfiguration.attributes.SensorFault, +} + +local function test_init_freeze_leak() + local subscribe_request = subscribed_attributes[1]:subscribe(mock_device_freeze_leak) + for i, cluster in ipairs(subscribed_attributes) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_freeze_leak)) + end + end + test.socket.matter:__expect_send({mock_device_freeze_leak.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_freeze_leak) + test.socket.device_lifecycle:__queue_receive({ mock_device_freeze_leak.id, "added" }) + test.socket.matter:__expect_send({mock_device_freeze_leak.id, clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(mock_device_freeze_leak, 1)}) + test.socket.matter:__expect_send({mock_device_freeze_leak.id, clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(mock_device_freeze_leak, 2)}) +end +test.set_test_init_function(test_init_freeze_leak) + +test.register_coroutine_test( + "Test profile change on init for Freeze and Leak combined device type", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_freeze_leak.id, "doConfigure" }) + mock_device_freeze_leak:expect_metadata_update({ profile = "freeze-leak-fault-freezeSensitivity-leakSensitivity" }) + mock_device_freeze_leak:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_freeze_leak } +) + +test.register_message_test( + "Boolean state freeze detection reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_freeze_leak, 2, false) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_freeze_leak, 2, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.freeze()) + } + } +) + + +test.register_message_test( + "Boolean state leak detection reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_freeze_leak, 1, false) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.waterSensor.water.dry()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_freeze_leak, 1, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.waterSensor.water.wet()) + } + } +) + +test.register_message_test( + "Test hardware fault alert handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.SensorFault:build_test_report_data(mock_device_freeze_leak, 1, 0x1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.SensorFault:build_test_report_data(mock_device_freeze_leak, 1, 0x0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_freeze_leak:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + } + } +) + +test.register_coroutine_test( + "Check that preference updates to low as expected", function() + test.socket.matter:__queue_receive({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:build_test_report_data( + mock_device_freeze_leak, 2, 4 + ) + }) + test.wait_for_events() + + test.socket.device_lifecycle():__queue_receive(mock_device_freeze_leak:generate_info_changed({ preferences = { freezeSensitivity = "0" } })) + test.socket.matter:__expect_send({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, 0) + }) + end +) + +test.register_coroutine_test( + "Check that preference updates to high as expected", function() + test.socket.matter:__queue_receive({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:build_test_report_data( + mock_device_freeze_leak, 2, 4 + ) + }) + test.wait_for_events() + test.socket.device_lifecycle():__queue_receive(mock_device_freeze_leak:generate_info_changed({ preferences = { freezeSensitivity = "2" } })) + test.socket.matter:__expect_send({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, mock_device_freeze_leak:get_field("freezeMax") - 1) + }) + end +) + +test.register_coroutine_test( + "Check that preference updates to high after being set on-device as expected", function() + test.socket.matter:__queue_receive({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:build_test_report_data( + mock_device_freeze_leak, 2, 4 + ) + }) + test.wait_for_events() + test.socket.device_lifecycle():__queue_receive(mock_device_freeze_leak:generate_info_changed({ preferences = { freezeSensitivity = "2" } })) + test.socket.matter:__expect_send({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, mock_device_freeze_leak:get_field("freezeMax") - 1) + }) + test.socket["matter"]:__queue_receive( + { + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:build_test_report_data( + mock_device_freeze_leak, 2, 2 -- put on level two + ) + } + ) + test.socket.device_lifecycle():__queue_receive(mock_device_freeze_leak:generate_info_changed({ preferences = { freezeSensitivity = "0" } })) + test.socket.matter:__expect_send({ + mock_device_freeze_leak.id, + clusters.BooleanStateConfiguration.attributes.CurrentSensitivityLevel:write(mock_device_freeze_leak, 2, 0) + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua index 9c439301b0..27d3b90842 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua @@ -14,7 +14,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" - local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" local PressureMeasurementCluster = require "PressureMeasurement" @@ -38,6 +37,9 @@ local matter_endpoints = { clusters = { {cluster_id = PressureMeasurementCluster.ID, cluster_type = "SERVER"}, {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0305, device_type_revision = 1} -- Pressure Sensor } } } diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua new file mode 100644 index 0000000000..3225a53e06 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_rain_sensor.lua @@ -0,0 +1,145 @@ +-- Copyright 2024 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" + +clusters.BooleanStateConfiguration = require "BooleanStateConfiguration" + +local mock_device_rain = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("rain-fault.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.BooleanState.ID, cluster_type = "SERVER", feature_map = 0}, + {cluster_id = clusters.BooleanStateConfiguration.ID, cluster_type = "SERVER", feature_map = 31}, + }, + device_types = { + {device_type_id = 0x0044, device_type_revision = 1} -- Rain Sensor + } + } + } +}) + +local subscribed_attributes = { + clusters.BooleanState.attributes.StateValue, + clusters.BooleanStateConfiguration.attributes.SensorFault, +} + +local function test_init_rain() + local subscribe_request = subscribed_attributes[1]:subscribe(mock_device_rain) + for i, cluster in ipairs(subscribed_attributes) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_rain)) + end + end + test.socket.matter:__expect_send({mock_device_rain.id, subscribe_request}) + test.mock_device.add_test_device(mock_device_rain) + test.socket.device_lifecycle:__queue_receive({ mock_device_rain.id, "added" }) + test.socket.matter:__expect_send({mock_device_rain.id, clusters.BooleanStateConfiguration.attributes.SupportedSensitivityLevels:read(mock_device_rain, 1)}) +end +test.set_test_init_function(test_init_rain) + +test.register_coroutine_test( + "Test profile change on init for Freeze and Leak combined device type", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_rain.id, "doConfigure" }) + mock_device_rain:expect_metadata_update({ profile = "rain-fault-rainSensitivity" }) + mock_device_rain:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_rain } +) + +test.register_message_test( + "Boolean state rain detection reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_rain.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_rain, 1, false) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rain:generate_test_message("main", capabilities.rainSensor.rain.undetected()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_rain.id, + clusters.BooleanState.server.attributes.StateValue:build_test_report_data(mock_device_rain, 1, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rain:generate_test_message("main", capabilities.rainSensor.rain.detected()) + } + } +) + +test.register_message_test( + "Test hardware fault alert handler", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_rain.id, + clusters.BooleanStateConfiguration.attributes.SensorFault:build_test_report_data(mock_device_rain, 1, 0x1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rain:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_rain.id, + clusters.BooleanStateConfiguration.attributes.SensorFault:build_test_report_data(mock_device_rain, 1, 0x0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_rain:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + } + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua index 18fa4573e6..ec294d70fb 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_featuremap.lua @@ -134,7 +134,11 @@ local function test_init_humidity_battery() test.socket.matter:__expect_send({mock_device_humidity_battery.id, subscribe_request_humidity_battery}) test.mock_device.add_test_device(mock_device_humidity_battery) + + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_battery.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_battery.id, "doConfigure" }) mock_device_humidity_battery:expect_metadata_update({ profile = "humidity-battery" }) + mock_device_humidity_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end local function test_init_humidity_no_battery() @@ -147,7 +151,11 @@ local function test_init_humidity_no_battery() test.socket.matter:__expect_send({mock_device_humidity_no_battery.id, subscribe_request_humidity_no_battery}) test.mock_device.add_test_device(mock_device_humidity_no_battery) + + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_no_battery.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_humidity_no_battery.id, "doConfigure" }) mock_device_humidity_no_battery:expect_metadata_update({ profile = "humidity" }) + mock_device_humidity_no_battery:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end local function test_init_temp_humidity() @@ -160,7 +168,11 @@ local function test_init_temp_humidity() test.socket.matter:__expect_send({mock_device_temp_humidity.id, subscribe_request_temp_humidity}) test.mock_device.add_test_device(mock_device_temp_humidity) + + test.socket.device_lifecycle:__queue_receive({ mock_device_temp_humidity.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_temp_humidity.id, "doConfigure" }) mock_device_temp_humidity:expect_metadata_update({ profile = "temperature-humidity" }) + mock_device_temp_humidity:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end test.register_coroutine_test( diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua index 9f7568fb2a..d8b38e392a 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_sensor_rpc.lua @@ -35,6 +35,9 @@ local matter_endpoints = { endpoint_id = 1, clusters = { {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "BOTH"}, + }, + device_types = { + device_type_id = 0x0301, device_type_revision = 1, -- Thermostat } } } diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 72760a5be9..e61e164a05 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -2374,6 +2374,17 @@ matterGeneric: deviceTypes: - id: 0x010B # Dimmable Plug-in Unit deviceProfileName: plug-level + - id: "matter/on-off/plug/electrical-sensor" + deviceLabel: Matter OnOff Plug + deviceTypes: + - id: 0x010A # On Off Plug-in Unit + - id: 0x0510 # Electrical Sensor + deviceProfileName: plug-power-energy-powerConsumption + - id: "matter/water-valve" + deviceLabel: Matter Water Valve + deviceTypes: + - id: 0x0042 # Water Valve + deviceProfileName: water-valve - id: "button" deviceLabel: Matter Button deviceTypes: diff --git a/drivers/SmartThings/matter-switch/profiles/plug-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/plug-energy-powerConsumption.yml new file mode 100644 index 0000000000..9c44a4d29f --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-energy-powerConsumption.yml @@ -0,0 +1,16 @@ +name: plug-energy-powerConsumption +components: + - id: main + capabilities: + - id: switch + 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/profiles/plug-power-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/plug-power-energy-powerConsumption.yml new file mode 100644 index 0000000000..5d09912dd3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-power-energy-powerConsumption.yml @@ -0,0 +1,18 @@ +name: plug-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: SmartPlug diff --git a/drivers/SmartThings/matter-switch/profiles/plug-power.yml b/drivers/SmartThings/matter-switch/profiles/plug-power.yml new file mode 100644 index 0000000000..018fba88ee --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/plug-power.yml @@ -0,0 +1,14 @@ +name: plug-power +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: powerMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug diff --git a/drivers/SmartThings/matter-switch/profiles/water-valve-level.yml b/drivers/SmartThings/matter-switch/profiles/water-valve-level.yml new file mode 100644 index 0000000000..48bfb78eef --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/water-valve-level.yml @@ -0,0 +1,18 @@ +name: water-valve-level +components: +- id: main + capabilities: + - id: valve + version: 1 + - id: level + version: 1 + config: + values: + - key: "level.value" + range: [0, 100] + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterValve diff --git a/drivers/SmartThings/matter-switch/profiles/water-valve.yml b/drivers/SmartThings/matter-switch/profiles/water-valve.yml new file mode 100644 index 0000000000..7f2827b648 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/water-valve.yml @@ -0,0 +1,12 @@ +name: water-valve +components: +- id: main + capabilities: + - id: valve + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: WaterValve diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/init.lua new file mode 100644 index 0000000000..83bc66aa1b --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/init.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalEnergyMeasurementServerAttributes = require "ElectricalEnergyMeasurement.server.attributes" +local ElectricalEnergyMeasurementTypes = require "ElectricalEnergyMeasurement.types" +local ElectricalEnergyMeasurement = {} + +ElectricalEnergyMeasurement.ID = 0x0091 +ElectricalEnergyMeasurement.NAME = "ElectricalEnergyMeasurement" +ElectricalEnergyMeasurement.server = {} +ElectricalEnergyMeasurement.client = {} +ElectricalEnergyMeasurement.server.attributes = ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(ElectricalEnergyMeasurement) +ElectricalEnergyMeasurement.types = ElectricalEnergyMeasurementTypes + +function ElectricalEnergyMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "Accuracy", + [0x0001] = "CumulativeEnergyImported", + [0x0002] = "CumulativeEnergyExported", + [0x0003] = "PeriodicEnergyImported", + [0x0004] = "PeriodicEnergyExported", + [0x0005] = "CumulativeEnergyReset", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +ElectricalEnergyMeasurement.attribute_direction_map = { + ["Accuracy"] = "server", + ["CumulativeEnergyImported"] = "server", + ["CumulativeEnergyExported"] = "server", + ["PeriodicEnergyImported"] = "server", + ["PeriodicEnergyExported"] = "server", + ["CumulativeEnergyReset"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +ElectricalEnergyMeasurement.FeatureMap = ElectricalEnergyMeasurement.types.Feature + +function ElectricalEnergyMeasurement.are_features_supported(feature, feature_map) + if (ElectricalEnergyMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalEnergyMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalEnergyMeasurement.NAME)) + end + return ElectricalEnergyMeasurement[direction].attributes[key] +end +ElectricalEnergyMeasurement.attributes = {} +setmetatable(ElectricalEnergyMeasurement.attributes, attribute_helper_mt) + +setmetatable(ElectricalEnergyMeasurement, {__index = cluster_base}) + +return ElectricalEnergyMeasurement + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua new file mode 100644 index 0000000000..22befec642 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/CumulativeEnergyExported.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CumulativeEnergyExported = { + ID = 0x0002, + NAME = "CumulativeEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function CumulativeEnergyExported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CumulativeEnergyExported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyExported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CumulativeEnergyExported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CumulativeEnergyExported:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CumulativeEnergyExported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CumulativeEnergyExported, {__call = CumulativeEnergyExported.new_value, __index = CumulativeEnergyExported.base_type}) +return CumulativeEnergyExported + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua new file mode 100644 index 0000000000..4c1ee29274 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/PeriodicEnergyExported.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local PeriodicEnergyExported = { + ID = 0x0004, + NAME = "PeriodicEnergyExported", + base_type = require "ElectricalEnergyMeasurement.types.EnergyMeasurementStruct", +} + +function PeriodicEnergyExported:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function PeriodicEnergyExported:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function PeriodicEnergyExported:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function PeriodicEnergyExported:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function PeriodicEnergyExported:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(PeriodicEnergyExported, {__call = PeriodicEnergyExported.new_value, __index = PeriodicEnergyExported.base_type}) +return PeriodicEnergyExported + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..adfdf42bbf --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ElectricalEnergyMeasurement.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ElectricalEnergyMeasurementServerAttributes = {} + +function ElectricalEnergyMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalEnergyMeasurementServerAttributes, attr_mt) + +return ElectricalEnergyMeasurementServerAttributes + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua new file mode 100644 index 0000000000..950b260227 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/EnergyMeasurementStruct.lua @@ -0,0 +1,98 @@ +local data_types = require "st.matter.data_types" +local StructureABC = require "st.matter.data_types.base_defs.StructureABC" +local EnergyMeasurementStruct = {} +local new_mt = StructureABC.new_mt({NAME = "EnergyMeasurementStruct", ID = data_types.name_to_id_map["Structure"]}) + +EnergyMeasurementStruct.field_defs = { + { + name = "energy", + field_id = 0, + is_nullable = false, + is_optional = false, + data_type = require "st.matter.data_types.Int64", + }, + { + name = "start_timestamp", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", + }, + { + name = "end_timestamp", + field_id = 2, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", + }, + { + name = "start_systime", + field_id = 3, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", + }, + { + name = "end_systime", + field_id = 4, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint64", + }, +} + +EnergyMeasurementStruct.init = function(cls, tbl) + local o = {} + o.elements = {} + o.num_elements = 0 + setmetatable(o, new_mt) + for idx, field_def in ipairs(cls.field_defs) do + if (not field_def.is_optional and not field_def.is_nullable) and not tbl[field_def.name] then + error("Missing non optional or non_nullable field: " .. field_def.name) + else + o.elements[field_def.name] = data_types.validate_or_build_type(tbl[field_def.name], field_def.data_type, field_def.name) + o.elements[field_def.name].field_id = field_def.field_id + o.num_elements = o.num_elements + 1 + end + end + return o +end + +EnergyMeasurementStruct.serialize = function(self, buf, include_control, tag) + return data_types['Structure'].serialize(self.elements, buf, include_control, tag) +end + +new_mt.__call = EnergyMeasurementStruct.init +new_mt.__index.serialize = EnergyMeasurementStruct.serialize + +EnergyMeasurementStruct.augment_type = function(self, val) + local elems = {} + local num_elements = 0 + for _, v in pairs(val.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + num_elements = num_elements + 1 + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + num_elements = num_elements + 1 + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + val.elements = elems + val.num_elements = num_elements + setmetatable(val, new_mt) +end + +setmetatable(EnergyMeasurementStruct, new_mt) + +return EnergyMeasurementStruct + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/Feature.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/Feature.lua new file mode 100644 index 0000000000..717ba6a2f3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/Feature.lua @@ -0,0 +1,116 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.IMPORTED_ENERGY = 0x0001 +Feature.EXPORTED_ENERGY = 0x0002 +Feature.CUMULATIVE_ENERGY = 0x0004 +Feature.PERIODIC_ENERGY = 0x0008 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + IMPORTED_ENERGY = 0x0001, + EXPORTED_ENERGY = 0x0002, + CUMULATIVE_ENERGY = 0x0004, + PERIODIC_ENERGY = 0x0008, +} + +Feature.is_imported_energy_set = function(self) + return (self.value & self.IMPORTED_ENERGY) ~= 0 +end + +Feature.set_imported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.IMPORTED_ENERGY + else + self.value = self.IMPORTED_ENERGY + end +end + +Feature.unset_imported_energy = function(self) + self.value = self.value & (~self.IMPORTED_ENERGY & self.BASE_MASK) +end +Feature.is_exported_energy_set = function(self) + return (self.value & self.EXPORTED_ENERGY) ~= 0 +end + +Feature.set_exported_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.EXPORTED_ENERGY + else + self.value = self.EXPORTED_ENERGY + end +end + +Feature.unset_exported_energy = function(self) + self.value = self.value & (~self.EXPORTED_ENERGY & self.BASE_MASK) +end +Feature.is_cumulative_energy_set = function(self) + return (self.value & self.CUMULATIVE_ENERGY) ~= 0 +end + +Feature.set_cumulative_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.CUMULATIVE_ENERGY + else + self.value = self.CUMULATIVE_ENERGY + end +end + +Feature.unset_cumulative_energy = function(self) + self.value = self.value & (~self.CUMULATIVE_ENERGY & self.BASE_MASK) +end +Feature.is_periodic_energy_set = function(self) + return (self.value & self.PERIODIC_ENERGY) ~= 0 +end + +Feature.set_periodic_energy = function(self) + if self.value ~= nil then + self.value = self.value | self.PERIODIC_ENERGY + else + self.value = self.PERIODIC_ENERGY + end +end + +Feature.unset_periodic_energy = function(self) + self.value = self.value & (~self.PERIODIC_ENERGY & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.IMPORTED_ENERGY | + Feature.EXPORTED_ENERGY | + Feature.CUMULATIVE_ENERGY | + Feature.PERIODIC_ENERGY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_imported_energy_set = Feature.is_imported_energy_set, + set_imported_energy = Feature.set_imported_energy, + unset_imported_energy = Feature.unset_imported_energy, + is_exported_energy_set = Feature.is_exported_energy_set, + set_exported_energy = Feature.set_exported_energy, + unset_exported_energy = Feature.unset_exported_energy, + is_cumulative_energy_set = Feature.is_cumulative_energy_set, + set_cumulative_energy = Feature.set_cumulative_energy, + unset_cumulative_energy = Feature.unset_cumulative_energy, + is_periodic_energy_set = Feature.is_periodic_energy_set, + set_periodic_energy = Feature.set_periodic_energy, + unset_periodic_energy = Feature.unset_periodic_energy, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/init.lua new file mode 100644 index 0000000000..bb0c39fe0e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalEnergyMeasurement/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("ElectricalEnergyMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ElectricalEnergyMeasurementTypes = {} + +setmetatable(ElectricalEnergyMeasurementTypes, types_mt) + +return ElectricalEnergyMeasurementTypes + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/init.lua new file mode 100644 index 0000000000..54785d16c6 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/init.lua @@ -0,0 +1,94 @@ +local cluster_base = require "st.matter.cluster_base" +local ElectricalPowerMeasurementServerAttributes = require "ElectricalPowerMeasurement.server.attributes" +local ElectricalPowerMeasurementTypes = require "ElectricalPowerMeasurement.types" + +local ElectricalPowerMeasurement = {} + +ElectricalPowerMeasurement.ID = 0x0090 +ElectricalPowerMeasurement.NAME = "ElectricalPowerMeasurement" +ElectricalPowerMeasurement.server = {} +ElectricalPowerMeasurement.client = {} +ElectricalPowerMeasurement.server.attributes = ElectricalPowerMeasurementServerAttributes:set_parent_cluster(ElectricalPowerMeasurement) +ElectricalPowerMeasurement.types = ElectricalPowerMeasurementTypes + +function ElectricalPowerMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "PowerMode", + [0x0001] = "NumberOfMeasurementTypes", + [0x0002] = "Accuracy", + [0x0003] = "Ranges", + [0x0004] = "Voltage", + [0x0005] = "ActiveCurrent", + [0x0006] = "ReactiveCurrent", + [0x0007] = "ApparentCurrent", + [0x0008] = "ActivePower", + [0x0009] = "ReactivePower", + [0x000A] = "ApparentPower", + [0x000B] = "RMSVoltage", + [0x000C] = "RMSCurrent", + [0x000D] = "RMSPower", + [0x000E] = "Frequency", + [0x000F] = "HarmonicCurrents", + [0x0010] = "HarmonicPhases", + [0x0011] = "PowerFactor", + [0x0012] = "NeutralCurrent", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +ElectricalPowerMeasurement.attribute_direction_map = { + ["PowerMode"] = "server", + ["NumberOfMeasurementTypes"] = "server", + ["Accuracy"] = "server", + ["Ranges"] = "server", + ["Voltage"] = "server", + ["ActiveCurrent"] = "server", + ["ReactiveCurrent"] = "server", + ["ApparentCurrent"] = "server", + ["ActivePower"] = "server", + ["ReactivePower"] = "server", + ["ApparentPower"] = "server", + ["RMSVoltage"] = "server", + ["RMSCurrent"] = "server", + ["RMSPower"] = "server", + ["Frequency"] = "server", + ["HarmonicCurrents"] = "server", + ["HarmonicPhases"] = "server", + ["PowerFactor"] = "server", + ["NeutralCurrent"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +ElectricalPowerMeasurement.FeatureMap = ElectricalPowerMeasurement.types.Feature + +function ElectricalPowerMeasurement.are_features_supported(feature, feature_map) + if (ElectricalPowerMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ElectricalPowerMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ElectricalPowerMeasurement.NAME)) + end + return ElectricalPowerMeasurement[direction].attributes[key] +end +ElectricalPowerMeasurement.attributes = {} +setmetatable(ElectricalPowerMeasurement.attributes, attribute_helper_mt) + +setmetatable(ElectricalPowerMeasurement, {__index = cluster_base}) + +return ElectricalPowerMeasurement + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua new file mode 100644 index 0000000000..6c34abd2f4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/ActivePower.lua @@ -0,0 +1,68 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local ActivePower = { + ID = 0x0008, + NAME = "ActivePower", + base_type = require "st.matter.data_types.Int64", +} + +function ActivePower:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function ActivePower:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function ActivePower:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function ActivePower:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function ActivePower:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ActivePower:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(ActivePower, {__call = ActivePower.new_value, __index = ActivePower.base_type}) +return ActivePower + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..0c30fa8dd4 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/server/attributes/init.lua @@ -0,0 +1,24 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ElectricalPowerMeasurement.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ElectricalPowerMeasurementServerAttributes = {} + +function ElectricalPowerMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ElectricalPowerMeasurementServerAttributes, attr_mt) + +return ElectricalPowerMeasurementServerAttributes + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/Feature.lua b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/Feature.lua new file mode 100644 index 0000000000..cbda4f3478 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/Feature.lua @@ -0,0 +1,138 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.DIRECT_CURRENT = 0x0001 +Feature.ALTERNATING_CURRENT = 0x0002 +Feature.POLYPHASE_POWER = 0x0004 +Feature.HARMONICS = 0x0008 +Feature.POWER_QUALITY = 0x0010 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + DIRECT_CURRENT = 0x0001, + ALTERNATING_CURRENT = 0x0002, + POLYPHASE_POWER = 0x0004, + HARMONICS = 0x0008, + POWER_QUALITY = 0x0010, +} + +Feature.is_direct_current_set = function(self) + return (self.value & self.DIRECT_CURRENT) ~= 0 +end + +Feature.set_direct_current = function(self) + if self.value ~= nil then + self.value = self.value | self.DIRECT_CURRENT + else + self.value = self.DIRECT_CURRENT + end +end + +Feature.unset_direct_current = function(self) + self.value = self.value & (~self.DIRECT_CURRENT & self.BASE_MASK) +end +Feature.is_alternating_current_set = function(self) + return (self.value & self.ALTERNATING_CURRENT) ~= 0 +end + +Feature.set_alternating_current = function(self) + if self.value ~= nil then + self.value = self.value | self.ALTERNATING_CURRENT + else + self.value = self.ALTERNATING_CURRENT + end +end + +Feature.unset_alternating_current = function(self) + self.value = self.value & (~self.ALTERNATING_CURRENT & self.BASE_MASK) +end +Feature.is_polyphase_power_set = function(self) + return (self.value & self.POLYPHASE_POWER) ~= 0 +end + +Feature.set_polyphase_power = function(self) + if self.value ~= nil then + self.value = self.value | self.POLYPHASE_POWER + else + self.value = self.POLYPHASE_POWER + end +end + +Feature.unset_polyphase_power = function(self) + self.value = self.value & (~self.POLYPHASE_POWER & self.BASE_MASK) +end +Feature.is_harmonics_set = function(self) + return (self.value & self.HARMONICS) ~= 0 +end + +Feature.set_harmonics = function(self) + if self.value ~= nil then + self.value = self.value | self.HARMONICS + else + self.value = self.HARMONICS + end +end + +Feature.unset_harmonics = function(self) + self.value = self.value & (~self.HARMONICS & self.BASE_MASK) +end +Feature.is_power_quality_set = function(self) + return (self.value & self.POWER_QUALITY) ~= 0 +end + +Feature.set_power_quality = function(self) + if self.value ~= nil then + self.value = self.value | self.POWER_QUALITY + else + self.value = self.POWER_QUALITY + end +end + +Feature.unset_power_quality = function(self) + self.value = self.value & (~self.POWER_QUALITY & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.DIRECT_CURRENT | + Feature.ALTERNATING_CURRENT | + Feature.POLYPHASE_POWER | + Feature.HARMONICS | + Feature.POWER_QUALITY + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_direct_current_set = Feature.is_direct_current_set, + set_direct_current = Feature.set_direct_current, + unset_direct_current = Feature.unset_direct_current, + is_alternating_current_set = Feature.is_alternating_current_set, + set_alternating_current = Feature.set_alternating_current, + unset_alternating_current = Feature.unset_alternating_current, + is_polyphase_power_set = Feature.is_polyphase_power_set, + set_polyphase_power = Feature.set_polyphase_power, + unset_polyphase_power = Feature.unset_polyphase_power, + is_harmonics_set = Feature.is_harmonics_set, + set_harmonics = Feature.set_harmonics, + unset_harmonics = Feature.unset_harmonics, + is_power_quality_set = Feature.is_power_quality_set, + set_power_quality = Feature.set_power_quality, + unset_power_quality = Feature.unset_power_quality, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/init.lua b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/init.lua new file mode 100644 index 0000000000..16d13a0688 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ElectricalPowerMeasurement/types/init.lua @@ -0,0 +1,15 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("ElectricalPowerMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ElectricalPowerMeasurementTypes = {} + +setmetatable(ElectricalPowerMeasurementTypes, types_mt) + +return ElectricalPowerMeasurementTypes + diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/init.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/init.lua new file mode 100644 index 0000000000..d8ab93412f --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/init.lua @@ -0,0 +1,123 @@ +local cluster_base = require "st.matter.cluster_base" +local ValveConfigurationAndControlServerAttributes = require "ValveConfigurationAndControl.server.attributes" +local ValveConfigurationAndControlServerCommands = require "ValveConfigurationAndControl.server.commands" +local ValveConfigurationAndControlTypes = require "ValveConfigurationAndControl.types" +local ValveConfigurationAndControl = {} + +ValveConfigurationAndControl.ID = 0x0081 +ValveConfigurationAndControl.NAME = "ValveConfigurationAndControl" +ValveConfigurationAndControl.server = {} +ValveConfigurationAndControl.client = {} +ValveConfigurationAndControl.server.attributes = ValveConfigurationAndControlServerAttributes:set_parent_cluster(ValveConfigurationAndControl) +ValveConfigurationAndControl.server.commands = ValveConfigurationAndControlServerCommands:set_parent_cluster(ValveConfigurationAndControl) +ValveConfigurationAndControl.types = ValveConfigurationAndControlTypes + +function ValveConfigurationAndControl:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "OpenDuration", + [0x0001] = "DefaultOpenDuration", + [0x0002] = "AutoCloseTime", + [0x0003] = "RemainingDuration", + [0x0004] = "CurrentState", + [0x0005] = "TargetState", + [0x0006] = "CurrentLevel", + [0x0007] = "TargetLevel", + [0x0008] = "DefaultOpenLevel", + [0x0009] = "ValveFault", + [0x000A] = "LevelStep", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +function ValveConfigurationAndControl:get_server_command_by_id(command_id) + local server_id_map = { + [0x0000] = "Open", + [0x0001] = "Close", + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + +function ValveConfigurationAndControl:get_event_by_id(event_id) + local event_id_map = { + [0x0000] = "ValveStateChanged", + [0x0001] = "ValveFault", + } + if event_id_map[event_id] ~= nil then + return self.server.events[event_id_map[event_id]] + end + return nil +end + +ValveConfigurationAndControl.attribute_direction_map = { + ["OpenDuration"] = "server", + ["DefaultOpenDuration"] = "server", + ["AutoCloseTime"] = "server", + ["RemainingDuration"] = "server", + ["CurrentState"] = "server", + ["TargetState"] = "server", + ["CurrentLevel"] = "server", + ["TargetLevel"] = "server", + ["DefaultOpenLevel"] = "server", + ["ValveFault"] = "server", + ["LevelStep"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + +ValveConfigurationAndControl.command_direction_map = { + ["Open"] = "server", + ["Close"] = "server", +} + +ValveConfigurationAndControl.FeatureMap = ValveConfigurationAndControl.types.Feature + +function ValveConfigurationAndControl.are_features_supported(feature, feature_map) + if (ValveConfigurationAndControl.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = ValveConfigurationAndControl.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, ValveConfigurationAndControl.NAME)) + end + return ValveConfigurationAndControl[direction].attributes[key] +end +ValveConfigurationAndControl.attributes = {} +setmetatable(ValveConfigurationAndControl.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = ValveConfigurationAndControl.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, ValveConfigurationAndControl.NAME)) + end + return ValveConfigurationAndControl[direction].commands[key] +end +ValveConfigurationAndControl.commands = {} +setmetatable(ValveConfigurationAndControl.commands, command_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return ValveConfigurationAndControl.server.events[key] +end +ValveConfigurationAndControl.events = {} +setmetatable(ValveConfigurationAndControl.events, event_helper_mt) + +setmetatable(ValveConfigurationAndControl, {__index = cluster_base}) + +return ValveConfigurationAndControl diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua new file mode 100644 index 0000000000..807beba485 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentLevel.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentLevel = { + ID = 0x0006, + NAME = "CurrentLevel", + base_type = require "st.matter.data_types.Uint8", +} + +function CurrentLevel:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +function CurrentLevel:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentLevel:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentLevel:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentLevel:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentLevel:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(CurrentLevel, {__call = CurrentLevel.new_value, __index = CurrentLevel.base_type}) +return CurrentLevel diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentState.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentState.lua new file mode 100644 index 0000000000..76f156d2a7 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/CurrentState.lua @@ -0,0 +1,67 @@ +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +local CurrentState = { + ID = 0x0004, + NAME = "CurrentState", + base_type = require "ValveConfigurationAndControl.types.ValveStateEnum", +} + +function CurrentState:new_value(...) + local o = self.base_type(table.unpack({...})) + self:augment_type(o) + return o +end + +function CurrentState:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentState:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil + ) +end + +function CurrentState:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function CurrentState:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + self:augment_type(data) + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function CurrentState:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(CurrentState, {__call = CurrentState.new_value, __index = CurrentState.base_type}) +return CurrentState diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/init.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/init.lua new file mode 100644 index 0000000000..237818d98f --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/attributes/init.lua @@ -0,0 +1,23 @@ +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("ValveConfigurationAndControl.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +local ValveConfigurationAndControlServerAttributes = {} + +function ValveConfigurationAndControlServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ValveConfigurationAndControlServerAttributes, attr_mt) + +return ValveConfigurationAndControlServerAttributes diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Close.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Close.lua new file mode 100644 index 0000000000..baee7688ec --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Close.lua @@ -0,0 +1,89 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local Close = {} + +Close.NAME = "Close" +Close.ID = 0x0001 +Close.field_defs = { +} + +function Close:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function Close:init(device, endpoint_id) + local out = {} + local args = {} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Close, + __tostring = Close.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function Close:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Close:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function Close:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(Close, {__call = Close.init}) + +return Close diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Open.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Open.lua new file mode 100644 index 0000000000..3f84040fc0 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/Open.lua @@ -0,0 +1,103 @@ +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" +local Open = {} + +Open.NAME = "Open" +Open.ID = 0x0000 +Open.field_defs = { + { + name = "open_duration", + field_id = 0, + is_nullable = true, + is_optional = true, + data_type = require "st.matter.data_types.Uint32", + }, + { + name = "target_level", + field_id = 1, + is_nullable = false, + is_optional = true, + data_type = require "st.matter.data_types.Uint8", + }, +} + +function Open:build_test_command_response(device, endpoint_id, status) + return self._cluster:build_test_command_response( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil, + status + ) +end + +function Open:init(device, endpoint_id, open_duration, target_level) + local out = {} + local args = {open_duration, target_level} + if #args > #self.field_defs then + error(self.NAME .. " received too many arguments") + end + for i,v in ipairs(self.field_defs) do + if v.is_optional and args[i] == nil then + out[v.name] = nil + elseif v.is_nullable and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name) + out[v.name].field_id = v.field_id + elseif not v.is_optional and args[i] == nil then + out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name) + out[v.name].field_id = v.field_id + else + out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name) + out[v.name].field_id = v.field_id + end + end + setmetatable(out, { + __index = Open, + __tostring = Open.pretty_print + }) + return self._cluster:build_cluster_command( + device, + out, + endpoint_id, + self._cluster.ID, + self.ID + ) +end + +function Open:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +function Open:augment_type(base_type_obj) + local elems = {} + for _, v in ipairs(base_type_obj.elements) do + for _, field_def in ipairs(self.field_defs) do + if field_def.field_id == v.field_id and + field_def.is_nullable and + (v.value == nil and v.elements == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name) + elseif field_def.field_id == v.field_id and not + (field_def.is_optional and v.value == nil) then + elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name) + if field_def.element_type ~= nil then + for i, e in ipairs(elems[field_def.name].elements) do + elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type) + end + end + end + end + end + base_type_obj.elements = elems +end + +function Open:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + self:augment_type(data) + return data +end + +setmetatable(Open, {__call = Open.init}) + +return Open diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/init.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/init.lua new file mode 100644 index 0000000000..330e35bba3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/server/commands/init.lua @@ -0,0 +1,22 @@ +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("ValveConfigurationAndControl.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +local ValveConfigurationAndControlServerCommands = {} + +function ValveConfigurationAndControlServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(ValveConfigurationAndControlServerCommands, command_mt) + +return ValveConfigurationAndControlServerCommands diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/Feature.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/Feature.lua new file mode 100644 index 0000000000..7433a99c9e --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/Feature.lua @@ -0,0 +1,74 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.TIME_SYNC = 0x0001 +Feature.LEVEL = 0x0002 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + TIME_SYNC = 0x0001, + LEVEL = 0x0002, +} + +Feature.is_time_sync_set = function(self) + return (self.value & self.TIME_SYNC) ~= 0 +end + +Feature.set_time_sync = function(self) + if self.value ~= nil then + self.value = self.value | self.TIME_SYNC + else + self.value = self.TIME_SYNC + end +end + +Feature.unset_time_sync = function(self) + self.value = self.value & (~self.TIME_SYNC & self.BASE_MASK) +end + +Feature.is_level_set = function(self) + return (self.value & self.LEVEL) ~= 0 +end + +Feature.set_level = function(self) + if self.value ~= nil then + self.value = self.value | self.LEVEL + else + self.value = self.LEVEL + end +end + +Feature.unset_level = function(self) + self.value = self.value & (~self.LEVEL & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.TIME_SYNC | + Feature.LEVEL + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_time_sync_set = Feature.is_time_sync_set, + set_time_sync = Feature.set_time_sync, + unset_time_sync = Feature.unset_time_sync, + is_level_set = Feature.is_level_set, + set_level = Feature.set_level, + unset_level = Feature.unset_level, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/ValveStateEnum.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/ValveStateEnum.lua new file mode 100644 index 0000000000..8b6da46ad8 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/ValveStateEnum.lua @@ -0,0 +1,29 @@ +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" +local ValveStateEnum = {} +local new_mt = UintABC.new_mt({NAME = "ValveStateEnum", ID = data_types.name_to_id_map["Uint8"]}, 1) +new_mt.__index.pretty_print = function(self) + local name_lookup = { + [self.CLOSED] = "CLOSED", + [self.OPEN] = "OPEN", + [self.TRANSITIONING] = "TRANSITIONING", + } + return string.format("%s: %s", self.field_name or self.NAME, name_lookup[self.value] or string.format("%d", self.value)) +end +new_mt.__tostring = new_mt.__index.pretty_print + +new_mt.__index.CLOSED = 0x00 +new_mt.__index.OPEN = 0x01 +new_mt.__index.TRANSITIONING = 0x02 + +ValveStateEnum.CLOSED = 0x00 +ValveStateEnum.OPEN = 0x01 +ValveStateEnum.TRANSITIONING = 0x02 + +ValveStateEnum.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(ValveStateEnum, new_mt) + +return ValveStateEnum diff --git a/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/init.lua b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/init.lua new file mode 100644 index 0000000000..835167f485 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/ValveConfigurationAndControl/types/init.lua @@ -0,0 +1,14 @@ +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("ValveConfigurationAndControl.types." .. key) + end + return types_mt.__types_cache[key] +end + +local ValveConfigurationAndControlTypes = {} + +setmetatable(ValveConfigurationAndControlTypes, types_mt) + +return ValveConfigurationAndControlTypes diff --git a/drivers/SmartThings/matter-switch/src/embedded-cluster-utils.lua b/drivers/SmartThings/matter-switch/src/embedded-cluster-utils.lua new file mode 100644 index 0000000000..66db6097c7 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/embedded-cluster-utils.lua @@ -0,0 +1,53 @@ +local clusters = require "st.matter.clusters" +local utils = require "st.utils" + +-- Include driver-side definitions when lua libs api version is < 11 +local version = require "version" +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ValveConfigurationAndControl = require "ValveConfigurationAndControl" +end + +local embedded_cluster_utils = {} + +local embedded_clusters = { + [clusters.ElectricalEnergyMeasurement.ID] = clusters.ElectricalEnergyMeasurement, + [clusters.ElectricalPowerMeasurement.ID] = clusters.ElectricalPowerMeasurement, + [clusters.ValveConfigurationAndControl.ID] = clusters.ValveConfigurationAndControl +} + +function embedded_cluster_utils.get_endpoints(device, cluster_id, opts) + -- If using older lua libs and need to check for an embedded cluster feature, + -- we must use the embedded cluster definitions here + if version.api < 11 and embedded_clusters[cluster_id] ~= nil then + local embedded_cluster = embedded_clusters[cluster_id] + local opts = opts or {} + if utils.table_size(opts) > 1 then + device.log.warn_with({hub_logs = true}, "Invalid options for get_endpoints") + return + end + local clus_has_features = function(clus, feature_bitmap) + if not feature_bitmap or not clus then return false end + return embedded_cluster.are_features_supported(feature_bitmap, clus.feature_map) + end + local eps = {} + for _, ep in ipairs(device.endpoints) do + for _, clus in ipairs(ep.clusters) do + if ((clus.cluster_id == cluster_id) + and (opts.feature_bitmap == nil or clus_has_features(clus, opts.feature_bitmap)) + and ((opts.cluster_type == nil and clus.cluster_type == "SERVER" or clus.cluster_type == "BOTH") + or (opts.cluster_type == clus.cluster_type)) + or (cluster_id == nil)) then + table.insert(eps, ep.endpoint_id) + if cluster_id == nil then break end + end + end + end + return eps + else + return device:get_endpoints(cluster_id, opts) + end + end + + return embedded_cluster_utils \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 9b90cfb1d7..6368b237da 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -149,6 +149,97 @@ local child_device_profile_overrides = { local detect_matter_thing +local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported" +local FIRST_EXPORT_REPORT_TIMESTAMP = "__first_export_report_timestamp" +local EXPORT_POLL_TIMER_SETTING_ATTEMPTED = "__export_poll_timer_setting_attempted" +local EXPORT_REPORT_TIMEOUT = "__export_report_timeout" +local TOTAL_EXPORTED_ENERGY = "__total_exported_energy" +local LAST_EXPORTED_REPORT_TIMESTAMP = "__last_exported_report_timestamp" +local RECURRING_EXPORT_REPORT_POLL_TIMER = "__recurring_export_report_poll_timer" +local MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds +local SUBSCRIPTION_REPORT_OCCURRED = "__subscription_report_occurred" + +local embedded_cluster_utils = require "embedded-cluster-utils" + +-- Include driver-side definitions when lua libs api version is < 11 +local version = require "version" +if version.api < 11 then + clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" + clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + clusters.ValveConfigurationAndControl = require "ValveConfigurationAndControl" +end + +-- Return an ISO-8061 timestamp in UTC +local function iso8061Timestamp(time) + return os.date("!%Y-%m-%dT%H:%M:%SZ", time) +end + +local function delete_export_poll_schedule(device) + local export_poll_timer = device:get_field(RECURRING_EXPORT_REPORT_POLL_TIMER) + if export_poll_timer then + device.thread:cancel_timer(export_poll_timer) + device:set_field(RECURRING_EXPORT_REPORT_POLL_TIMER, nil) + device:set_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED, nil) + end +end + +local function send_export_poll_report(device, latest_total_exported_energy_wh) + local current_time = os.time() + local last_time = device:get_field(LAST_EXPORTED_REPORT_TIMESTAMP) or 0 + device:set_field(LAST_EXPORTED_REPORT_TIMESTAMP, current_time, { persist = true }) + + -- Calculate the energy delta between reports + local energy_delta_wh = 0.0 + local previous_exported_report = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME) + if previous_exported_report and previous_exported_report.energy then + energy_delta_wh = math.max(latest_total_exported_energy_wh - previous_exported_report.energy, 0.0) + end + + -- Report the energy consumed during the time interval. The unit of these values should be 'Wh' + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ + start = iso8061Timestamp(last_time), + ["end"] = iso8061Timestamp(current_time - 1), + deltaEnergy = energy_delta_wh, + energy = latest_total_exported_energy_wh + })) +end + +local function create_poll_report_schedule(device) + local export_timer = device.thread:call_on_schedule( + device:get_field(EXPORT_REPORT_TIMEOUT), + send_export_poll_report(device, device:get_field(TOTAL_EXPORTED_ENERGY)), + "polling_export_report_schedule_timer" + ) + device:set_field(RECURRING_EXPORT_REPORT_POLL_TIMER, export_timer) +end + +local function set_poll_report_timer_and_schedule(device, is_cumulative_report) + local cumul_eps = embedded_cluster_utils.get_endpoints(device, + clusters.ElectricalEnergyMeasurement.ID, + {feature_bitmap = clusters.ElectricalEnergyMeasurement.types.Feature.CUMULATIVE_ENERGY }) + if #cumul_eps == 0 then + device:set_field(CUMULATIVE_REPORTS_NOT_SUPPORTED, true) + end + if #cumul_eps > 0 and not is_cumulative_report then + return + elseif not device:get_field(SUBSCRIPTION_REPORT_OCCURRED) then + device:set_field(SUBSCRIPTION_REPORT_OCCURRED, true) + elseif not device:get_field(FIRST_EXPORT_REPORT_TIMESTAMP) then + device:set_field(FIRST_EXPORT_REPORT_TIMESTAMP, os.time()) + else + local first_timestamp = device:get_field(FIRST_EXPORT_REPORT_TIMESTAMP) + local second_timestamp = os.time() + local report_interval_secs = second_timestamp - first_timestamp + device:set_field(EXPORT_REPORT_TIMEOUT, math.max(report_interval_secs, MINIMUM_ST_ENERGY_REPORT_INTERVAL)) + -- the poll schedule is only needed for devices that support powerConsumption + if device:supports_capability(capabilities.powerConsumptionReport) then + create_poll_report_schedule(device) + end + device:set_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED, true) + end +end + local START_BUTTON_PRESS = "__start_button_press" local TIMEOUT_THRESHOLD = 10 --arbitrary timeout local HELD_THRESHOLD = 1 @@ -246,7 +337,7 @@ 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 --- and supports the OnOff or Switch cluster. This is done to bypass the ---- BRIDGED_NODE_DEVICE_TYPE on bridged devices +--- BRIDGED_NODE_DEVICE_TYPE on bridged devices. local function find_default_endpoint(device, component) local switch_eps = device:get_endpoints(clusters.OnOff.ID) local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.Feature.MOMENTARY_SWITCH}) @@ -260,7 +351,7 @@ local function find_default_endpoint(device, component) end table.sort(all_eps) - for _,ep in ipairs(all_eps) do + for _, ep in ipairs(all_eps) do if ep ~= 0 then --0 is the matter RootNode endpoint return ep end @@ -299,6 +390,30 @@ local function assign_child_profile(device, child_ep) return profile or "switch-binary" end +local function do_configure(driver, device) + local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID) + local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID) + local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID) + local profile_name = nil + if #energy_eps > 0 and #power_eps > 0 then + profile_name = "plug-power-energy-powerConsumption" + elseif #energy_eps > 0 then + profile_name = "plug-energy-powerConsumption" + elseif #power_eps > 0 then + profile_name = "plug-power" + elseif #valve_eps > 0 then + profile_name = "water-valve" + if #embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID, + {feature_bitmap = clusters.ValveConfigurationAndControl.types.Feature.LEVEL}) > 0 then + profile_name = profile_name .. "-level" + end + end + + if profile_name then + device:try_update_metadata({ profile = profile_name }) + end +end + local function configure_buttons(device) if device.network_type ~= device_lib.NETWORK_TYPE_CHILD then local MS = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.Feature.MOMENTARY_SWITCH}) @@ -360,25 +475,25 @@ local function initialize_switch(driver, device) -- support for bindings. local num_switch_server_eps = 0 local main_endpoint = find_default_endpoint(device) - if #switch_eps > 0 then for _, ep in ipairs(switch_eps) do - -- Create child devices for non-main switch endpoints - num_switch_server_eps = num_switch_server_eps + 1 - if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint + if device:supports_server_cluster(clusters.OnOff.ID, ep) then + num_switch_server_eps = num_switch_server_eps + 1 local name = string.format("%s %d", device.label, num_switch_server_eps) - local child_profile = assign_child_profile(device, ep) - driver:try_create_device( - { - type = "EDGE_CHILD", - label = name, - profile = child_profile, - parent_device_id = device.id, - parent_assigned_child_key = string.format("%d", ep), - vendor_provided_label = name - } - ) - parent_child_device = true + if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint + local child_profile = assign_child_profile(device, ep) + driver:try_create_device( + { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%d", ep), + vendor_provided_label = name + } + ) + parent_child_device = true + end end end elseif #button_eps > 0 then @@ -409,7 +524,7 @@ local function initialize_switch(driver, device) device:set_field(COMPONENT_TO_ENDPOINT_MAP_BUTTON, component_map, {persist = true}) end - if #switch_eps > 0 then + if num_switch_server_eps > 0 then -- The case where num_switch_server_eps > 0 is a workaround for devices that have a -- Light Switch device type but implement the On Off cluster as server (which is against the spec -- for this device type). By default, we do not support Light Switch device types because by spec these @@ -417,7 +532,7 @@ local function initialize_switch(driver, device) -- do not have a generic fingerprint and will join as a matter-thing. However, we have seen some devices -- claim to be Light Switch device types and still implement their clusters as server, so this is a -- workaround for those devices. - if num_switch_server_eps > 0 and detect_matter_thing(device) == true then + if detect_matter_thing(device) then local id = 0 for _, ep in ipairs(device.endpoints) do -- main_endpoint only supports server cluster by definition of get_endpoints() @@ -526,6 +641,7 @@ end local function device_removed(driver, device) log.info("device removed") + delete_export_poll_schedule(device) end local function handle_switch_on(driver, device, cmd) @@ -547,7 +663,8 @@ local function handle_switch_off(driver, device, cmd) device:send(req) end -local function handle_set_level(driver, device, cmd) + +local function handle_set_switch_level(driver, device, cmd) if type(device.register_native_capability_cmd_handler) == "function" then device:register_native_capability_cmd_handler(cmd.capability, cmd.command) end @@ -610,6 +727,31 @@ local function handle_set_color_temperature(driver, device, cmd) device:send(req) end +local function handle_valve_open(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.ValveConfigurationAndControl.server.commands.Open(device, endpoint_id) + device:send(req) +end + +local function handle_valve_close(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.ValveConfigurationAndControl.server.commands.Close(device, endpoint_id) + device:send(req) +end + +local function handle_set_level(driver, device, cmd) + local commands = clusters.ValveConfigurationAndControl.server.commands + local endpoint_id = device:component_to_endpoint(cmd.component) + local level = cmd.args.level + if not level then + return + elseif level == 0 then + device:send(commands.Close(device, endpoint_id)) + else + device:send(commands.Open(device, endpoint_id, nil, level)) + end +end + local function handle_refresh(driver, device, cmd) --Note: no endpoint specified indicates a wildcard endpoint local req = clusters.OnOff.attributes.OnOff:read(device) @@ -778,6 +920,31 @@ local function occupancy_attr_handler(driver, device, ib, response) device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) end +local function cumul_energy_exported_handler(driver, device, ib, response) + device:set_field(TOTAL_EXPORTED_ENERGY, ib.data.elements.energy.value) + device:emit_event(capabilities.energyMeter.energy({ value = ib.data.elements.energy.value, unit = "Wh" })) +end + +local function per_energy_exported_handler(driver, device, ib, response) + local latest_energy_report = device:get_field(TOTAL_EXPORTED_ENERGY) or 0 + local summed_energy_report = latest_energy_report + ib.data.elements.energy.value + device:set_field(TOTAL_EXPORTED_ENERGY, summed_energy_report) + device:emit_event(capabilities.energyMeter.energy({ value = summed_energy_report, unit = "Wh" })) +end + +local function energy_report_handler_factory(is_cumulative_report) + return function(driver, device, ib, response) + if not device:get_field(EXPORT_POLL_TIMER_SETTING_ATTEMPTED) then + set_poll_report_timer_and_schedule(device, is_cumulative_report) + end + if is_cumulative_report then + cumul_energy_exported_handler(driver, device, ib, response) + elseif device:get_field(CUMULATIVE_REPORTS_NOT_SUPPORTED) then + per_energy_exported_handler(driver, device, ib, response) + end + end +end + local function initial_press_event_handler(driver, device, ib, response) if get_field_for_endpoint(device, SUPPORTS_MULTI_PRESS, ib.endpoint_id) then -- Receipt of an InitialPress event means we do not want to ignore the next MultiPressComplete event @@ -811,6 +978,26 @@ local function short_release_event_handler(driver, device, ib, response) end end +local function active_power_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event(capabilities.powerMeter.power({ value = ib.data.value, unit = "W"})) + end +end + +local function valve_state_attr_handler(driver, device, ib, response) + if ib.data.value == 0 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.valve.valve.closed()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.valve.valve.open()) + end +end + +local function valve_level_attr_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.level.level(ib.data.value)) + end +end + local function multi_press_complete_event_handler(driver, device, ib, response) -- in the case of multiple button presses -- emit number of times, multiple presses have been completed @@ -871,6 +1058,14 @@ local function device_added(driver, device) handle_refresh(driver, device) end + -- Reset the values + if device:supports_capability(capabilities.powerMeter) then + device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + end + if device:supports_capability(capabilities.energyMeter) then + device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + end + -- call device init in case init is not called after added due to device caching device_init(driver, device) end @@ -880,7 +1075,8 @@ local matter_driver_template = { init = device_init, added = device_added, removed = device_removed, - infoChanged = info_changed + infoChanged = info_changed, + doConfigure = do_configure }, matter_handlers = { attr = { @@ -908,6 +1104,17 @@ local matter_driver_template = { [clusters.OccupancySensing.ID] = { [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler, }, + [clusters.ElectricalPowerMeasurement.ID] = { + [clusters.ElectricalPowerMeasurement.attributes.ActivePower.ID] = active_power_handler, + }, + [clusters.ElectricalEnergyMeasurement.ID] = { + [clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported.ID] = energy_report_handler_factory(true), + [clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported.ID] = energy_report_handler_factory(false), + }, + [clusters.ValveConfigurationAndControl.ID] = { + [clusters.ValveConfigurationAndControl.attributes.CurrentState.ID] = valve_state_attr_handler, + [clusters.ValveConfigurationAndControl.attributes.CurrentLevel.ID] = valve_level_attr_handler + }, [clusters.PowerSource.ID] = { [clusters.PowerSource.attributes.BatPercentRemaining.ID] = battery_percent_remaining_attr_handler, }, @@ -951,9 +1158,22 @@ local matter_driver_template = { [capabilities.motionSensor.ID] = { clusters.OccupancySensing.attributes.Occupancy }, + [capabilities.valve.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentState + }, + [capabilities.level.ID] = { + clusters.ValveConfigurationAndControl.attributes.CurrentLevel + }, [capabilities.battery.ID] = { clusters.PowerSource.attributes.BatPercentRemaining, }, + [capabilities.energyMeter.ID] = { + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported + }, + [capabilities.powerMeter.ID] = { + clusters.ElectricalPowerMeasurement.attributes.ActivePower + } }, subscribed_events = { [capabilities.button.ID] = { @@ -969,7 +1189,7 @@ local matter_driver_template = { [capabilities.switch.commands.off.NAME] = handle_switch_off, }, [capabilities.switchLevel.ID] = { - [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level + [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_switch_level }, [capabilities.refresh.ID] = { [capabilities.refresh.commands.refresh.NAME] = handle_refresh, @@ -982,14 +1202,26 @@ local matter_driver_template = { [capabilities.colorTemperature.ID] = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = handle_set_color_temperature, }, + [capabilities.valve.ID] = { + [capabilities.valve.commands.open.NAME] = handle_valve_open, + [capabilities.valve.commands.close.NAME] = handle_valve_close + }, + [capabilities.level.ID] = { + [capabilities.level.commands.setLevel.NAME] = handle_set_level + } }, supported_capabilities = { capabilities.switch, capabilities.switchLevel, capabilities.colorControl, capabilities.colorTemperature, + capabilities.level, capabilities.motionSensor, capabilities.illuminanceMeasurement, + capabilities.powerMeter, + capabilities.energyMeter, + capabilities.powerConsumptionReport, + capabilities.valve, capabilities.button, capabilities.battery }, diff --git a/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua new file mode 100644 index 0000000000..23125b002a --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_electrical_sensor.lua @@ -0,0 +1,711 @@ +-- Copyright 2024 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" + +clusters.ElectricalEnergyMeasurement = require "ElectricalEnergyMeasurement" +clusters.ElectricalPowerMeasurement = require "ElectricalPowerMeasurement" + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-power-energy-powerConsumption.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 14, }, + { cluster_id = clusters.ElectricalPowerMeasurement.ID, cluster_type = "SERVER", feature_map = 0, }, + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 }, -- Electrical Sensor + } + }, + { + endpoint_id = 2, + clusters = { + { cluster_id = clusters.OnOff.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 0, }, + }, + device_types = { + { device_type_id = 0x010A, device_type_revision = 1 } -- OnOff Plug + } + }, + }, +}) + + +local mock_device_periodic = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("plug-energy-powerConsumption.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.ElectricalEnergyMeasurement.ID, cluster_type = "SERVER", feature_map = 10, }, + }, + device_types = { + { device_type_id = 0x0510, device_type_revision = 1 } -- Electrical Sensor + } + }, + }, +}) + +local subscribed_attributes_periodic = { + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported, +} +local subscribed_attributes = { + clusters.OnOff.attributes.OnOff, + clusters.ElectricalPowerMeasurement.attributes.ActivePower, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported, +} + +local cumulative_report_val_19 = { + energy = 19, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, +} + +local cumulative_report_val_29 = { + energy = 29, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, +} + +local cumulative_report_val_39 = { + energy = 39, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, +} + +local periodic_report_val_23 = { + energy = 23, + start_timestamp = 0, + end_timestamp = 0, + start_systime = 0, + end_systime = 0, +} + +local function test_init() + 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.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +local function test_init_periodic() + 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.mock_device.add_test_device(mock_device_periodic) +end + +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" }) + 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.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" })) + ) + + test.wait_for_events() + end +) + +test.register_message_test( + "On command should send the appropriate commands", + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "on" } + } + }, + { + { + 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, 2) + } + } + } +) + +test.register_message_test( + "Off command should send the appropriate commands", + { + channel = "devices", + direction = "send", + message = { + "register_native_capability_cmd_handler", + { device_uuid = mock_device.id, capability_id = "switch", capability_cmd_id = "off" } + } + }, + { + { + 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, 2) + } + } + } +) + +test.register_message_test( + "Active power measurement should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalPowerMeasurement.server.attributes.ActivePower:build_test_report_data(mock_device, 1, 17) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({value = 17, unit="W"})) + }, + } +) + +test.register_message_test( + "Cumulative Energy measurement should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyExported:build_test_report_data(mock_device, 1, cumulative_report_val_19) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 19, unit="Wh"})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyExported:build_test_report_data(mock_device, 1, cumulative_report_val_19) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 19, unit="Wh"})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyExported:build_test_report_data(mock_device, 1, cumulative_report_val_29) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 19 + })) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 29, unit="Wh"})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.CumulativeEnergyExported:build_test_report_data(mock_device, 1, cumulative_report_val_39) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 39, unit="Wh"})) + }, + } +) + +test.register_message_test( + "Periodic Energy as subordinate to Cumulative Energy measurement should not generate any messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyExported:build_test_report_data(mock_device, 1, periodic_report_val_23) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyExported:build_test_report_data(mock_device, 1, periodic_report_val_23) + } + }, + } +) + +test.register_message_test( + "Periodic Energy measurement should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyExported:build_test_report_data(mock_device_periodic, 1, periodic_report_val_23) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 23, unit="Wh"})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyExported:build_test_report_data(mock_device_periodic, 1, periodic_report_val_23) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 46, unit="Wh"})) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.server.attributes.PeriodicEnergyExported:build_test_report_data(mock_device_periodic, 1, periodic_report_val_23) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device_periodic:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1969-12-31T23:59:59Z", + deltaEnergy = 0.0, + energy = 46 + })) + }, + { + channel = "capability", + direction = "send", + message = mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({value = 69, unit="Wh"})) + }, + }, + { test_init = test_init_periodic } +) + +local MINIMUM_ST_ENERGY_REPORT_INTERVAL = (15 * 60) -- 15 minutes, reported in seconds + +test.register_coroutine_test( + "Generated poll timer (<15 minutes) gets correctly set", function() + + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(899) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:build_test_report_data( + mock_device, 1, cumulative_report_val_29 + ) + } + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:14:58Z", + deltaEnergy = 0.0, + energy = 19 + })) + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 29, unit = "Wh" })) + ) + test.wait_for_events() + local report_export_poll_timer = mock_device:get_field("__recurring_export_report_poll_timer") + local export_timer_length = mock_device:get_field("__export_report_timeout") + assert(report_export_poll_timer ~= nil, "report_export_poll_timer should exist") + assert(export_timer_length ~= nil, "export_timer_length should exist") + assert(export_timer_length == MINIMUM_ST_ENERGY_REPORT_INTERVAL, "export_timer should min_interval") + end +) + +test.register_coroutine_test( + "Generated poll timer (>15 minutes) gets correctly set", function() + + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(2000) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:build_test_report_data( + mock_device, 1, cumulative_report_val_29 + ) + } + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:33:19Z", + deltaEnergy = 0.0, + energy = 19 + })) + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 29, unit = "Wh" })) + ) + test.wait_for_events() + local report_export_poll_timer = mock_device:get_field("__recurring_export_report_poll_timer") + local export_timer_length = mock_device:get_field("__export_report_timeout") + assert(report_export_poll_timer ~= nil, "report_export_poll_timer should exist") + assert(export_timer_length ~= nil, "export_timer_length should exist") + assert(export_timer_length == 2000, "export_timer should min_interval") + end +) + +test.register_coroutine_test( + "Check when the device is removed", function() + + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported: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, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(2000) + test.socket["matter"]:__queue_receive( + { + mock_device.id, + clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyExported:build_test_report_data( + mock_device, 1, cumulative_report_val_29 + ) + } + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({ + start = "1970-01-01T00:00:00Z", + ["end"] = "1970-01-01T00:33:19Z", + deltaEnergy = 0.0, + energy = 19 + })) + ) + test.socket["capability"]:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 29, unit = "Wh" })) + ) + test.wait_for_events() + local report_export_poll_timer = mock_device:get_field("__recurring_export_report_poll_timer") + local export_timer_length = mock_device:get_field("__export_report_timeout") + assert(report_export_poll_timer ~= nil, "report_export_poll_timer should exist") + assert(export_timer_length ~= nil, "export_timer_length should exist") + assert(export_timer_length == 2000, "export_timer should min_interval") + + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "removed" }) + test.wait_for_events() + report_export_poll_timer = mock_device:get_field("__recurring_export_report_poll_timer") + export_timer_length = mock_device:get_field("__export_report_timeout") + assert(report_export_poll_timer == nil, "report_export_poll_timer should exist") + assert(export_timer_length == nil, "export_timer_length should exist") + end +) + +test.register_coroutine_test( + "Generated periodic export energy device poll timer (<15 minutes) gets correctly set", function() + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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, unit = "Wh" })) + ) + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(899) + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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.powerConsumptionReport.powerConsumption({ + deltaEnergy=0.0, + ["end"]="1970-01-01T00:14:58Z", + energy=46, + start="1970-01-01T00:00:00Z" + })) + ) + test.socket["capability"]:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({ value = 69, unit = "Wh" })) + ) + test.wait_for_events() + local report_export_poll_timer = mock_device_periodic:get_field("__recurring_export_report_poll_timer") + local export_timer_length = mock_device_periodic:get_field("__export_report_timeout") + assert(report_export_poll_timer ~= nil, "report_export_poll_timer should exist") + assert(export_timer_length ~= nil, "export_timer_length should exist") + assert(export_timer_length == MINIMUM_ST_ENERGY_REPORT_INTERVAL, "export_timer should min_interval") + end, + { test_init = test_init_periodic } +) + + +test.register_coroutine_test( + "Generated periodic export energy device poll timer (>15 minutes) gets correctly set", function() + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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, unit = "Wh" })) + ) + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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, unit = "Wh" })) + ) + test.wait_for_events() + test.mock_time.advance_time(2000) + test.socket["matter"]:__queue_receive( + { + mock_device_periodic.id, + clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyExported: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.powerConsumptionReport.powerConsumption({ + deltaEnergy=0.0, + ["end"] = "1970-01-01T00:33:19Z", + energy=46, + start="1970-01-01T00:00:00Z" + })) + ) + test.socket["capability"]:__expect_send( + mock_device_periodic:generate_test_message("main", capabilities.energyMeter.energy({ value = 69, unit = "Wh" })) + ) + test.wait_for_events() + local report_export_poll_timer = mock_device_periodic:get_field("__recurring_export_report_poll_timer") + local export_timer_length = mock_device_periodic:get_field("__export_report_timeout") + assert(report_export_poll_timer ~= nil, "report_export_poll_timer should exist") + assert(export_timer_length ~= nil, "export_timer_length should exist") + assert(export_timer_length == 2000, "export_timer should min_interval") + end, + { test_init = test_init_periodic } +) + +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" }) + mock_device:expect_metadata_update({ profile = "plug-power-energy-powerConsumption" }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + 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, "doConfigure" }) + mock_device_periodic:expect_metadata_update({ profile = "plug-energy-powerConsumption" }) + mock_device_periodic:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end, + { test_init = test_init_periodic } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua index 638931d03c..4329cbce65 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_switch_device_types.lua @@ -119,6 +119,34 @@ local mock_device_color_dimmer = test.mock_device.build_test_matter_device({ } }) +local mock_device_water_valve = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("matter-thing.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.ValveConfigurationAndControl.ID, cluster_type = "SERVER", cluster_revision = 1, feature_map = 2}, + }, + device_types = { + {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve + } + } + } +}) + local mock_device_parent_client_child_server = test.mock_device.build_test_matter_device({ profile = t_utils.get_profile_definition("matter-thing.yml"), manufacturer_info = { @@ -315,6 +343,11 @@ local function test_init_color_dimmer() mock_device_color_dimmer:expect_metadata_update({ profile = "switch-color-level" }) end +local function test_init_water_valve() + test.mock_device.add_test_device(mock_device_water_valve) + mock_device_water_valve:expect_metadata_update({ profile = "water-valve-level" }) +end + local function test_init_parent_child_different_types() local cluster_subscribe_list = { clusters.OnOff.attributes.OnOff, @@ -389,6 +422,13 @@ test.register_coroutine_test( { test_init = test_init_onoff_client } ) +test.register_coroutine_test( + "Test profile change on init for water valve parent cluster as server", + function() + end, + { test_init = test_init_water_valve } +) + test.register_coroutine_test( "Test profile change on init for onoff parent cluster as client and onoff child as server", function() diff --git a/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua new file mode 100644 index 0000000000..d4ead24458 --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_matter_water_valve.lua @@ -0,0 +1,235 @@ +-- Copyright 2024 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 mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("water-valve-level.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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.ValveConfigurationAndControl.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 2 + }, + }, + device_types = { + {device_type_id = 0x0042, device_type_revision = 1} -- Water Valve + } + } + } +}) + +local cluster_subscribe_list = { + clusters.ValveConfigurationAndControl.attributes.CurrentState, + clusters.ValveConfigurationAndControl.attributes.CurrentLevel +} + +local function test_init() + 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( + "Open command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "valve", component = "main", command = "open", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Close command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "valve", component = "main", command = "close", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.commands.Close(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Set level command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "level", component = "main", command = "setLevel", args = { 25 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.commands.Open(mock_device, 1, nil, 25) + } + } + } +) + +test.register_message_test( + "Set level command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "level", component = "main", command = "setLevel", args = { 0 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.commands.Close(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Current state reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentState:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.valve.valve.closed()) + }, + } +) + +test.register_message_test( + "Current state reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentState:build_test_report_data(mock_device, 1, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) + }, + } +) + +test.register_message_test( + "Current state reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentState:build_test_report_data(mock_device, 1, 2) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.valve.valve.open()) + }, + } +) + +test.register_message_test( + "Current level reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ValveConfigurationAndControl.server.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.level.level(50)) + }, + } +) + +test.run_registered_tests() diff --git a/tools/config.luacov b/tools/config.luacov index c72c977219..137c9c91a1 100644 --- a/tools/config.luacov +++ b/tools/config.luacov @@ -32,6 +32,16 @@ configuration = { "TotalVolatileOrganicCompoundsConcentrationMeasurement", "ActivatedCarbonFilterMonitoring", "HepaFilterMonitoring", + "MicrowaveOvenControl", + "MicrowaveOvenMode", + "OvenMode", + "DeviceEnergyManagementMode", + "ElectricalEnergyMeasurement", + "ElectricalPowerMeasurement", + "EnergyEvse", + "EnergyEvseMode", + "BooleanStateConfiguration", + "ValveConfigurationAndControl", }, cobertura = { filenameparser = function(filename) From f6ceb31212a9cc8869404aab4b760f2669885ec7 Mon Sep 17 00:00:00 2001 From: Detailzc <138568377+Detailzc@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:30:10 +0800 Subject: [PATCH 6/9] Add letianpai 1/2/3 switches and bulb Chinese name (#1652) Signed-off-by: WangShao --- tools/localizations/cn.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index c68aea9de6..a137a621d8 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -68,3 +68,7 @@ Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "Aqara Smart Wall Switch T1 (With Neutral, Single Rocker)",Aqara智能墙壁开关T1 (零火线单键版) "Aqara Smart Wall Switch T1 (With Neutral, Double Rocker)",Aqara智能墙壁开关T1 (零火线双键版) "Aqara Smart Wall Switch T1 (With Neutral, Triple Rocker)",Aqara智能墙壁开关T1 (零火线三键版) +"LeTianPai Smart Switch 1 Way",乐天派智能开关(零火版)单开 +"LeTianPai Smart Switch 2 Way",乐天派智能开关(零火版)双开 +"LeTianPai Smart Switch 3 Way",乐天派智能开关(零火版)三开 +"LeTianPai Smart Light Bulb",乐天派智能灯泡 From 0d7bee272ac40b8d089de1ea205572c1edfabbb5 Mon Sep 17 00:00:00 2001 From: Alissa Dornbos <79465613+lelandblue@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:35:46 -0400 Subject: [PATCH 7/9] New Devices (Matter Switch) LetainPai Smart Switch(s) (#1649) * New-devices-letianpai-three-switches * Update Device Labels There was feedback provided from the partner that they wanted updated Device Labels, so that feedback is being incorporated in this commit. * Fixing Brand Name Spelling Error --- .../SmartThings/matter-switch/fingerprints.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index e61e164a05..7cb345c535 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -218,6 +218,22 @@ matterManufacturer: vendorId: 0x1021 productId: 0x0005 deviceProfileName: switch-binary +#LeTianPai + - id: "5163/4100" + deviceLabel: LeTianPai Smart Switch 3 Way + vendorId: 0x142B + productId: 0x1004 + deviceProfileName: switch-binary + - id: "5163/4099" + deviceLabel: LeTianPai Smart Switch 2 Way + vendorId: 0x142B + productId: 0x1003 + deviceProfileName: switch-binary + - id: "5163/4098" + deviceLabel: LeTianPai Smart Switch 1 Way + vendorId: 0x142B + productId: 0x1002 + deviceProfileName: switch-binary #Lifx - id: "5155/169" deviceLabel: LIFX Color (A21) From bf42ce6770d2f0f59338761d2546d316d70c69e7 Mon Sep 17 00:00:00 2001 From: Alissa Dornbos <79465613+lelandblue@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:30:47 -0400 Subject: [PATCH 8/9] New Device Level Bolt (#1650) --- drivers/SmartThings/matter-lock/fingerprints.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml index 1c3e420473..e8e5f86be1 100755 --- a/drivers/SmartThings/matter-lock/fingerprints.yml +++ b/drivers/SmartThings/matter-lock/fingerprints.yml @@ -16,6 +16,11 @@ matterManufacturer: vendorId: 0x129F productId: 0x0001 deviceProfileName: lock-nocodes-notamper-batteryLevel + - id: "4767/3" + deviceLabel: Level Bolt (Matter) + vendorId: 0x129F + productId: 0x0003 + deviceProfileName: lock-nocodes-notamper-batteryLevel #Nuki - id: "Nuki Smart Lock Pro" deviceLabel: Nuki Smart Lock Pro From 9a008a5b7c0fbecf268bc96d399666a3da146014 Mon Sep 17 00:00:00 2001 From: nickolas-deboom <158304111+nickolas-deboom@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:20:54 -0500 Subject: [PATCH 9/9] Sort endpoints before initializing switch (#1657) Sort endpoints before initializing switch --- .../SmartThings/matter-switch/src/init.lua | 2 + .../test_multi_switch_parent_child_lights.lua | 146 +++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 6368b237da..9c6da7f50f 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -458,6 +458,8 @@ end local function initialize_switch(driver, device) local switch_eps = device:get_endpoints(clusters.OnOff.ID) local button_eps = device:get_endpoints(clusters.Switch.ID, {feature_bitmap=clusters.Switch.types.Feature.MOMENTARY_SWITCH}) + table.sort(switch_eps) + table.sort(button_eps) local profile_name = nil diff --git a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua index b198834a1e..bb004c00ce 100644 --- a/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua +++ b/drivers/SmartThings/matter-switch/src/test/test_multi_switch_parent_child_lights.lua @@ -76,9 +76,75 @@ local mock_device = test.mock_device.build_test_matter_device({ } }) +local child1_ep_non_sequential = 50 +local child2_ep_non_sequential = 30 +local child3_ep_non_sequential = 40 + +local mock_device_parent_child_endpoints_non_sequential = test.mock_device.build_test_matter_device({ + label = "Matter Switch", + profile = t_utils.get_profile_definition("light-level-colorTemperature.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + 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 = parent_ep, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0100, device_type_revision = 2} -- On/Off Light + } + }, + { + endpoint_id = child1_ep_non_sequential, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2} + }, + device_types = { + {device_type_id = 0x0100, device_type_revision = 2}, -- On/Off Light + {device_type_id = 0x0101, device_type_revision = 2} -- Dimmable Light + } + }, + { + endpoint_id = child2_ep_non_sequential, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, + }, + device_types = { + {device_type_id = 0x010D, device_type_revision = 2} -- Extended Color Light + } + }, + { + endpoint_id = child3_ep_non_sequential, + clusters = { + {cluster_id = clusters.OnOff.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER", feature_map = 2}, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 30}, + }, + device_types = { + {device_type_id = 0x010D, device_type_revision = 2} -- Extended Color Light + } + }, + } +}) + local child_profiles = { [child1_ep] = t_utils.get_profile_definition("light-level.yml"), - [child2_ep] = t_utils.get_profile_definition("light-color-level.yml") + [child2_ep] = t_utils.get_profile_definition("light-color-level.yml"), } local mock_children = {} @@ -138,6 +204,77 @@ local function test_init() }) end +local child_profiles_non_sequential = { + [child1_ep_non_sequential] = t_utils.get_profile_definition("light-level.yml"), + [child2_ep_non_sequential] = t_utils.get_profile_definition("light-color-level.yml"), + [child3_ep_non_sequential] = t_utils.get_profile_definition("light-color-level.yml"), +} + +local mock_children_non_sequential = {} +for i, endpoint in ipairs(mock_device_parent_child_endpoints_non_sequential.endpoints) do + if endpoint.endpoint_id ~= parent_ep and endpoint.endpoint_id ~= 0 then + local child_data = { + profile = child_profiles_non_sequential[endpoint.endpoint_id], + device_network_id = string.format("%s:%d", mock_device_parent_child_endpoints_non_sequential.id, endpoint.endpoint_id), + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", endpoint.endpoint_id) + } + mock_children_non_sequential[endpoint.endpoint_id] = test.mock_device.build_test_child_device(child_data) + end +end + +local function test_init_parent_child_endpoints_non_sequential() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.LevelControl.attributes.MaxLevel, + clusters.LevelControl.attributes.MinLevel, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMaxMireds, + clusters.ColorControl.attributes.ColorTempPhysicalMinMireds, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY + } + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device_parent_child_endpoints_non_sequential) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device_parent_child_endpoints_non_sequential)) + end + end + test.socket.matter:__expect_send({mock_device_parent_child_endpoints_non_sequential.id, subscribe_request}) + + test.mock_device.add_test_device(mock_device_parent_child_endpoints_non_sequential) + for _, child in pairs(mock_children_non_sequential) do + test.mock_device.add_test_device(child) + end + + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 2", + profile = "light-color-level", + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child2_ep_non_sequential) + }) + + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 3", + profile = "light-color-level", + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child3_ep_non_sequential) + }) + + mock_device_parent_child_endpoints_non_sequential:expect_device_create({ + type = "EDGE_CHILD", + label = "Matter Switch 4", + profile = "light-level", + parent_device_id = mock_device_parent_child_endpoints_non_sequential.id, + parent_assigned_child_key = string.format("%d", child1_ep_non_sequential) + }) +end + test.set_test_init_function(test_init) test.register_message_test( @@ -494,4 +631,11 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Test child devices are created in order of their endpoints", + function() + end, + { test_init = test_init_parent_child_endpoints_non_sequential } +) + test.run_registered_tests()