From 8e85df4023eb567473e0f1796d746f04a5fa280c Mon Sep 17 00:00:00 2001 From: Eric Maycock Date: Mon, 30 Jun 2025 16:10:27 -0600 Subject: [PATCH 01/32] Add support for Inovelli mmWave switch --- .../zigbee-switch/fingerprints.yml | 5 + .../profiles/inovelli-vzm32-sn.yml | 400 +++++++++++++++ .../SmartThings/zigbee-switch/src/init.lua | 4 +- .../src/inovelli-vzm32-sn/init.lua | 478 ++++++++++++++++++ 4 files changed, 886 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index d45c0aa13c..b6bd96f0eb 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2309,6 +2309,11 @@ zigbeeManufacturer: manufacturer: Inovelli model: VZM31-SN deviceProfileName: inovelli-vzm31-sn + - id: "Inovelli/VZM32-SN" + deviceLabel: "Inovelli mmWave Dimmer Blue Series" + manufacturer: Inovelli + model: VZM32-SN + deviceProfileName: inovelli-vzm32-sn - id: "LAISIAO/BATH" deviceLabel: Laisiao Bathroom Heater manufacturer: LAISIAO diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml new file mode 100644 index 0000000000..9a543a6bc7 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -0,0 +1,400 @@ +name: inovelli-vzm32-sn +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + config: + values: + - key: "illuminance.value" + range: [0, 5000] + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: refresh + version: 1 + - id: configuration + version: 1 + - id: firmwareUpdate + version: 1 + categories: + - name: Switch + - id: button1 + label: Down Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button2 + label: Up Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController + - id: button3 + label: Config Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "255": "Clear" + "1": "Solid" + "2": "Fast Blink" + "3": "Slow Blink" + "4": "Pulse" + "5": "Chase" + "6": "Open/Close" + "7": "Small-to-Big" + "8": "Aurora" + "9": "Slow Falling" + "10": "Medium Falling" + "11": "Fast Falling" + "12": "Slow Rising" + "13": "Medium Rising" + "14": "Fast Rising" + "15": "Medium Blink" + "16": "Slow Chase" + "17": "Fast Chase" + "18": "Fast Siren" + "19": "Slow Siren" + default: 1 + - name: "parameter258" + title: "258. Switch Mode" + description: "Use as a Dimmer or an On/Off switch" + required: false + preferenceType: enumeration + definition: + options: + "0": "Dimmer" + "1": "On/Off (default)" + default: 1 + - name: "parameter22" + title: "22. Aux Switch Type" + description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: false + preferenceType: enumeration + definition: + options: + "0": "None (default)" + "1": "3-Way Dumb Switch" + "2": "3-Way Aux Switch" + "3": "Full Sine Wave (fw 2.11+)" + default: 0 + - name: "parameter52" + title: "52. Smart Bulb Mode" + description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: false + preferenceType: enumeration + definition: + options: + "0": "Disabled (default)" + "1": "Smart Bulb Mode" + default: 0 + - name: "parameter1" + title: "1. Dimming Speed (Remote)" + description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + Default=25 (2500ms or 2.5s)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 126 + default: 25 + - name: "parameter2" + title: "2. Dimming Speed (Local)" + description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter3" + title: "3. Ramp Rate (Remote)" + description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter4" + title: "4. Ramp Rate (Local)" + description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 3)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 127 + default: 127 + - name: "parameter9" + title: "9. Minimum Level" + description: "The minimum level that the light can be dimmed. Useful when the user has a light that does not turn on or flickers at a lower level." + required: false + preferenceType: number + definition: + minimum: 1 + maximum: 99 + default: 1 + - name: "parameter10" + title: "10. Maximum Level" + description: "The maximum level that the light can be dimmed. Useful when the user wants to limit the maximum brighness." + required: false + preferenceType: number + definition: + minimum: 2 + maximum: 100 + default: 100 + - name: "parameter11" + title: "11. Invert Switch" + description: "Inverts the orientation of the switch. Useful when the switch is installed upside down. Essentially up becomes down and down becomes up." + required: false + preferenceType: enumeration + definition: + options: + "0": "No (default)" + "1": "Yes" + default: 0 + - name: "parameter15" + title: "15. Level After Power Restored" + description: "The level the switch will return to when power is restored after power failure. + 0=Off + 1-100=Set Level + 101=Use previous level." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 101 + default: 101 + - name: "parameter17" + title: "17. Load Level Indicator Timeout" + description: "Shows the level that the load is at for x number of seconds after the load is adjusted and then returns to the Default LED state." + required: false + preferenceType: enumeration + definition: + options: + "0": "Do not display Load Level" + "1": "1 Second" + "2": "2 Seconds" + "3": "3 Seconds" + "4": "4 Seconds" + "5": "5 Seconds" + "6": "6 Seconds" + "7": "7 Seconds" + "8": "8 Seconds" + "9": "9 Seconds" + "10": "10 Seconds" + "11": "Display Load Level with no timeout" + default: 11 + - name: "parameter95" + title: "95. LED Indicator Color (w/On)" + description: "Set the color of the Full LED Indicator when the load is on." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter96" + title: "96. LED Indicator Color (w/Off)" + description: "Set the color of the Full LED Indicator when the load is off." + required: false + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter97" + title: "97. LED Indicator Intensity (w/On)" + description: "Set the intensity of the Full LED Indicator when the load is on." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 50 + - name: "parameter98" + title: "98. LED Indicator Intensity (w/Off)" + description: "Set the intensity of the Full LED Indicator when the load is off." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 5 + - name: "parameter101" + title: "101. mmWave Height Minimum (Floor)" + description: "Minimum range of the Z-Axis in mm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -300 + - name: "parameter102" + title: "102. mmWave Height Maximum (Ceiling)" + description: "Maximum range of the Z-Axis in mm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 300 + - name: "parameter103" + title: "103. mmWave Width Minimum (Left)" + description: "Minimum range of the X-Axis in mm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -600 + - name: "parameter104" + title: "104. mmWave Width Maximum (Right)" + description: "Maximum range of the X-Axis in mm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 600 + - name: "parameter105" + title: "105. mmWave Depth Minimum (Near)" + description: "Minimum range of the Y-Axis in mm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 0 + - name: "parameter106" + title: "106. mmWave Depth Maximum (Far)" + description: "Maximum range of the Y-Axis in mm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 600 + - name: "parameter110" + title: "110. Light On Presence Behavior" + description: "When presence is detected, choose how to control the light load" + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled" + "1": "Auto On/Off when occupied (default)" + "2": "Auto Off when vacant" + "3": "Auto On when occupied" + "4": "Auto On/Off when Vacant" + "5": "Auto On when Vacant" + "6": "Auto Off when Occupied" + default: 1 + - name: "parameter111" + title: "111. mmWave Control Commands" + description: "Advanced commands to send to the mmWave Module (Please see documentation)" + required: false + preferenceType: enumeration + definition: + options: + "1": "Set Interference Area" + "3": "Clear Interference Area" + "255": "Factory Reset Module" + default: 1 + - name: "parameter112" + title: "112. mmWave Detection Sensitivity" + description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." + required: false + preferenceType: enumeration + definition: + options: + "0": "Low" + "1": "Medium" + "2": "High (default)" + default: 2 + - name: "parameter113" + title: "113. mmWave Detection Delay" + description: "The time from detecting a person to triggering an action. 0-Low (5s), 1-Medium (1s), 2-Fast (0.2s)." + required: false + preferenceType: enumeration + definition: + options: + "0": "5 seconds" + "1": "1 second" + "2": "0.2 seconds (default)" + default: 2 + - name: "parameter114" + title: "114. mmWave Time Out" + description: "Adjust the timeout after presence is no longer detected. After the timeout the load will turn off." + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 4294967295 + default: 10 + - name: "parameter117" + title: "117. Room Size Preset" + description: "Allows selection of predefined room dimensions for mmWave sensor processing." + required: false + preferenceType: enumeration + definition: + options: + "0": "Custom (User-defined)" + "1": "Small" + "2": "Medium" + "3": "Large" + default: 0 diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 85efef5e11..5d5b418ba4 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -138,7 +138,8 @@ local zigbee_switch_driver_template = { capabilities.colorTemperature, capabilities.powerMeter, capabilities.energyMeter, - capabilities.motionSensor + capabilities.motionSensor, + capabilities.illuminanceMeasurement, }, sub_drivers = { lazy_load_if_possible("hanssem"), @@ -164,6 +165,7 @@ local zigbee_switch_driver_template = { lazy_load_if_possible("robb"), lazy_load_if_possible("wallhero"), lazy_load_if_possible("inovelli-vzm31-sn"), + lazy_load_if_possible("inovelli-vzm32-sn"), lazy_load_if_possible("laisiao"), lazy_load_if_possible("tuya-multi") }, diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua new file mode 100644 index 0000000000..ee38121f2c --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -0,0 +1,478 @@ +-- 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 clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local utils = require "st.utils" +local st_device = require "st.device" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local device_management = require "st.zigbee.device_management" +local configurations = require "configurations" +local OccupancySensing = clusters.OccupancySensing + +local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" + +local INOVELLI_VZM32_SN_FINGERPRINTS = { + { mfr = "Inovelli", model = "VZM32-SN" } +} + +local PRIVATE_CLUSTER_ID = 0xFC31 +local PRIVATE_CLUSTER_MMWAVE_ID = 0xFC32 +local PRIVATE_CMD_NOTIF_ID = 0x01 +local PRIVATE_CMD_SCENE_ID =0x00 +local PRIVATE_CMD_MMWAVE_ID = 0x00 +local MFG_CODE = 0x122F + +local preference_map = { + parameter258 = {parameter_number = 258, size = data_types.Boolean}, + parameter22 = {parameter_number = 22, size = data_types.Uint8}, + parameter52 = {parameter_number = 52, size = data_types.Boolean}, + parameter1 = {parameter_number = 1, size = data_types.Uint8}, + parameter2 = {parameter_number = 2, size = data_types.Uint8}, + parameter3 = {parameter_number = 3, size = data_types.Uint8}, + parameter4 = {parameter_number = 4, size = data_types.Uint8}, + parameter9 = {parameter_number = 9, size = data_types.Uint8}, + parameter10 = {parameter_number = 10, size = data_types.Uint8}, + parameter11 = {parameter_number = 11, size = data_types.Boolean}, + parameter15 = {parameter_number = 15, size = data_types.Uint8}, + parameter17 = {parameter_number = 17, size = data_types.Uint8}, + parameter95 = {parameter_number = 95, size = data_types.Uint8}, + parameter96 = {parameter_number = 96, size = data_types.Uint8}, + parameter97 = {parameter_number = 97, size = data_types.Uint8}, + parameter98 = {parameter_number = 98, size = data_types.Uint8}, + parameter101 = {parameter_number = 101, size = data_types.Int16}, + parameter102 = {parameter_number = 102, size = data_types.Int16}, + parameter103 = {parameter_number = 103, size = data_types.Int16}, + parameter104 = {parameter_number = 104, size = data_types.Int16}, + parameter105 = {parameter_number = 105, size = data_types.Int16}, + parameter106 = {parameter_number = 106, size = data_types.Int16}, + parameter110 = {parameter_number = 110, size = data_types.Uint8}, + parameter111 = {parameter_number = 111, size = data_types.Uint32}, + parameter112 = {parameter_number = 112, size = data_types.Uint8}, + parameter113 = {parameter_number = 113, size = data_types.Uint8}, + parameter114 = {parameter_number = 114, size = data_types.Uint32}, + parameter115 = {parameter_number = 115, size = data_types.Uint32}, + parameter117 = {parameter_number = 117, size = data_types.Uint8}, +} + +local preferences_to_numeric_value = function(new_value) + local numeric = tonumber(new_value) + if numeric == nil then -- in case the value is Boolean + numeric = new_value and 1 or 0 + end + return numeric +end + +local preferences_calculate_parameter = function(new_value, type, number) + if number == "parameter9" or number == "parameter10" or number == "parameter13" or number == "parameter14" or number == "parameter15" or number == "parameter55" or number == "parameter56" then + if new_value == 101 then + return 255 + else + return utils.round(new_value / 100 * 254) + end + else + return new_value + end +end + +local is_inovelli_vzm32_sn = function(opts, driver, device) + for _, fingerprint in ipairs(INOVELLI_VZM32_SN_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("inovelli-vzm32-sn") + return true, subdriver + end + end + return false +end + +local function to_boolean(value) + if value == 0 or value =="0" then + return false + else + return true + end +end + +local map_key_attribute_to_capability = { + [0x00] = capabilities.button.button.pushed, + [0x01] = capabilities.button.button.held, + [0x02] = capabilities.button.button.down_hold, + [0x03] = capabilities.button.button.pushed_2x, + [0x04] = capabilities.button.button.pushed_3x, + [0x05] = capabilities.button.button.pushed_4x, + [0x06] = capabilities.button.button.pushed_5x, +} + +local key_mmwave_preferences = { + "parameter101", + "parameter102", + "parameter103", + "parameter104", + "parameter105", + "parameter106", + "parameter111", + "parameter112", + "parameter113", + "parameter114", + "parameter117", +} + +local function button_to_component(buttonId) + if buttonId > 0 then + return string.format("button%d", buttonId) + end +end + +local function scene_handler(driver, device, zb_rx) + local bytes = zb_rx.body.zcl_body.body_bytes + local button_number = bytes:byte(1) + local capability_attribute = map_key_attribute_to_capability[bytes:byte(2)] + local additional_fields = { + state_change = true + } + + local event + if capability_attribute ~= nil then + event = capability_attribute(additional_fields) + end + + local comp = device.profile.components[button_to_component(button_number)] + if comp ~= nil then + device:emit_component_event(comp, event) + end +end + +local function add_child(driver,parent,profile,child_type) + local child_metadata = { + type = "EDGE_CHILD", + label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), + profile = profile, + parent_device_id = parent.id, + parent_assigned_child_key = child_type, + vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) + } + driver:try_create_device(child_metadata) +end + +local function contains(array, value) + for _, element in ipairs(array) do + if element == value then + return true + end + end + return false +end + +local function info_changed(driver, device, event, args) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local time_diff = 3 + local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) + if last_clock_set_time ~= nil then + time_diff = os.difftime(os.time(), last_clock_set_time) + end + device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) + + if time_diff > 2 then + local preferences = preference_map + if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then + if not device:get_child_by_parent_assigned_key('notification') then + add_child(driver,device,'rgbw-bulb-2700K-6500K','notificaiton') + end + end + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then + local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) + if id == "parameter111" then + print("mmwave control command: " .. id .. " " .. value) + device:send(cluster_base.build_manufacturer_specific_command( + device, + PRIVATE_CLUSTER_MMWAVE_ID, + PRIVATE_CMD_MMWAVE_ID, + MFG_CODE, + utils.serialize_int(new_parameter_value,1,false,false))) + elseif contains(key_mmwave_preferences, id) then + print("mmwave preference: " .. id .. " " .. value) + if(preferences[id].size == data_types.Boolean) then + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_MMWAVE_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, to_boolean(new_parameter_value))) + else + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_MMWAVE_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) + end + else + print("preference: " .. id .. " " .. value) + if(preferences[id].size == data_types.Boolean) then + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, to_boolean(new_parameter_value))) + else + device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) + end + end + end + end + device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) + end + end +end + +local do_configure = function(self, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:refresh() + device:configure() + + device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. + + -- Retrieve Neutral Setting "Parameter 21" + device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, 21, MFG_CODE)) + device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) + + device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( + device, + 10, -- Minimum reporting interval (seconds) + 60, -- Maximum reporting interval (seconds) + 25 -- Reportable change (in raw unit values) + )) + + -- Additional one time configuration + if device:supports_capability(capabilities.powerMeter) then + -- Divisor and multipler for PowerMeter + device:send(clusters.SimpleMetering.attributes.Divisor:read(device)) + device:send(clusters.SimpleMetering.attributes.Multiplier:read(device)) + end + + if device:supports_capability(capabilities.energyMeter) then + -- Divisor and multipler for EnergyMeter + device:send(clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(device)) + device:send(clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(device)) + end + end +end + +local device_init = function(self, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time()) + if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil and device:supports_capability(capabilities.switchLevel)then + device:emit_event(capabilities.switchLevel.level(0)) + end + if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == nil and device:supports_capability(capabilities.powerMeter) then + device:emit_event(capabilities.powerMeter.power(0)) + end + if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil and device:supports_capability(capabilities.energyMeter)then + device:emit_event(capabilities.energyMeter.energy(0)) + end + + for _, component in pairs(device.profile.components) do + if string.find(component.id, "button") ~= nil then + if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.supportedButtonValues.NAME) == nil then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + { visibility = { displayed = false } } + ) + ) + end + if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.numberOfButtons.NAME) == nil then + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + end + end + end + device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local function energy_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + raw_value = raw_value / 100 + device:emit_event(capabilities.energyMeter.energy({value = raw_value, unit = "kWh" })) +end + +local function power_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + raw_value = raw_value / 10 + device:emit_event(capabilities.powerMeter.power({value = raw_value, unit = "W" })) +end + +local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return utils.round(value / 100 * 255) + end +end + +local function getNotificationValue(device, value) + local notificationValue = 0 + local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 + local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) + local effect = device:get_parent_device().preferences.notificationType or 1 + notificationValue = notificationValue + (effect*16777216) + notificationValue = notificationValue + (huePercentToValue(value or color)*65536) + notificationValue = notificationValue + (level*256) + notificationValue = notificationValue + (255*1) + return notificationValue +end + +local function on_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.OnOff.server.commands.On(device)) + else + device:emit_event(capabilities.switch.switch("on")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end +end + +local function off_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.OnOff.server.commands.Off(device)) + else + device:emit_event(capabilities.switch.switch("off")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(0,4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end +end + +local function switch_level_handler(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(clusters.Level.server.commands.MoveToLevelWithOnOff(device, math.floor(command.args.level/100.0 * 254), command.args.rate or 0xFFFF)) + else + device:emit_event(capabilities.switchLevel.level(command.args.level)) + device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) + end +end + +local function set_color_temperature(driver, device, command) + device:emit_event(capabilities.colorControl.hue(100)) + device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device, 100),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) +end + +local function set_color(driver, device, command) + device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) + device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) + local dev = device:get_parent_device() + local send_configuration = function() + dev:send(cluster_base.build_manufacturer_specific_command( + dev, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_NOTIF_ID, + MFG_CODE, + utils.serialize_int(getNotificationValue(device),4,false,false))) + end + device.thread:call_with_delay(1,send_configuration) +end + +local function occupancy_attr_handler(driver, device, occupancy, zb_rx) + device:emit_event(occupancy.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) +end + +local function illuminance_attr_handler(driver, device, illuminance, zb_rx) + local lux = math.floor(10 ^ ((illuminance.value - 1) / 10000)) + print("illuminance: " .. lux) + device:emit_event(capabilities.illuminanceMeasurement.illuminance({value = lux, unit = "lux" })) +end + +local inovelli_vzm32_sn = { + NAME = "inovelli vzm32-sn handler", + lifecycle_handlers = { + doConfigure = do_configure, + init = configurations.power_reconfig_wrapper(device_init), + infoChanged = info_changed + }, + zigbee_handlers = { + attr = { + [clusters.SimpleMetering.ID] = { + [clusters.SimpleMetering.attributes.InstantaneousDemand.ID] = power_meter_handler, + [clusters.SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler + }, + [clusters.ElectricalMeasurement.ID] = { + [clusters.ElectricalMeasurement.attributes.ActivePower.ID] = power_meter_handler + }, + [OccupancySensing.ID] = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler + }, + [clusters.IlluminanceMeasurement.ID] = { + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = illuminance_attr_handler + }, + }, + cluster = { + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_CMD_SCENE_ID] = scene_handler, + } + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler, + [capabilities.switch.commands.off.NAME] = off_handler, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler + }, + [capabilities.colorControl.ID] = { + [capabilities.colorControl.commands.setColor.NAME] = set_color + }, + [capabilities.colorTemperature.ID] = { + [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature + } + }, + can_handle = is_inovelli_vzm32_sn +} + +return inovelli_vzm32_sn From 014f04f64728b6e0f7ff10cb7a92f3ad6afc334f Mon Sep 17 00:00:00 2001 From: Inovelli <37669481+InovelliUSA@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:58:11 -0600 Subject: [PATCH 02/32] Fix for measurement unit being cm (not mm) --- .../zigbee-switch/profiles/inovelli-vzm32-sn.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml index 9a543a6bc7..988c804414 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -277,7 +277,7 @@ preferences: default: 5 - name: "parameter101" title: "101. mmWave Height Minimum (Floor)" - description: "Minimum range of the Z-Axis in mm" + description: "Minimum range of the Z-Axis in cm" required: true preferenceType: number definition: @@ -286,7 +286,7 @@ preferences: default: -300 - name: "parameter102" title: "102. mmWave Height Maximum (Ceiling)" - description: "Maximum range of the Z-Axis in mm" + description: "Maximum range of the Z-Axis in cm" required: true preferenceType: number definition: @@ -295,7 +295,7 @@ preferences: default: 300 - name: "parameter103" title: "103. mmWave Width Minimum (Left)" - description: "Minimum range of the X-Axis in mm" + description: "Minimum range of the X-Axis in cm" required: true preferenceType: number definition: @@ -304,7 +304,7 @@ preferences: default: -600 - name: "parameter104" title: "104. mmWave Width Maximum (Right)" - description: "Maximum range of the X-Axis in mm" + description: "Maximum range of the X-Axis in cm" required: true preferenceType: number definition: @@ -313,7 +313,7 @@ preferences: default: 600 - name: "parameter105" title: "105. mmWave Depth Minimum (Near)" - description: "Minimum range of the Y-Axis in mm" + description: "Minimum range of the Y-Axis in cm" required: true preferenceType: number definition: @@ -322,7 +322,7 @@ preferences: default: 0 - name: "parameter106" title: "106. mmWave Depth Maximum (Far)" - description: "Maximum range of the Y-Axis in mm" + description: "Maximum range of the Y-Axis in cm" required: true preferenceType: number definition: From bb6b5567706f65bc7a33fc0342791d06685cffe4 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Sat, 26 Jul 2025 14:30:56 -0600 Subject: [PATCH 03/32] Configure illuminance reporting and fix p101-106 unit incorrect --- .../profiles/inovelli-vzm32-sn.yml | 20 ++++++++++--------- .../src/inovelli-vzm32-sn/init.lua | 14 +++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml index 9a543a6bc7..2220653a2c 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -277,7 +277,7 @@ preferences: default: 5 - name: "parameter101" title: "101. mmWave Height Minimum (Floor)" - description: "Minimum range of the Z-Axis in mm" + description: "Minimum range of the Z-Axis in cm" required: true preferenceType: number definition: @@ -286,7 +286,7 @@ preferences: default: -300 - name: "parameter102" title: "102. mmWave Height Maximum (Ceiling)" - description: "Maximum range of the Z-Axis in mm" + description: "Maximum range of the Z-Axis in cm" required: true preferenceType: number definition: @@ -295,7 +295,7 @@ preferences: default: 300 - name: "parameter103" title: "103. mmWave Width Minimum (Left)" - description: "Minimum range of the X-Axis in mm" + description: "Minimum range of the X-Axis in cm" required: true preferenceType: number definition: @@ -304,7 +304,7 @@ preferences: default: -600 - name: "parameter104" title: "104. mmWave Width Maximum (Right)" - description: "Maximum range of the X-Axis in mm" + description: "Maximum range of the X-Axis in cm" required: true preferenceType: number definition: @@ -313,7 +313,7 @@ preferences: default: 600 - name: "parameter105" title: "105. mmWave Depth Minimum (Near)" - description: "Minimum range of the Y-Axis in mm" + description: "Minimum range of the Y-Axis in cm" required: true preferenceType: number definition: @@ -322,7 +322,7 @@ preferences: default: 0 - name: "parameter106" title: "106. mmWave Depth Maximum (Far)" - description: "Maximum range of the Y-Axis in mm" + description: "Maximum range of the Y-Axis in cm" required: true preferenceType: number definition: @@ -394,7 +394,9 @@ preferences: definition: options: "0": "Custom (User-defined)" - "1": "Small" - "2": "Medium" - "3": "Large" + "1": "X-Small" + "2": "Small" + "3": "Medium" + "4": "Large" + "5": "X-Large" default: 0 diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index ee38121f2c..c3ab228bc6 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -216,7 +216,7 @@ local function info_changed(driver, device, event, args) else device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) end - end + end end end device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) @@ -224,10 +224,21 @@ local function info_changed(driver, device, event, args) end end +local function configure_illuminance_reporting(device) + local value = math.floor(10000 * math.log10(15) + 1) + device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( + device, + 10, -- Minimum reporting interval (seconds) + 600, -- Maximum reporting interval (seconds) + 15 -- Reportable change (in raw unit values) + )) +end + local do_configure = function(self, device) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then device:refresh() device:configure() + configure_illuminance_reporting(device) device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. @@ -424,7 +435,6 @@ end local function illuminance_attr_handler(driver, device, illuminance, zb_rx) local lux = math.floor(10 ^ ((illuminance.value - 1) / 10000)) - print("illuminance: " .. lux) device:emit_event(capabilities.illuminanceMeasurement.illuminance({value = lux, unit = "lux" })) end From 881378d4e009bf673b4ea0fe3445c00b0c550e86 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Sat, 26 Jul 2025 15:50:37 -0600 Subject: [PATCH 04/32] adjusting lux reporting. remove p117 as mmwave param and updated its options --- .../profiles/inovelli-vzm32-sn.yml | 2 +- .../src/inovelli-vzm32-sn/init.lua | 30 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml index 2220653a2c..ec82041ed9 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -393,7 +393,7 @@ preferences: preferenceType: enumeration definition: options: - "0": "Custom (User-defined)" + "0": "Custom (User defined)" "1": "X-Small" "2": "Small" "3": "Medium" diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index c3ab228bc6..d611973042 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -126,7 +126,6 @@ local key_mmwave_preferences = { "parameter112", "parameter113", "parameter114", - "parameter117", } local function button_to_component(buttonId) @@ -175,6 +174,17 @@ local function contains(array, value) return false end +local function configure_illuminance_reporting(device) + local min_lux_change = 15 + local value = math.floor(10000 * math.log10(min_lux_change) + 1) + device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( + device, + 10, -- Minimum reporting interval (seconds) + 600, -- Maximum reporting interval (seconds) + value -- Reportable change (in raw unit values) + )) +end + local function info_changed(driver, device, event, args) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then local time_diff = 3 @@ -183,6 +193,7 @@ local function info_changed(driver, device, event, args) time_diff = os.difftime(os.time(), last_clock_set_time) end device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) + configure_illuminance_reporting(device) if time_diff > 2 then local preferences = preference_map @@ -224,16 +235,6 @@ local function info_changed(driver, device, event, args) end end -local function configure_illuminance_reporting(device) - local value = math.floor(10000 * math.log10(15) + 1) - device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( - device, - 10, -- Minimum reporting interval (seconds) - 600, -- Maximum reporting interval (seconds) - 15 -- Reportable change (in raw unit values) - )) -end - local do_configure = function(self, device) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then device:refresh() @@ -246,13 +247,6 @@ local do_configure = function(self, device) device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, 21, MFG_CODE)) device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( - device, - 10, -- Minimum reporting interval (seconds) - 60, -- Maximum reporting interval (seconds) - 25 -- Reportable change (in raw unit values) - )) - -- Additional one time configuration if device:supports_capability(capabilities.powerMeter) then -- Divisor and multipler for PowerMeter From bdf36fd6742a151097dbc3c7c810ef887e8f998c Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 1 Aug 2025 13:35:49 -0600 Subject: [PATCH 05/32] initializing values for occupancy, illuminance, and binding to occupancy cluster --- .../zigbee-switch/src/inovelli-vzm32-sn/init.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index d611973042..85bfefb3c1 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -242,6 +242,7 @@ local do_configure = function(self, device) configure_illuminance_reporting(device) device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. + device:send(device_management.build_bind_request(device, OccupancySensing.ID, self.environment_info.hub_zigbee_eui)) -- Retrieve Neutral Setting "Parameter 21" device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, 21, MFG_CODE)) @@ -274,6 +275,12 @@ local device_init = function(self, device) if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil and device:supports_capability(capabilities.energyMeter)then device:emit_event(capabilities.energyMeter.energy(0)) end + if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == nil and device:supports_capability(capabilities.illuminanceMeasurement) then + device:emit_event(capabilities.illuminanceMeasurement.illuminance(0)) + end + if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == nil and device:supports_capability(capabilities.motionSensor) then + device:emit_event(capabilities.motionSensor.motion.active()) + end for _, component in pairs(device.profile.components) do if string.find(component.id, "button") ~= nil then From 7e9d74a65bfd45a5423e0933caca8e7e28c8af8d Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 1 Aug 2025 15:10:11 -0600 Subject: [PATCH 06/32] add ability to reset energy meter --- .../src/inovelli-vzm32-sn/init.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index 85bfefb3c1..10c1cf6dd6 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -31,6 +31,7 @@ local INOVELLI_VZM32_SN_FINGERPRINTS = { local PRIVATE_CLUSTER_ID = 0xFC31 local PRIVATE_CLUSTER_MMWAVE_ID = 0xFC32 local PRIVATE_CMD_NOTIF_ID = 0x01 +local PRIVATE_CMD_ENERGY_RESET_ID = 0x02 local PRIVATE_CMD_SCENE_ID =0x00 local PRIVATE_CMD_MMWAVE_ID = 0x00 local MFG_CODE = 0x122F @@ -439,6 +440,20 @@ local function illuminance_attr_handler(driver, device, illuminance, zb_rx) device:emit_event(capabilities.illuminanceMeasurement.illuminance({value = lux, unit = "lux" })) end +local function handle_resetEnergyMeter(self, device) + device:send(cluster_base.build_manufacturer_specific_command( + device, + PRIVATE_CLUSTER_ID, + PRIVATE_CMD_ENERGY_RESET_ID, + MFG_CODE, + utils.serialize_int(0,1,false,false))) + + -- Read total energy consumption (kWh) + device:send(clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(device)) + -- Alternative power reading from Electrical Measurement cluster + device:send(clusters.ElectricalMeasurement.attributes.ActivePower:read(device)) +end + local inovelli_vzm32_sn = { NAME = "inovelli vzm32-sn handler", lifecycle_handlers = { @@ -481,6 +496,9 @@ local inovelli_vzm32_sn = { }, [capabilities.colorTemperature.ID] = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature + }, + [capabilities.energyMeter.ID] = { + [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = handle_resetEnergyMeter, } }, can_handle = is_inovelli_vzm32_sn From 8b4350021ff2fc934ceb700fd2f99b08217f0fad Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 1 Aug 2025 15:11:08 -0600 Subject: [PATCH 07/32] add ability to reset energy meter --- .../SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index 10c1cf6dd6..93f53835dc 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -448,9 +448,7 @@ local function handle_resetEnergyMeter(self, device) MFG_CODE, utils.serialize_int(0,1,false,false))) - -- Read total energy consumption (kWh) device:send(clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(device)) - -- Alternative power reading from Electrical Measurement cluster device:send(clusters.ElectricalMeasurement.attributes.ActivePower:read(device)) end From 92b4add58027b3b0ebb81b81886d9a006c7ee971 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 13 Aug 2025 15:43:33 -0600 Subject: [PATCH 08/32] adjusting some default parameters and adding ota image notify for firmware update process during certification --- .../profiles/inovelli-vzm32-sn.yml | 18 ++++++++---------- .../src/inovelli-vzm32-sn/init.lua | 11 +++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml index ec82041ed9..97df63f0bc 100644 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml @@ -90,9 +90,9 @@ preferences: preferenceType: enumeration definition: options: - "0": "Dimmer" - "1": "On/Off (default)" - default: 1 + "0": "Dimmer (default)" + "1": "On/Off" + default: 0 - name: "parameter22" title: "22. Aux Switch Type" description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." @@ -100,11 +100,9 @@ preferences: preferenceType: enumeration definition: options: - "0": "None (default)" - "1": "3-Way Dumb Switch" - "2": "3-Way Aux Switch" - "3": "Full Sine Wave (fw 2.11+)" - default: 0 + "0": "None" + "1": "3-Way Aux Switch (default)" + default: 1 - name: "parameter52" title: "52. Smart Bulb Mode" description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." @@ -354,7 +352,7 @@ preferences: "1": "Set Interference Area" "3": "Clear Interference Area" "255": "Factory Reset Module" - default: 1 + default: 3 - name: "parameter112" title: "112. mmWave Detection Sensitivity" description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." @@ -385,7 +383,7 @@ preferences: definition: minimum: 0 maximum: 4294967295 - default: 10 + default: 30 - name: "parameter117" title: "117. Room Size Preset" description: "Allows selection of predefined room dimensions for mmWave sensor processing." diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index 93f53835dc..f883a73d0e 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -186,6 +186,15 @@ local function configure_illuminance_reporting(device) )) end +local function send_ota_image_notify(device) + local PAYLOAD_TYPE = 0x00 + local QUERY_JITTER = 100 + local MFG_CODE = MFG_CODE + local IMAGE_TYPE = 0xFFFF + local NEW_VERSION = 0xFFFFFFFF + device:send(OTAUpgrade.commands.ImageNotify(device, PAYLOAD_TYPE, QUERY_JITTER, MFG_CODE, IMAGE_TYPE, NEW_VERSION)) +end + local function info_changed(driver, device, event, args) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then local time_diff = 3 @@ -241,6 +250,8 @@ local do_configure = function(self, device) device:refresh() device:configure() configure_illuminance_reporting(device) + send_ota_image_notify(device) + device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. device:send(device_management.build_bind_request(device, OccupancySensing.ID, self.environment_info.hub_zigbee_eui)) From 6d36aeebf48d9b718210ab25e676e87426facc8d Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 13 Aug 2025 15:53:35 -0600 Subject: [PATCH 09/32] adding missing OTAUpgrade declaration --- drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua index f883a73d0e..a8657397b6 100644 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua @@ -21,6 +21,7 @@ local capabilities = require "st.capabilities" local device_management = require "st.zigbee.device_management" local configurations = require "configurations" local OccupancySensing = clusters.OccupancySensing +local OTAUpgrade = clusters.OTAUpgrade local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" From 1da18de1aecbae47b6049c9532cea986d57aa0de Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 29 Aug 2025 13:52:53 -0600 Subject: [PATCH 10/32] Inovelli - adding support for vzw32-sn for wwst --- .../SmartThings/zwave-switch/fingerprints.yml | 6 + .../inovelli-mmwave-dimmer-vzw32-sn.yml | 449 ++++++++++++++++++ .../profiles/rgbw-bulb-2700K-6500K.yml | 22 + drivers/SmartThings/zwave-switch/src/init.lua | 1 + .../src/inovelli-vzw32-sn/init.lua | 408 ++++++++++++++++ .../zwave-switch/src/preferences.lua | 42 ++ 6 files changed, 928 insertions(+) create mode 100644 drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml create mode 100644 drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml create mode 100644 drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 7154dc11c7..08147feae9 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -56,6 +56,12 @@ zwaveManufacturer: productType: 0x0003 productId: 0x0001 deviceProfileName: inovelli-dimmer + - id: "Inovelli/VZW32-SN" + deviceLabel: Inovelli mmWave Dimmer Red Series + manufacturerId: 0x031E + productType: 0x0017 + productId: 0x0001 + deviceProfileName: inovelli-mmwave-dimmer-vzw32-sn - id: "010F/0403" deviceLabel: Fibaro Single Switch manufacturerId: 0x010F diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml new file mode 100644 index 0000000000..dd94afb2a9 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -0,0 +1,449 @@ +name: inovelli-mmwave-dimmer-vzw32-sn +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + config: + values: + - key: "illuminance.value" + range: [0, 5000] + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: "eventflute36860.log" + version: 1 + - id: configuration + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +- id: button1 + label: Down Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +- id: button2 + label: Up Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +- id: button3 + label: Config Button + capabilities: + - id: button + version: 1 + categories: + - name: RemoteController +preferences: + - name: "notificationChild" + title: "Add Child Device - Notification" + description: "Create Separate Child Device for Notification Control" + required: false + preferenceType: boolean + definition: + default: false + - name: "notificationType" + title: "Notification Effect" + description: "This is the notification effect used by the notification child device" + required: false + preferenceType: enumeration + definition: + options: + "255": "Clear" + "1": "Solid" + "2": "Fast Blink" + "3": "Slow Blink" + "4": "Pulse" + "5": "Chase" + "6": "Open/Close" + "7": "Small-to-Big" + "8": "Aurora" + "9": "Slow Falling" + "10": "Medium Falling" + "11": "Fast Falling" + "12": "Slow Rising" + "13": "Medium Rising" + "14": "Fast Rising" + "15": "Medium Blink" + "16": "Slow Chase" + "17": "Fast Chase" + "18": "Fast Siren" + "19": "Slow Siren" + default: 1 + - name: "parameter158" + title: "158. Switch Mode" + description: "Use as a Dimmer or an On/Off switch" + required: true + preferenceType: enumeration + definition: + options: + "0": "Dimmer (default)" + "1": "On/Off" + default: 0 + - name: "parameter22" + title: "22. Aux Switch Type" + description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: true + preferenceType: enumeration + definition: + options: + "0": "None" + "1": "3-Way Aux Switch (default)" + default: 1 + - name: "parameter52" + title: "52. Smart Bulb Mode" + description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled (default)" + "1": "Smart Bulb Mode" + default: 0 + - name: "parameter1" + title: "1. Dimming Speed (Remote)" + description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + Default=25 (2500ms or 2.5s)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 25 + - name: "parameter2" + title: "2. Dimming Speed (Local)" + description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter3" + title: "3. Ramp Rate (Remote)" + description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 1)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter4" + title: "4. Ramp Rate (Local)" + description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. + (i.e 25 = 2500ms or 2.5s) Default=255 (Sync with parameter 3)" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 255 + default: 255 + - name: "parameter9" + title: "9. Minimum Level" + description: "The minimum level that the light can be dimmed. Useful when the user has a light that does not turn on or flickers at a lower level." + required: true + preferenceType: number + definition: + minimum: 1 + maximum: 99 + default: 1 + - name: "parameter10" + title: "10. Maximum Level" + description: "The maximum level that the light can be dimmed. Useful when the user wants to limit the maximum brighness." + required: true + preferenceType: number + definition: + minimum: 2 + maximum: 100 + default: 100 + - name: "parameter15" + title: "15. Level After Power Restored" + description: "The level the switch will return to when power is restored after power failure. + 0=Off + 1-100=Set Level + 101=Use previous level." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 101 + default: 101 + - name: "parameter18" + title: "18. Active Power Reports" + description: "Power level change that will result in a new power report being sent. + 0 = Disabled + 1-32767 = 0.1W-3276.7W." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 100 + - name: "parameter19" + title: "19. Periodic Power & Energy Reports" + description: "Time period between consecutive power & energy reports being sent (in seconds). The timer is reset after each report is sent." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 3600 + - name: "parameter20" + title: "20. Active Energy Reports" + description: "Energy level change that will result in a new energy report being sent. + 0 = Disabled + 1-32767 = 0.01kWh-327.67kWh." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 100 + - name: "parameter50" + title: "50. Button Press Delay" + description: "Adjust the delay used in scene control. 0=no delay (disables multi-tap scenes), 1=100ms, 2=200ms, 3=300ms, etc." + required: true + preferenceType: enumeration + definition: + options: + "0": "0ms" + "1": "100ms" + "2": "200ms" + "3": "300ms" + "4": "400ms" + "5": "500ms (default)" + "6": "600ms" + "7": "700ms" + "8": "800ms" + "9": "900ms" + default: 5 + - name: "parameter95" + title: "95. LED Indicator Color (w/On)" + description: "Set the color of the Full LED Indicator when the load is on." + required: true + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter96" + title: "96. LED Indicator Color (w/Off)" + description: "Set the color of the Full LED Indicator when the load is off." + required: true + preferenceType: enumeration + definition: + options: + "0": "Red" + "7": "Orange" + "28": "Lemon" + "64": "Lime" + "85": "Green" + "106": "Teal" + "127": "Cyan" + "148": "Aqua" + "170": "Blue (default)" + "190": "Violet" + "212": "Magenta" + "234": "Pink" + "255": "White" + default: 170 + - name: "parameter97" + title: "97. LED Indicator Intensity (w/On)" + description: "Set the intensity of the Full LED Indicator when the load is on." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 50 + - name: "parameter98" + title: "98. LED Indicator Intensity (w/Off)" + description: "Set the intensity of the Full LED Indicator when the load is off." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 100 + default: 5 + - name: "parameter101" + title: "101. mmWave Height Minimum (Floor)" + description: "Minimum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -300 + - name: "parameter102" + title: "102. mmWave Height Maximum (Ceiling)" + description: "Maximum range of the Z-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 300 + - name: "parameter103" + title: "103. mmWave Width Minimum (Left)" + description: "Minimum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: -600 + - name: "parameter104" + title: "104. mmWave Width Maximum (Right)" + description: "Maximum range of the X-Axis in cm" + required: true + preferenceType: number + definition: + minimum: -600 + maximum: 600 + default: 600 + - name: "parameter105" + title: "105. mmWave Depth Minimum (Near)" + description: "Minimum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 0 + - name: "parameter106" + title: "106. mmWave Depth Maximum (Far)" + description: "Maximum range of the Y-Axis in cm" + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 600 + default: 600 + - name: "parameter108" + title: "108. mmWave Stay Life" + description: "Optimize detection in areas where user may be still for a long time. The delay time of the stay area is set to 50ms when it is set to 1, to 1 second when it is set to 20, and the default value is 300, that is, 15 seconds" + required: false + preferenceType: number + definition: + minimum: 0 + maximum: 4294967295 + default: 300 + - name: "parameter110" + title: "Light On Presence Behavior" + description: "When presence is detected, choose how to control the light load" + required: true + preferenceType: enumeration + definition: + options: + "0": "Disabled" + "1": "Auto On/Off when occupied (default)" + "2": "Auto Off when vacant" + "3": "Auto On when occupied" + "4": "Auto On/Off when Vacant" + "5": "Auto On when Vacant" + "6": "Auto Off when Occupied" + default: 1 + - name: "parameter111" + title: "111. mmWave Control Commands" + description: "Advanced commands to send to the mmWave Module (Please see documentation)" + required: false + preferenceType: enumeration + definition: + options: + "1": "Set Interference Area" + "3": "Clear Interference Area" + "254": "Get mmWave FW Version (see history)" + "255": "Factory Reset Module" + default: 3 + - name: "parameter112" + title: "112. mmWave Sensitivity" + description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." + required: false + preferenceType: enumeration + definition: + options: + "0": "Low" + "1": "Medium" + "2": "High (default)" + default: 2 + - name: "parameter113" + title: "113. mmWave Detection Delay" + description: "The time from detecting a person to triggering an action. 0-Low (5s), 1-Medium (1s), 2-Fast (0.2s)." + required: false + preferenceType: enumeration + definition: + options: + "0": "5 seconds" + "1": "1 second" + "2": "0.2 seconds (default)" + default: 2 + - name: "parameter114" + title: "mmWave Detection Timeout" + description: "Adjust the timeout after presence is no longer detected. After the timeout the load will turn off." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 4294967296 + default: 30 + - name: "parameter117" + title: "117. Room Size" + description: "Sets the x, y, and z dimensions of the room for mmWave detection. Changing this parameter will update parameters 101-106 to reflect the preset." + required: true + preferenceType: enumeration + definition: + options: + "0": "Custom" + "1": "X-Small" + "2": "Small" + "3": "Medium" + "4": "Large" + "5": "X-Large" + default: 0 + - name: "parameter118" + title: "Lux Threshold" + description: "Threshold to send lux report." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 20 + - name: "parameter119" + title: "Lux Interval" + description: "Interval, in seconds, to send Lux reports." + required: true + preferenceType: number + definition: + minimum: 0 + maximum: 32767 + default: 600 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml new file mode 100644 index 0000000000..8878a04a99 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml @@ -0,0 +1,22 @@ +name: rgbw-bulb-2700K-6500K +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [ 2700, 6500 ] + - id: colorControl + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/zwave-switch/src/init.lua b/drivers/SmartThings/zwave-switch/src/init.lua index 3acae8ffe0..a278ee0930 100644 --- a/drivers/SmartThings/zwave-switch/src/init.lua +++ b/drivers/SmartThings/zwave-switch/src/init.lua @@ -145,6 +145,7 @@ local driver_template = { sub_drivers = { lazy_load_if_possible("eaton-accessory-dimmer"), lazy_load_if_possible("inovelli-LED"), + lazy_load_if_possible("inovelli-vzw32-sn"), lazy_load_if_possible("dawon-smart-plug"), lazy_load_if_possible("inovelli-2-channel-smart-plug"), lazy_load_if_possible("zwave-dual-switch"), diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua new file mode 100644 index 0000000000..8b0fcd1394 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -0,0 +1,408 @@ +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +--- @type st.zwave.CommandClass.Version +local Version = (require "st.zwave.CommandClass.Version")({ version=1 }) +--- @type st.zwave.CommandClass.Association +local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) +--- @type st.zwave.constants +local constants = require "st.zwave.constants" +--- @type st.zwave.CommandClass.SwitchBinary +local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Basic +local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) +--- @type st.zwave.CommandClass.SwitchMultilevel +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +local preferencesMap = require "preferences" + +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) + +--- @type st.utils +local utils = require "st.utils" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +local log = require "log" +local st_device = require "st.device" + +--- @type st.zwave.CommandClass.CentralScene +local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) + +local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" + +local NOTIFICATION_PARAMETER_NUMBER = 99 + +local INOVELLI_VZW32_SN_FINGERPRINTS = { + { mfr = 0x031E, prod = 0x0017, model = 0x0001 } -- Inovelli VZW32-SN +} + +--- Map component to end_points(channels) +--- +--- @param device st.zwave.Device +--- @param component_id string ID +--- @return table dst_channels destination channels e.g. {2} for Z-Wave channel 2 or {} for unencapsulated +local function component_to_endpoint(device, component_id) + local ep_num = component_id:match("switch(%d)") + return { ep_num and tonumber(ep_num) } +end + +--- Map end_point(channel) to Z-Wave endpoint 9 channel) +--- +--- @param device st.zwave.Device +--- @param ep number the endpoint(Z-Wave channel) ID to find the component for +--- @return string the component ID the endpoint matches to +local function endpoint_to_component(device, ep) + local switch_comp = string.format("switch%d", ep) + if device.profile.components[switch_comp] ~= nil then + return switch_comp + else + return "main" + end +end + +local function button_to_component(buttonId) + if buttonId > 0 then + return string.format("button%d", buttonId) + end +end + +local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return utils.round(value / 100 * 255) + end +end + +local preferences_to_numeric_value = function(new_value) + local numeric = tonumber(new_value) + if numeric == nil then -- in case the value is boolean + numeric = new_value and 1 or 0 + end + return numeric +end + +local preferences_calculate_parameter = function(new_value, type, number) + local numeric = tonumber(new_value) + if type == 4 and new_value > 2147483647 then + return ((4294967296 - new_value) * -1) + elseif type == 2 and new_value > 32767 then + return ((65536 - new_value) * -1) + elseif type == 1 and new_value > 127 then + return ((256 - new_value) * -1) + else + return new_value + end +end + +local function add_child(driver,parent,profile,child_type) + local child_metadata = { + type = "EDGE_CHILD", + label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), + profile = profile, + parent_device_id = parent.id, + parent_assigned_child_key = child_type, + vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) + } + driver:try_create_device(child_metadata) +end + +local function initialize(device) + if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == null then + device:emit_event(capabilities.illuminanceMeasurement.illuminance(0)) + end + if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == null then + device:emit_event(capabilities.motionSensor.motion.active()) + end + if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == null then + device:emit_event(capabilities.powerMeter.power(0)) + end + if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == null then + device:emit_event(capabilities.energyMeter.energy(0)) + end + if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == null then + device:emit_event(capabilities.switchLevel.level(0)) + end + + for _, component in pairs(device.profile.components) do + if string.find(component.id, "button") ~= nil then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + { visibility = { displayed = false } } + ) + ) + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) + ) + end + end +end + +local function getNotificationValue(device, value) + local notificationValue = 0 + local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 + local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) + local effect = device:get_parent_device().preferences.notificationType or 1 + notificationValue = notificationValue + (effect*16777216) + notificationValue = notificationValue + (huePercentToValue(value or color)*65536) + notificationValue = notificationValue + (level*256) + notificationValue = notificationValue + (255*1) + return notificationValue +end + +local function set_color(driver, device, command) + device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) + device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=NOTIFICATION_PARAMETER_NUMBER, + configuration_value=getNotificationValue(device), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) +end + +local function set_color_temperature(driver, device, command) + device:emit_event(capabilities.colorControl.hue(100)) + device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=NOTIFICATION_PARAMETER_NUMBER, + configuration_value=getNotificationValue(device, 100), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) +end + +local function switch_level_set(driver, device, command) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local level = utils.round(command.args.level) + level = utils.clamp_value(level, 0, 99) + + device:emit_event(level > 0 and capabilities.switch.switch.on() or capabilities.switch.switch.off()) + + device:send(SwitchMultilevel:Set({ value=level, duration=command.args.rate or "default" })) + + device.thread:call_with_delay(3, function(d) + device:send(SwitchMultilevel:Get({})) + end) + else + device:emit_event(capabilities.switchLevel.level(command.args.level)) + device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=NOTIFICATION_PARAMETER_NUMBER, + configuration_value=getNotificationValue(device), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + end +end + +local function can_handle_inovelli_vzw32(opts, driver, device, ...) + for _, fingerprint in ipairs(INOVELLI_VZW32_SN_FINGERPRINTS) do + if device:id_match(fingerprint.mfr, fingerprint.prod, fingerprint.model) then + local subdriver = require("inovelli-vzw32-sn") + return true, subdriver + end + end + return false +end + +local device_init = function(self, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) + device:send(Version:Get({})) + initialize(device) + else + device:emit_event(capabilities.colorControl.hue(1)) + device:emit_event(capabilities.colorControl.saturation(1)) + device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) + device:emit_event(capabilities.switchLevel.level(100)) + device:emit_event(capabilities.switch.switch("off")) + end +end + +local function info_changed(driver, device, event, args) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + local time_diff = 3 + local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) + if last_clock_set_time ~= nil then + time_diff = os.difftime(os.time(), last_clock_set_time) + end + device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) + if time_diff > 2 then + local preferences = preferencesMap.get_device_parameters(device) + if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then + if not device:get_child_by_parent_assigned_key('notification') then + add_child(driver,device,'rgbw-bulb-2700K-6500K','notificaiton') + end + end + + for id, value in pairs(device.preferences) do + if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then + local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) + device:send(Configuration:Set({parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value})) + end + end + device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) + else + log.info("info_changed running more than once. Cancelling this run. Time diff: " .. time_diff) + end + end +end + +local function switch_set_on_off_handler(value) + return function(driver, device, command) + + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Basic:Set({ value = value })) + device.thread:call_with_delay(3, function(d) + device:send(SwitchMultilevel:Get({})) + end) + else + device:emit_event(capabilities.switch.switch(value == 0 and "off" or "on")) + local dev = device:get_parent_device() + local config = Configuration:Set({ + parameter_number=NOTIFICATION_PARAMETER_NUMBER, + configuration_value=(value == 0 and 0 or getNotificationValue(device)), + size=4 + }) + local send_configuration = function() + dev:send(config) + end + device.thread:call_with_delay(1,send_configuration) + end + end +end + +local function version_report(driver, device, cmd) + log.info("Firmware Version: "..cmd.args.firmware_0_version.."."..cmd.args.firmware_0_sub_version) +end + +local function notification_report_handler(self, device, cmd) + local event + if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then + if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then + event = cmd.args.notification_status == 0 and capabilities.motionSensor.motion.inactive() or capabilities.motionSensor.motion.active() + elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then + if #cmd.args.event_parameter >= 1 and string.byte(cmd.args.event_parameter, 1) == 8 then + event = capabilities.motionSensor.motion.inactive() + else + event = capabilities.tamperAlert.tamper.clear() + end + end + end + if (event ~= nil) then + device:emit_event(event) + end +end + +local map_key_attribute_to_capability = { + [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, + [CentralScene.key_attributes.KEY_RELEASED] = capabilities.button.button.held, + [CentralScene.key_attributes.KEY_HELD_DOWN] = capabilities.button.button.down_hold, + [CentralScene.key_attributes.KEY_PRESSED_2_TIMES] = capabilities.button.button.pushed_2x, + [CentralScene.key_attributes.KEY_PRESSED_3_TIMES] = capabilities.button.button.pushed_3x, + [CentralScene.key_attributes.KEY_PRESSED_4_TIMES] = capabilities.button.button.pushed_4x, + [CentralScene.key_attributes.KEY_PRESSED_5_TIMES] = capabilities.button.button.pushed_5x, +} + +local function central_scene_notification_handler(self, device, cmd) + if ( cmd.args.scene_number ~= nil and cmd.args.scene_number ~= 0 ) then + local button_number = cmd.args.scene_number + local capability_attribute = map_key_attribute_to_capability[cmd.args.key_attributes] + local additional_fields = { + state_change = true + } + + local event + if capability_attribute ~= nil then + event = capability_attribute(additional_fields) + end + + if event ~= nil then + -- device reports scene notifications from endpoint 0 (main) but central scene events have to be emitted for button components: 1,2,3 + local comp = device.profile.components[button_to_component(button_number)] + if comp ~= nil then + device:emit_component_event(comp, event) + end + end + end +end + +local function basic_and_switch_binary_report_handler(driver, device, cmd) + local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value + local event = value == SwitchBinary.value.OFF_DISABLE and capabilities.switch.switch.off() or capabilities.switch.switch.on() + device:emit_event_for_endpoint(cmd.src_channel, event) +end + +local function onoff_level_report_handler(self, device, cmd) + local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value + device:emit_event(value == SwitchMultilevel.value.OFF_DISABLE and capabilities.switch.switch.off() or capabilities.switch.switch.on()) + if value >= 0 then + device:emit_event(capabilities.switchLevel.level(value >= 99 and 100 or value)) + end +end + +------------------------------------------------------------------------------------------- +-- Register message handlers and run driver +------------------------------------------------------------------------------------------- +local inovelli_vzw32_sn = { + NAME = "inovelli vzw32-sn handler", + lifecycle_handlers = { + init = device_init, + infoChanged = info_changed, + }, + + zwave_handlers = { + [cc.CONFIGURATION] = { + [Configuration.REPORT] = function() end -- Empty function since configuration_report was unused + }, + [cc.CENTRAL_SCENE] = { + [CentralScene.NOTIFICATION] = central_scene_notification_handler + }, + [cc.VERSION] = { + [Version.REPORT] = version_report + }, + [cc.BASIC] = { + [Basic.REPORT] = basic_and_switch_binary_report_handler + }, + [cc.SWITCH_MULTILEVEL] = { + [SwitchMultilevel.REPORT] = onoff_level_report_handler + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.switch.on.NAME] = switch_set_on_off_handler(SwitchBinary.value.ON_ENABLE), + [capabilities.switch.switch.off.NAME] = switch_set_on_off_handler(SwitchBinary.value.OFF_DISABLE) + }, + [capabilities.colorControl.ID] = { + [capabilities.colorControl.commands.setColor.NAME] = set_color + }, + [capabilities.colorTemperature.ID] = { + [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_set + } + }, + can_handle = can_handle_inovelli_vzw32 +} + +return inovelli_vzw32_sn \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 73ed5e52f3..5c67321cf4 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -70,6 +70,48 @@ local devices = { switchType = {parameter_number = 22, size = 1} } }, + INOVELLI_VZW32_SN = { + MATCHING_MATRIX = { + mfrs = 0x031E, + product_types = {0x0017}, + product_ids = 0x0001 + }, + PARAMETERS = { + parameter158 = {parameter_number = 158, size = 1}, + parameter22 = {parameter_number = 22, size = 1}, + parameter52 = {parameter_number = 52, size = 1}, + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter9 = {parameter_number = 9, size = 1}, + parameter10 = {parameter_number = 10, size = 1}, + parameter15 = {parameter_number = 15, size = 1}, + parameter18 = {parameter_number = 18, size = 1}, + parameter19 = {parameter_number = 19, size = 2}, + parameter20 = {parameter_number = 20, size = 2}, + parameter50 = {parameter_number = 50, size = 1}, + parameter95 = {parameter_number = 95, size = 1}, + parameter96 = {parameter_number = 96, size = 1}, + parameter97 = {parameter_number = 97, size = 1}, + parameter98 = {parameter_number = 98, size = 1}, + parameter101 = {parameter_number = 101, size = 2}, + parameter102 = {parameter_number = 102, size = 2}, + parameter103 = {parameter_number = 103, size = 2}, + parameter104 = {parameter_number = 104, size = 2}, + parameter105 = {parameter_number = 105, size = 2}, + parameter106 = {parameter_number = 106, size = 2}, + parameter108 = {parameter_number = 108, size = 4}, + parameter110 = {parameter_number = 110, size = 2}, + parameter111 = {parameter_number = 111, size = 1}, + parameter112 = {parameter_number = 112, size = 1}, + parameter113 = {parameter_number = 113, size = 1}, + parameter114 = {parameter_number = 114, size = 4}, + parameter117 = {parameter_number = 117, size = 1}, + parameter118 = {parameter_number = 118, size = 2}, + parameter119 = {parameter_number = 119, size = 2} + } + }, QUBINO_FLUSH_DIMMER = { MATCHING_MATRIX = { mfrs = 0x0159, From 10017c0f445d54a4098fd12c3ae7e8a0dcad907f Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 29 Aug 2025 14:30:52 -0600 Subject: [PATCH 11/32] accidentally included some vzm32-sn (zigbee) changes in this branch. Removing them --- .../zigbee-switch/fingerprints.yml | 5 - .../profiles/inovelli-vzm32-sn.yml | 400 -------------- .../SmartThings/zigbee-switch/src/init.lua | 4 +- .../src/inovelli-vzm32-sn/init.lua | 517 ------------------ 4 files changed, 1 insertion(+), 925 deletions(-) delete mode 100644 drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml delete mode 100644 drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index b6bd96f0eb..d45c0aa13c 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -2309,11 +2309,6 @@ zigbeeManufacturer: manufacturer: Inovelli model: VZM31-SN deviceProfileName: inovelli-vzm31-sn - - id: "Inovelli/VZM32-SN" - deviceLabel: "Inovelli mmWave Dimmer Blue Series" - manufacturer: Inovelli - model: VZM32-SN - deviceProfileName: inovelli-vzm32-sn - id: "LAISIAO/BATH" deviceLabel: Laisiao Bathroom Heater manufacturer: LAISIAO diff --git a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml b/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml deleted file mode 100644 index 97df63f0bc..0000000000 --- a/drivers/SmartThings/zigbee-switch/profiles/inovelli-vzm32-sn.yml +++ /dev/null @@ -1,400 +0,0 @@ -name: inovelli-vzm32-sn -components: - - id: main - capabilities: - - id: switch - version: 1 - - id: switchLevel - version: 1 - - id: motionSensor - version: 1 - - id: illuminanceMeasurement - version: 1 - config: - values: - - key: "illuminance.value" - range: [0, 5000] - - id: powerMeter - version: 1 - - id: energyMeter - version: 1 - - id: refresh - version: 1 - - id: configuration - version: 1 - - id: firmwareUpdate - version: 1 - categories: - - name: Switch - - id: button1 - label: Down Button - capabilities: - - id: button - version: 1 - categories: - - name: RemoteController - - id: button2 - label: Up Button - capabilities: - - id: button - version: 1 - categories: - - name: RemoteController - - id: button3 - label: Config Button - capabilities: - - id: button - version: 1 - categories: - - name: RemoteController -preferences: - - name: "notificationChild" - title: "Add Child Device - Notification" - description: "Create Separate Child Device for Notification Control" - required: false - preferenceType: boolean - definition: - default: false - - name: "notificationType" - title: "Notification Effect" - description: "This is the notification effect used by the notification child device" - required: false - preferenceType: enumeration - definition: - options: - "255": "Clear" - "1": "Solid" - "2": "Fast Blink" - "3": "Slow Blink" - "4": "Pulse" - "5": "Chase" - "6": "Open/Close" - "7": "Small-to-Big" - "8": "Aurora" - "9": "Slow Falling" - "10": "Medium Falling" - "11": "Fast Falling" - "12": "Slow Rising" - "13": "Medium Rising" - "14": "Fast Rising" - "15": "Medium Blink" - "16": "Slow Chase" - "17": "Fast Chase" - "18": "Fast Siren" - "19": "Slow Siren" - default: 1 - - name: "parameter258" - title: "258. Switch Mode" - description: "Use as a Dimmer or an On/Off switch" - required: false - preferenceType: enumeration - definition: - options: - "0": "Dimmer (default)" - "1": "On/Off" - default: 0 - - name: "parameter22" - title: "22. Aux Switch Type" - description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." - required: false - preferenceType: enumeration - definition: - options: - "0": "None" - "1": "3-Way Aux Switch (default)" - default: 1 - - name: "parameter52" - title: "52. Smart Bulb Mode" - description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." - required: false - preferenceType: enumeration - definition: - options: - "0": "Disabled (default)" - "1": "Smart Bulb Mode" - default: 0 - - name: "parameter1" - title: "1. Dimming Speed (Remote)" - description: "This changes the speed that the light dims up when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. - Default=25 (2500ms or 2.5s)" - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 126 - default: 25 - - name: "parameter2" - title: "2. Dimming Speed (Local)" - description: "This changes the speed that the light dims up when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. - (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 127 - default: 127 - - name: "parameter3" - title: "3. Ramp Rate (Remote)" - description: "This changes the speed that the light turns on when controlled from the hub. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. - (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 1)" - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 127 - default: 127 - - name: "parameter4" - title: "4. Ramp Rate (Local)" - description: "This changes the speed that the light turns on when controlled at the switch. A setting of '0' turns the light immediately on. Increasing the value slows down the transition speed. Value is multiplied by 100ms. - (i.e 25 = 2500ms or 2.5s) Default=127 (Sync with parameter 3)" - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 127 - default: 127 - - name: "parameter9" - title: "9. Minimum Level" - description: "The minimum level that the light can be dimmed. Useful when the user has a light that does not turn on or flickers at a lower level." - required: false - preferenceType: number - definition: - minimum: 1 - maximum: 99 - default: 1 - - name: "parameter10" - title: "10. Maximum Level" - description: "The maximum level that the light can be dimmed. Useful when the user wants to limit the maximum brighness." - required: false - preferenceType: number - definition: - minimum: 2 - maximum: 100 - default: 100 - - name: "parameter11" - title: "11. Invert Switch" - description: "Inverts the orientation of the switch. Useful when the switch is installed upside down. Essentially up becomes down and down becomes up." - required: false - preferenceType: enumeration - definition: - options: - "0": "No (default)" - "1": "Yes" - default: 0 - - name: "parameter15" - title: "15. Level After Power Restored" - description: "The level the switch will return to when power is restored after power failure. - 0=Off - 1-100=Set Level - 101=Use previous level." - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 101 - default: 101 - - name: "parameter17" - title: "17. Load Level Indicator Timeout" - description: "Shows the level that the load is at for x number of seconds after the load is adjusted and then returns to the Default LED state." - required: false - preferenceType: enumeration - definition: - options: - "0": "Do not display Load Level" - "1": "1 Second" - "2": "2 Seconds" - "3": "3 Seconds" - "4": "4 Seconds" - "5": "5 Seconds" - "6": "6 Seconds" - "7": "7 Seconds" - "8": "8 Seconds" - "9": "9 Seconds" - "10": "10 Seconds" - "11": "Display Load Level with no timeout" - default: 11 - - name: "parameter95" - title: "95. LED Indicator Color (w/On)" - description: "Set the color of the Full LED Indicator when the load is on." - required: false - preferenceType: enumeration - definition: - options: - "0": "Red" - "7": "Orange" - "28": "Lemon" - "64": "Lime" - "85": "Green" - "106": "Teal" - "127": "Cyan" - "148": "Aqua" - "170": "Blue (default)" - "190": "Violet" - "212": "Magenta" - "234": "Pink" - "255": "White" - default: 170 - - name: "parameter96" - title: "96. LED Indicator Color (w/Off)" - description: "Set the color of the Full LED Indicator when the load is off." - required: false - preferenceType: enumeration - definition: - options: - "0": "Red" - "7": "Orange" - "28": "Lemon" - "64": "Lime" - "85": "Green" - "106": "Teal" - "127": "Cyan" - "148": "Aqua" - "170": "Blue (default)" - "190": "Violet" - "212": "Magenta" - "234": "Pink" - "255": "White" - default: 170 - - name: "parameter97" - title: "97. LED Indicator Intensity (w/On)" - description: "Set the intensity of the Full LED Indicator when the load is on." - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 100 - default: 50 - - name: "parameter98" - title: "98. LED Indicator Intensity (w/Off)" - description: "Set the intensity of the Full LED Indicator when the load is off." - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 100 - default: 5 - - name: "parameter101" - title: "101. mmWave Height Minimum (Floor)" - description: "Minimum range of the Z-Axis in cm" - required: true - preferenceType: number - definition: - minimum: -600 - maximum: 600 - default: -300 - - name: "parameter102" - title: "102. mmWave Height Maximum (Ceiling)" - description: "Maximum range of the Z-Axis in cm" - required: true - preferenceType: number - definition: - minimum: -600 - maximum: 600 - default: 300 - - name: "parameter103" - title: "103. mmWave Width Minimum (Left)" - description: "Minimum range of the X-Axis in cm" - required: true - preferenceType: number - definition: - minimum: -600 - maximum: 600 - default: -600 - - name: "parameter104" - title: "104. mmWave Width Maximum (Right)" - description: "Maximum range of the X-Axis in cm" - required: true - preferenceType: number - definition: - minimum: -600 - maximum: 600 - default: 600 - - name: "parameter105" - title: "105. mmWave Depth Minimum (Near)" - description: "Minimum range of the Y-Axis in cm" - required: true - preferenceType: number - definition: - minimum: 0 - maximum: 600 - default: 0 - - name: "parameter106" - title: "106. mmWave Depth Maximum (Far)" - description: "Maximum range of the Y-Axis in cm" - required: true - preferenceType: number - definition: - minimum: 0 - maximum: 600 - default: 600 - - name: "parameter110" - title: "110. Light On Presence Behavior" - description: "When presence is detected, choose how to control the light load" - required: true - preferenceType: enumeration - definition: - options: - "0": "Disabled" - "1": "Auto On/Off when occupied (default)" - "2": "Auto Off when vacant" - "3": "Auto On when occupied" - "4": "Auto On/Off when Vacant" - "5": "Auto On when Vacant" - "6": "Auto Off when Occupied" - default: 1 - - name: "parameter111" - title: "111. mmWave Control Commands" - description: "Advanced commands to send to the mmWave Module (Please see documentation)" - required: false - preferenceType: enumeration - definition: - options: - "1": "Set Interference Area" - "3": "Clear Interference Area" - "255": "Factory Reset Module" - default: 3 - - name: "parameter112" - title: "112. mmWave Detection Sensitivity" - description: "Adjust the sensitivity of the mmWave sensor. 0-Low, 1-Medium, 2-High." - required: false - preferenceType: enumeration - definition: - options: - "0": "Low" - "1": "Medium" - "2": "High (default)" - default: 2 - - name: "parameter113" - title: "113. mmWave Detection Delay" - description: "The time from detecting a person to triggering an action. 0-Low (5s), 1-Medium (1s), 2-Fast (0.2s)." - required: false - preferenceType: enumeration - definition: - options: - "0": "5 seconds" - "1": "1 second" - "2": "0.2 seconds (default)" - default: 2 - - name: "parameter114" - title: "114. mmWave Time Out" - description: "Adjust the timeout after presence is no longer detected. After the timeout the load will turn off." - required: false - preferenceType: number - definition: - minimum: 0 - maximum: 4294967295 - default: 30 - - name: "parameter117" - title: "117. Room Size Preset" - description: "Allows selection of predefined room dimensions for mmWave sensor processing." - required: false - preferenceType: enumeration - definition: - options: - "0": "Custom (User defined)" - "1": "X-Small" - "2": "Small" - "3": "Medium" - "4": "Large" - "5": "X-Large" - default: 0 diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 5d5b418ba4..85efef5e11 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -138,8 +138,7 @@ local zigbee_switch_driver_template = { capabilities.colorTemperature, capabilities.powerMeter, capabilities.energyMeter, - capabilities.motionSensor, - capabilities.illuminanceMeasurement, + capabilities.motionSensor }, sub_drivers = { lazy_load_if_possible("hanssem"), @@ -165,7 +164,6 @@ local zigbee_switch_driver_template = { lazy_load_if_possible("robb"), lazy_load_if_possible("wallhero"), lazy_load_if_possible("inovelli-vzm31-sn"), - lazy_load_if_possible("inovelli-vzm32-sn"), lazy_load_if_possible("laisiao"), lazy_load_if_possible("tuya-multi") }, diff --git a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua b/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua deleted file mode 100644 index a8657397b6..0000000000 --- a/drivers/SmartThings/zigbee-switch/src/inovelli-vzm32-sn/init.lua +++ /dev/null @@ -1,517 +0,0 @@ --- 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 clusters = require "st.zigbee.zcl.clusters" -local cluster_base = require "st.zigbee.cluster_base" -local utils = require "st.utils" -local st_device = require "st.device" -local data_types = require "st.zigbee.data_types" -local capabilities = require "st.capabilities" -local device_management = require "st.zigbee.device_management" -local configurations = require "configurations" -local OccupancySensing = clusters.OccupancySensing -local OTAUpgrade = clusters.OTAUpgrade - -local LATEST_CLOCK_SET_TIMESTAMP = "latest_clock_set_timestamp" - -local INOVELLI_VZM32_SN_FINGERPRINTS = { - { mfr = "Inovelli", model = "VZM32-SN" } -} - -local PRIVATE_CLUSTER_ID = 0xFC31 -local PRIVATE_CLUSTER_MMWAVE_ID = 0xFC32 -local PRIVATE_CMD_NOTIF_ID = 0x01 -local PRIVATE_CMD_ENERGY_RESET_ID = 0x02 -local PRIVATE_CMD_SCENE_ID =0x00 -local PRIVATE_CMD_MMWAVE_ID = 0x00 -local MFG_CODE = 0x122F - -local preference_map = { - parameter258 = {parameter_number = 258, size = data_types.Boolean}, - parameter22 = {parameter_number = 22, size = data_types.Uint8}, - parameter52 = {parameter_number = 52, size = data_types.Boolean}, - parameter1 = {parameter_number = 1, size = data_types.Uint8}, - parameter2 = {parameter_number = 2, size = data_types.Uint8}, - parameter3 = {parameter_number = 3, size = data_types.Uint8}, - parameter4 = {parameter_number = 4, size = data_types.Uint8}, - parameter9 = {parameter_number = 9, size = data_types.Uint8}, - parameter10 = {parameter_number = 10, size = data_types.Uint8}, - parameter11 = {parameter_number = 11, size = data_types.Boolean}, - parameter15 = {parameter_number = 15, size = data_types.Uint8}, - parameter17 = {parameter_number = 17, size = data_types.Uint8}, - parameter95 = {parameter_number = 95, size = data_types.Uint8}, - parameter96 = {parameter_number = 96, size = data_types.Uint8}, - parameter97 = {parameter_number = 97, size = data_types.Uint8}, - parameter98 = {parameter_number = 98, size = data_types.Uint8}, - parameter101 = {parameter_number = 101, size = data_types.Int16}, - parameter102 = {parameter_number = 102, size = data_types.Int16}, - parameter103 = {parameter_number = 103, size = data_types.Int16}, - parameter104 = {parameter_number = 104, size = data_types.Int16}, - parameter105 = {parameter_number = 105, size = data_types.Int16}, - parameter106 = {parameter_number = 106, size = data_types.Int16}, - parameter110 = {parameter_number = 110, size = data_types.Uint8}, - parameter111 = {parameter_number = 111, size = data_types.Uint32}, - parameter112 = {parameter_number = 112, size = data_types.Uint8}, - parameter113 = {parameter_number = 113, size = data_types.Uint8}, - parameter114 = {parameter_number = 114, size = data_types.Uint32}, - parameter115 = {parameter_number = 115, size = data_types.Uint32}, - parameter117 = {parameter_number = 117, size = data_types.Uint8}, -} - -local preferences_to_numeric_value = function(new_value) - local numeric = tonumber(new_value) - if numeric == nil then -- in case the value is Boolean - numeric = new_value and 1 or 0 - end - return numeric -end - -local preferences_calculate_parameter = function(new_value, type, number) - if number == "parameter9" or number == "parameter10" or number == "parameter13" or number == "parameter14" or number == "parameter15" or number == "parameter55" or number == "parameter56" then - if new_value == 101 then - return 255 - else - return utils.round(new_value / 100 * 254) - end - else - return new_value - end -end - -local is_inovelli_vzm32_sn = function(opts, driver, device) - for _, fingerprint in ipairs(INOVELLI_VZM32_SN_FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("inovelli-vzm32-sn") - return true, subdriver - end - end - return false -end - -local function to_boolean(value) - if value == 0 or value =="0" then - return false - else - return true - end -end - -local map_key_attribute_to_capability = { - [0x00] = capabilities.button.button.pushed, - [0x01] = capabilities.button.button.held, - [0x02] = capabilities.button.button.down_hold, - [0x03] = capabilities.button.button.pushed_2x, - [0x04] = capabilities.button.button.pushed_3x, - [0x05] = capabilities.button.button.pushed_4x, - [0x06] = capabilities.button.button.pushed_5x, -} - -local key_mmwave_preferences = { - "parameter101", - "parameter102", - "parameter103", - "parameter104", - "parameter105", - "parameter106", - "parameter111", - "parameter112", - "parameter113", - "parameter114", -} - -local function button_to_component(buttonId) - if buttonId > 0 then - return string.format("button%d", buttonId) - end -end - -local function scene_handler(driver, device, zb_rx) - local bytes = zb_rx.body.zcl_body.body_bytes - local button_number = bytes:byte(1) - local capability_attribute = map_key_attribute_to_capability[bytes:byte(2)] - local additional_fields = { - state_change = true - } - - local event - if capability_attribute ~= nil then - event = capability_attribute(additional_fields) - end - - local comp = device.profile.components[button_to_component(button_number)] - if comp ~= nil then - device:emit_component_event(comp, event) - end -end - -local function add_child(driver,parent,profile,child_type) - local child_metadata = { - type = "EDGE_CHILD", - label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)), - profile = profile, - parent_device_id = parent.id, - parent_assigned_child_key = child_type, - vendor_provided_label = string.format("%s %s", parent.label, child_type:gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end)) - } - driver:try_create_device(child_metadata) -end - -local function contains(array, value) - for _, element in ipairs(array) do - if element == value then - return true - end - end - return false -end - -local function configure_illuminance_reporting(device) - local min_lux_change = 15 - local value = math.floor(10000 * math.log10(min_lux_change) + 1) - device:send(clusters.IlluminanceMeasurement.attributes.MeasuredValue:configure_reporting( - device, - 10, -- Minimum reporting interval (seconds) - 600, -- Maximum reporting interval (seconds) - value -- Reportable change (in raw unit values) - )) -end - -local function send_ota_image_notify(device) - local PAYLOAD_TYPE = 0x00 - local QUERY_JITTER = 100 - local MFG_CODE = MFG_CODE - local IMAGE_TYPE = 0xFFFF - local NEW_VERSION = 0xFFFFFFFF - device:send(OTAUpgrade.commands.ImageNotify(device, PAYLOAD_TYPE, QUERY_JITTER, MFG_CODE, IMAGE_TYPE, NEW_VERSION)) -end - -local function info_changed(driver, device, event, args) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - local time_diff = 3 - local last_clock_set_time = device:get_field(LATEST_CLOCK_SET_TIMESTAMP) - if last_clock_set_time ~= nil then - time_diff = os.difftime(os.time(), last_clock_set_time) - end - device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time(), {persist = true}) - configure_illuminance_reporting(device) - - if time_diff > 2 then - local preferences = preference_map - if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then - if not device:get_child_by_parent_assigned_key('notification') then - add_child(driver,device,'rgbw-bulb-2700K-6500K','notificaiton') - end - end - for id, value in pairs(device.preferences) do - if args.old_st_store.preferences[id] ~= value and preferences and preferences[id] then - local new_parameter_value = preferences_calculate_parameter(preferences_to_numeric_value(device.preferences[id]), preferences[id].size, id) - if id == "parameter111" then - print("mmwave control command: " .. id .. " " .. value) - device:send(cluster_base.build_manufacturer_specific_command( - device, - PRIVATE_CLUSTER_MMWAVE_ID, - PRIVATE_CMD_MMWAVE_ID, - MFG_CODE, - utils.serialize_int(new_parameter_value,1,false,false))) - elseif contains(key_mmwave_preferences, id) then - print("mmwave preference: " .. id .. " " .. value) - if(preferences[id].size == data_types.Boolean) then - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_MMWAVE_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, to_boolean(new_parameter_value))) - else - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_MMWAVE_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) - end - else - print("preference: " .. id .. " " .. value) - if(preferences[id].size == data_types.Boolean) then - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, to_boolean(new_parameter_value))) - else - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, preferences[id].parameter_number, MFG_CODE, preferences[id].size, new_parameter_value)) - end - end - end - end - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - end - end -end - -local do_configure = function(self, device) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:refresh() - device:configure() - configure_illuminance_reporting(device) - send_ota_image_notify(device) - - - device:send(device_management.build_bind_request(device, PRIVATE_CLUSTER_ID, self.environment_info.hub_zigbee_eui, 2)) -- Bind device for button presses. - device:send(device_management.build_bind_request(device, OccupancySensing.ID, self.environment_info.hub_zigbee_eui)) - - -- Retrieve Neutral Setting "Parameter 21" - device:send(cluster_base.read_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, 21, MFG_CODE)) - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - - -- Additional one time configuration - if device:supports_capability(capabilities.powerMeter) then - -- Divisor and multipler for PowerMeter - device:send(clusters.SimpleMetering.attributes.Divisor:read(device)) - device:send(clusters.SimpleMetering.attributes.Multiplier:read(device)) - end - - if device:supports_capability(capabilities.energyMeter) then - -- Divisor and multipler for EnergyMeter - device:send(clusters.ElectricalMeasurement.attributes.ACPowerDivisor:read(device)) - device:send(clusters.ElectricalMeasurement.attributes.ACPowerMultiplier:read(device)) - end - end -end - -local device_init = function(self, device) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:set_field(LATEST_CLOCK_SET_TIMESTAMP, os.time()) - if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil and device:supports_capability(capabilities.switchLevel)then - device:emit_event(capabilities.switchLevel.level(0)) - end - if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == nil and device:supports_capability(capabilities.powerMeter) then - device:emit_event(capabilities.powerMeter.power(0)) - end - if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil and device:supports_capability(capabilities.energyMeter)then - device:emit_event(capabilities.energyMeter.energy(0)) - end - if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == nil and device:supports_capability(capabilities.illuminanceMeasurement) then - device:emit_event(capabilities.illuminanceMeasurement.illuminance(0)) - end - if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == nil and device:supports_capability(capabilities.motionSensor) then - device:emit_event(capabilities.motionSensor.motion.active()) - end - - for _, component in pairs(device.profile.components) do - if string.find(component.id, "button") ~= nil then - if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.supportedButtonValues.NAME) == nil then - device:emit_component_event( - component, - capabilities.button.supportedButtonValues( - {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, - { visibility = { displayed = false } } - ) - ) - end - if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.numberOfButtons.NAME) == nil then - device:emit_component_event( - component, - capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) - ) - end - end - end - device:send(cluster_base.read_attribute(device, data_types.ClusterId(0x0000), 0x4000)) - else - device:emit_event(capabilities.colorControl.hue(1)) - device:emit_event(capabilities.colorControl.saturation(1)) - device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) - device:emit_event(capabilities.switchLevel.level(100)) - device:emit_event(capabilities.switch.switch("off")) - end -end - -local function energy_meter_handler(driver, device, value, zb_rx) - local raw_value = value.value - raw_value = raw_value / 100 - device:emit_event(capabilities.energyMeter.energy({value = raw_value, unit = "kWh" })) -end - -local function power_meter_handler(driver, device, value, zb_rx) - local raw_value = value.value - raw_value = raw_value / 10 - device:emit_event(capabilities.powerMeter.power({value = raw_value, unit = "W" })) -end - -local function huePercentToValue(value) - if value <= 2 then - return 0 - elseif value >= 98 then - return 255 - else - return utils.round(value / 100 * 255) - end -end - -local function getNotificationValue(device, value) - local notificationValue = 0 - local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 - local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) - local effect = device:get_parent_device().preferences.notificationType or 1 - notificationValue = notificationValue + (effect*16777216) - notificationValue = notificationValue + (huePercentToValue(value or color)*65536) - notificationValue = notificationValue + (level*256) - notificationValue = notificationValue + (255*1) - return notificationValue -end - -local function on_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.OnOff.server.commands.On(device)) - else - device:emit_event(capabilities.switch.switch("on")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function off_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.OnOff.server.commands.Off(device)) - else - device:emit_event(capabilities.switch.switch("off")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(0,4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function switch_level_handler(driver, device, command) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:send(clusters.Level.server.commands.MoveToLevelWithOnOff(device, math.floor(command.args.level/100.0 * 254), command.args.rate or 0xFFFF)) - else - device:emit_event(capabilities.switchLevel.level(command.args.level)) - device:emit_event(capabilities.switch.switch(command.args.level ~= 0 and "on" or "off")) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) - end -end - -local function set_color_temperature(driver, device, command) - device:emit_event(capabilities.colorControl.hue(100)) - device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device, 100),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) -end - -local function set_color(driver, device, command) - device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) - device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) - local dev = device:get_parent_device() - local send_configuration = function() - dev:send(cluster_base.build_manufacturer_specific_command( - dev, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_NOTIF_ID, - MFG_CODE, - utils.serialize_int(getNotificationValue(device),4,false,false))) - end - device.thread:call_with_delay(1,send_configuration) -end - -local function occupancy_attr_handler(driver, device, occupancy, zb_rx) - device:emit_event(occupancy.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) -end - -local function illuminance_attr_handler(driver, device, illuminance, zb_rx) - local lux = math.floor(10 ^ ((illuminance.value - 1) / 10000)) - device:emit_event(capabilities.illuminanceMeasurement.illuminance({value = lux, unit = "lux" })) -end - -local function handle_resetEnergyMeter(self, device) - device:send(cluster_base.build_manufacturer_specific_command( - device, - PRIVATE_CLUSTER_ID, - PRIVATE_CMD_ENERGY_RESET_ID, - MFG_CODE, - utils.serialize_int(0,1,false,false))) - - device:send(clusters.SimpleMetering.attributes.CurrentSummationDelivered:read(device)) - device:send(clusters.ElectricalMeasurement.attributes.ActivePower:read(device)) -end - -local inovelli_vzm32_sn = { - NAME = "inovelli vzm32-sn handler", - lifecycle_handlers = { - doConfigure = do_configure, - init = configurations.power_reconfig_wrapper(device_init), - infoChanged = info_changed - }, - zigbee_handlers = { - attr = { - [clusters.SimpleMetering.ID] = { - [clusters.SimpleMetering.attributes.InstantaneousDemand.ID] = power_meter_handler, - [clusters.SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler - }, - [clusters.ElectricalMeasurement.ID] = { - [clusters.ElectricalMeasurement.attributes.ActivePower.ID] = power_meter_handler - }, - [OccupancySensing.ID] = { - [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler - }, - [clusters.IlluminanceMeasurement.ID] = { - [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = illuminance_attr_handler - }, - }, - cluster = { - [PRIVATE_CLUSTER_ID] = { - [PRIVATE_CMD_SCENE_ID] = scene_handler, - } - } - }, - capability_handlers = { - [capabilities.switch.ID] = { - [capabilities.switch.commands.on.NAME] = on_handler, - [capabilities.switch.commands.off.NAME] = off_handler, - }, - [capabilities.switchLevel.ID] = { - [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler - }, - [capabilities.colorControl.ID] = { - [capabilities.colorControl.commands.setColor.NAME] = set_color - }, - [capabilities.colorTemperature.ID] = { - [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temperature - }, - [capabilities.energyMeter.ID] = { - [capabilities.energyMeter.commands.resetEnergyMeter.NAME] = handle_resetEnergyMeter, - } - }, - can_handle = is_inovelli_vzm32_sn -} - -return inovelli_vzm32_sn From 2f855cb2d7857dec592c220aabc6db0117c53186 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 29 Aug 2025 16:04:49 -0600 Subject: [PATCH 12/32] some updates from linter results. Notification turns on when color changed. Update the app UI --- .../src/inovelli-vzw32-sn/init.lua | 59 +++++++------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 8b0fcd1394..23ffa7eae7 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -5,8 +5,6 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({ version= local Version = (require "st.zwave.CommandClass.Version")({ version=1 }) --- @type st.zwave.CommandClass.Association local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) ---- @type st.zwave.constants -local constants = require "st.zwave.constants" --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) --- @type st.zwave.CommandClass.Basic @@ -14,7 +12,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) local preferencesMap = require "preferences" - --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) @@ -43,7 +40,7 @@ local INOVELLI_VZW32_SN_FINGERPRINTS = { --- @return table dst_channels destination channels e.g. {2} for Z-Wave channel 2 or {} for unencapsulated local function component_to_endpoint(device, component_id) local ep_num = component_id:match("switch(%d)") - return { ep_num and tonumber(ep_num) } + return { ep_num and tonumber(ep_num) } end --- Map end_point(channel) to Z-Wave endpoint 9 channel) @@ -85,7 +82,6 @@ local preferences_to_numeric_value = function(new_value) end local preferences_calculate_parameter = function(new_value, type, number) - local numeric = tonumber(new_value) if type == 4 and new_value > 2147483647 then return ((4294967296 - new_value) * -1) elseif type == 2 and new_value > 32767 then @@ -110,35 +106,37 @@ local function add_child(driver,parent,profile,child_type) end local function initialize(device) - if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == null then + if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == nil then device:emit_event(capabilities.illuminanceMeasurement.illuminance(0)) end - if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == null then + if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == nil then device:emit_event(capabilities.motionSensor.motion.active()) end - if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == null then + if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == nil then device:emit_event(capabilities.powerMeter.power(0)) end - if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == null then + if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil then device:emit_event(capabilities.energyMeter.energy(0)) end - if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == null then + if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil then device:emit_event(capabilities.switchLevel.level(0)) end for _, component in pairs(device.profile.components) do if string.find(component.id, "button") ~= nil then - device:emit_component_event( - component, - capabilities.button.supportedButtonValues( - {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, - { visibility = { displayed = false } } + if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.supportedButtonValues.NAME) == nil then + device:emit_component_event( + component, + capabilities.button.supportedButtonValues( + {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, + { visibility = { displayed = false } } + ) + ) + device:emit_component_event( + component, + capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) ) - ) - device:emit_component_event( - component, - capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) - ) + end end end end @@ -158,6 +156,7 @@ end local function set_color(driver, device, command) device:emit_event(capabilities.colorControl.hue(command.args.color.hue)) device:emit_event(capabilities.colorControl.saturation(command.args.color.saturation)) + device:emit_event(capabilities.switch.switch("on")) local dev = device:get_parent_device() local config = Configuration:Set({ parameter_number=NOTIFICATION_PARAMETER_NUMBER, @@ -173,6 +172,7 @@ end local function set_color_temperature(driver, device, command) device:emit_event(capabilities.colorControl.hue(100)) device:emit_event(capabilities.colorTemperature.colorTemperature(command.args.temperature)) + device:emit_event(capabilities.switch.switch("on")) local dev = device:get_parent_device() local config = Configuration:Set({ parameter_number=NOTIFICATION_PARAMETER_NUMBER, @@ -295,24 +295,6 @@ local function version_report(driver, device, cmd) log.info("Firmware Version: "..cmd.args.firmware_0_version.."."..cmd.args.firmware_0_sub_version) end -local function notification_report_handler(self, device, cmd) - local event - if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then - if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then - event = cmd.args.notification_status == 0 and capabilities.motionSensor.motion.inactive() or capabilities.motionSensor.motion.active() - elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then - if #cmd.args.event_parameter >= 1 and string.byte(cmd.args.event_parameter, 1) == 8 then - event = capabilities.motionSensor.motion.inactive() - else - event = capabilities.tamperAlert.tamper.clear() - end - end - end - if (event ~= nil) then - device:emit_event(event) - end -end - local map_key_attribute_to_capability = { [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, [CentralScene.key_attributes.KEY_RELEASED] = capabilities.button.button.held, @@ -369,7 +351,6 @@ local inovelli_vzw32_sn = { init = device_init, infoChanged = info_changed, }, - zwave_handlers = { [cc.CONFIGURATION] = { [Configuration.REPORT] = function() end -- Empty function since configuration_report was unused From d400b8bedb659dfde758238863ae63255a6420b4 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Thu, 11 Sep 2025 13:34:59 -0600 Subject: [PATCH 13/32] Removing unsupported capabilities --- .../zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml index dd94afb2a9..22a0cb96d4 100644 --- a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -20,10 +20,6 @@ components: version: 1 - id: firmwareUpdate version: 1 - - id: "eventflute36860.log" - version: 1 - - id: configuration - version: 1 - id: refresh version: 1 categories: From 554b18c43395aeb081cbc7acffc711c96143a159 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Mon, 13 Oct 2025 17:50:38 -0600 Subject: [PATCH 14/32] fixing mmwave reset command --- .../zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml index 22a0cb96d4..5c79ef2ffe 100644 --- a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -377,8 +377,7 @@ preferences: options: "1": "Set Interference Area" "3": "Clear Interference Area" - "254": "Get mmWave FW Version (see history)" - "255": "Factory Reset Module" + "0": "Factory Reset Module" default: 3 - name: "parameter112" title: "112. mmWave Sensitivity" From 9ab266ee10bf71dc426a2d2fe92275249af3052d Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 16:50:17 -0600 Subject: [PATCH 15/32] remove some preferences. Prevent extra events being sent for child device during init --- .../inovelli-mmwave-dimmer-vzw32-sn.yml | 44 +------------------ .../src/inovelli-vzw32-sn/init.lua | 20 ++++++--- .../zwave-switch/src/preferences.lua | 6 +-- 3 files changed, 17 insertions(+), 53 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml index 5c79ef2ffe..4f027d5bbd 100644 --- a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -91,16 +91,6 @@ preferences: "0": "Dimmer (default)" "1": "On/Off" default: 0 - - name: "parameter22" - title: "22. Aux Switch Type" - description: "Set the Aux switch type. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." - required: true - preferenceType: enumeration - definition: - options: - "0": "None" - "1": "3-Way Aux Switch (default)" - default: 1 - name: "parameter52" title: "52. Smart Bulb Mode" description: "For use with Smart Bulbs that need constant power and are controlled via commands rather than power. Smart Bulb Mode does not work in Dumb 3-Way Switch mode." @@ -409,36 +399,4 @@ preferences: definition: minimum: 0 maximum: 4294967296 - default: 30 - - name: "parameter117" - title: "117. Room Size" - description: "Sets the x, y, and z dimensions of the room for mmWave detection. Changing this parameter will update parameters 101-106 to reflect the preset." - required: true - preferenceType: enumeration - definition: - options: - "0": "Custom" - "1": "X-Small" - "2": "Small" - "3": "Medium" - "4": "Large" - "5": "X-Large" - default: 0 - - name: "parameter118" - title: "Lux Threshold" - description: "Threshold to send lux report." - required: true - preferenceType: number - definition: - minimum: 0 - maximum: 32767 - default: 20 - - name: "parameter119" - title: "Lux Interval" - description: "Interval, in seconds, to send Lux reports." - required: true - preferenceType: number - definition: - minimum: 0 - maximum: 32767 - default: 600 \ No newline at end of file + default: 30 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 23ffa7eae7..78baa8eb8e 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -230,11 +230,21 @@ local device_init = function(self, device) device:send(Version:Get({})) initialize(device) else - device:emit_event(capabilities.colorControl.hue(1)) - device:emit_event(capabilities.colorControl.saturation(1)) - device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) - device:emit_event(capabilities.switchLevel.level(100)) - device:emit_event(capabilities.switch.switch("off")) + if device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) == nil then + device:emit_event(capabilities.colorControl.hue(1)) + end + if device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.saturation.NAME) == nil then + device:emit_event(capabilities.colorControl.saturation(1)) + end + if device:get_latest_state("main", capabilities.colorTemperature.ID, capabilities.colorTemperature.colorTemperature.NAME) == nil then + device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) + end + if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil then + device:emit_event(capabilities.switchLevel.level(100)) + end + if device:get_latest_state("main", capabilities.switch.ID, capabilities.switch.switch.NAME) == nil then + device:emit_event(capabilities.switch.switch("off")) + end end end diff --git a/drivers/SmartThings/zwave-switch/src/preferences.lua b/drivers/SmartThings/zwave-switch/src/preferences.lua index 5c67321cf4..9c1c449820 100644 --- a/drivers/SmartThings/zwave-switch/src/preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/preferences.lua @@ -78,7 +78,6 @@ local devices = { }, PARAMETERS = { parameter158 = {parameter_number = 158, size = 1}, - parameter22 = {parameter_number = 22, size = 1}, parameter52 = {parameter_number = 52, size = 1}, parameter1 = {parameter_number = 1, size = 1}, parameter2 = {parameter_number = 2, size = 1}, @@ -106,10 +105,7 @@ local devices = { parameter111 = {parameter_number = 111, size = 1}, parameter112 = {parameter_number = 112, size = 1}, parameter113 = {parameter_number = 113, size = 1}, - parameter114 = {parameter_number = 114, size = 4}, - parameter117 = {parameter_number = 117, size = 1}, - parameter118 = {parameter_number = 118, size = 2}, - parameter119 = {parameter_number = 119, size = 2} + parameter114 = {parameter_number = 114, size = 4} } }, QUBINO_FLUSH_DIMMER = { From f2a13e639db3015800ec2b7e051c791f95ab3cfe Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 16:51:50 -0600 Subject: [PATCH 16/32] add copyright info --- .../zwave-switch/src/inovelli-vzw32-sn/init.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 78baa8eb8e..6c8e1b1f94 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -1,3 +1,17 @@ +-- Copyright 2025 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" --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) From 4e15fcc147874d8c60336b57d4802902c6a4fe90 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 16:57:26 -0600 Subject: [PATCH 17/32] remove firmware update capability --- .../zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml index 4f027d5bbd..d85a661839 100644 --- a/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml +++ b/drivers/SmartThings/zwave-switch/profiles/inovelli-mmwave-dimmer-vzw32-sn.yml @@ -18,8 +18,6 @@ components: version: 1 - id: energyMeter version: 1 - - id: firmwareUpdate - version: 1 - id: refresh version: 1 categories: From 94a46630c65f5b380b7f3db799d39391a7689113 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 17:02:47 -0600 Subject: [PATCH 18/32] remove unused configuration handler and notification class --- .../SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 6c8e1b1f94..7512cefd68 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -26,8 +26,6 @@ local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchMultilevel local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) local preferencesMap = require "preferences" ---- @type st.zwave.CommandClass.Notification -local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.utils local utils = require "st.utils" @@ -376,9 +374,6 @@ local inovelli_vzw32_sn = { infoChanged = info_changed, }, zwave_handlers = { - [cc.CONFIGURATION] = { - [Configuration.REPORT] = function() end -- Empty function since configuration_report was unused - }, [cc.CENTRAL_SCENE] = { [CentralScene.NOTIFICATION] = central_scene_notification_handler }, From 76657454893f31114f9e4ee8c77ee5b578629ce2 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 17:13:08 -0600 Subject: [PATCH 19/32] removing on/off and level handlers --- .../src/inovelli-vzw32-sn/init.lua | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 7512cefd68..2fa285d3e7 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -350,20 +350,6 @@ local function central_scene_notification_handler(self, device, cmd) end end -local function basic_and_switch_binary_report_handler(driver, device, cmd) - local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value - local event = value == SwitchBinary.value.OFF_DISABLE and capabilities.switch.switch.off() or capabilities.switch.switch.on() - device:emit_event_for_endpoint(cmd.src_channel, event) -end - -local function onoff_level_report_handler(self, device, cmd) - local value = cmd.args.target_value and cmd.args.target_value or cmd.args.value - device:emit_event(value == SwitchMultilevel.value.OFF_DISABLE and capabilities.switch.switch.off() or capabilities.switch.switch.on()) - if value >= 0 then - device:emit_event(capabilities.switchLevel.level(value >= 99 and 100 or value)) - end -end - ------------------------------------------------------------------------------------------- -- Register message handlers and run driver ------------------------------------------------------------------------------------------- @@ -380,12 +366,6 @@ local inovelli_vzw32_sn = { [cc.VERSION] = { [Version.REPORT] = version_report }, - [cc.BASIC] = { - [Basic.REPORT] = basic_and_switch_binary_report_handler - }, - [cc.SWITCH_MULTILEVEL] = { - [SwitchMultilevel.REPORT] = onoff_level_report_handler - } }, capability_handlers = { [capabilities.switch.ID] = { From a088f12268c525ea8a73bb68e8bee61251e51073 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 17:16:54 -0600 Subject: [PATCH 20/32] removing version report features --- .../zwave-switch/src/inovelli-vzw32-sn/init.lua | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 2fa285d3e7..ef7e09cc0b 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -15,8 +15,6 @@ local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) ---- @type st.zwave.CommandClass.Version -local Version = (require "st.zwave.CommandClass.Version")({ version=1 }) --- @type st.zwave.CommandClass.Association local Association = (require "st.zwave.CommandClass.Association")({ version = 1 }) --- @type st.zwave.CommandClass.SwitchBinary @@ -313,10 +311,6 @@ local function switch_set_on_off_handler(value) end end -local function version_report(driver, device, cmd) - log.info("Firmware Version: "..cmd.args.firmware_0_version.."."..cmd.args.firmware_0_sub_version) -end - local map_key_attribute_to_capability = { [CentralScene.key_attributes.KEY_PRESSED_1_TIME] = capabilities.button.button.pushed, [CentralScene.key_attributes.KEY_RELEASED] = capabilities.button.button.held, @@ -363,9 +357,6 @@ local inovelli_vzw32_sn = { [cc.CENTRAL_SCENE] = { [CentralScene.NOTIFICATION] = central_scene_notification_handler }, - [cc.VERSION] = { - [Version.REPORT] = version_report - }, }, capability_handlers = { [capabilities.switch.ID] = { From 70f4c98e2f166a70b508506d75830f0875994712 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Tue, 21 Oct 2025 22:29:41 -0600 Subject: [PATCH 21/32] add unit tests --- .../src/test/test_inovelli_vzw32_sn.lua | 280 ++++++++++++++ .../src/test/test_inovelli_vzw32_sn_child.lua | 307 ++++++++++++++++ .../test_inovelli_vzw32_sn_preferences.lua | 347 ++++++++++++++++++ .../src/test/test_inovelli_vzw32_sn_utils.lua | 249 +++++++++++++ 4 files changed, 1183 insertions(+) create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua create mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua new file mode 100644 index 0000000000..602e0147ad --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -0,0 +1,280 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) +local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) +local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) +local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) +local t_utils = require "integration_test.utils" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + {value = zw.SWITCH_BINARY}, + {value = zw.SWITCH_MULTILEVEL}, + {value = zw.BASIC}, + {value = zw.CONFIGURATION}, + {value = zw.CENTRAL_SCENE}, + {value = zw.ASSOCIATION}, + } + } +} + +-- Create mock device +local mock_inovelli_vzw32_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw32_sn) +end +test.set_test_init_function(test_init) + +-- Test device initialization +test.register_message_test( + "Device should initialize properly on added lifecycle event", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch on command +test.register_message_test( + "Switch on command should send Basic Set with ON value", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "on", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Basic:Set({ value = SwitchBinary.value.ON_ENABLE }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch off command +test.register_message_test( + "Switch off command should send Basic Set with OFF value", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "off", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Basic:Set({ value = SwitchBinary.value.OFF_DISABLE }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test switch level command +test.register_message_test( + "Switch level command should send SwitchMultilevel Set", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "switchLevel", command = "setLevel", args = { level = 50 } } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Set({ value = 50, duration = "default" }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test color control command +test.register_message_test( + "Color control command should emit events and send configuration", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "colorControl", command = "setColor", args = { color = { hue = 100, saturation = 50 } } } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.colorControl.hue(100)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.colorControl.saturation(50)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.switch.switch("on")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- This would be calculated based on notification value + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test central scene notifications +test.register_message_test( + "Central scene notification should emit button events", + { + { + channel = "zwave", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, zw_test_utils.zwave_test_build_receive_command(CentralScene:Notification({ + scene_number = 1, + key_attributes = CentralScene.key_attributes.KEY_PRESSED_1_TIME + })) } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_inovelli_vzw32_sn:generate_test_message("button1", capabilities.button.button.pushed({ + state_change = true + })) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test refresh capability +test.register_message_test( + "Refresh capability should request switch level", + { + { + channel = "capability", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + SwitchMultilevel:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua new file mode 100644 index 0000000000..7bf04126ab --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -0,0 +1,307 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) +local t_utils = require "integration_test.utils" +local st_device = require "st.device" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + {value = zw.SWITCH_BINARY}, + {value = zw.SWITCH_MULTILEVEL}, + {value = zw.BASIC}, + {value = zw.CONFIGURATION}, + {value = zw.CENTRAL_SCENE}, + {value = zw.ASSOCIATION}, + } + } +} + +-- Create mock parent device +local mock_parent_device = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +-- Create mock child device (notification device) +local mock_child_device = test.mock_device.build_test_device({ + profile = t_utils.get_profile_definition("rgbw-bulb-2700K-6500K.yml"), + parent_device_id = mock_parent_device.id, + parent_assigned_child_key = "notification" +}) + +-- Set child device network type +mock_child_device.network_type = st_device.NETWORK_TYPE_CHILD + +local function test_init() + test.mock_device.add_test_device(mock_parent_device) + test.mock_device.add_test_device(mock_child_device) +end +test.set_test_init_function(test_init) + +-- Test child device initialization +test.register_message_test( + "Child device should initialize with default color values", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_child_device.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch on command +test.register_message_test( + "Child device switch on should emit events and send configuration to parent", + { + { + channel = "capability", + direction = "receive", + message = { + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- This would be calculated based on notification value + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device switch off command +test.register_message_test( + "Child device switch off should emit events and send configuration to parent", + { + { + channel = "capability", + direction = "receive", + message = { + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device level command +test.register_message_test( + "Child device level command should emit events and send configuration to parent", + { + { + channel = "capability", + direction = "receive", + message = { + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level = 75 } } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(75)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- This would be calculated based on notification value + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device color command +test.register_message_test( + "Child device color command should emit events and send configuration to parent", + { + { + channel = "capability", + direction = "receive", + message = { + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = { color = { hue = 200, saturation = 80 } } } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(200)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(80)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- This would be calculated based on notification value + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test child device color temperature command +test.register_message_test( + "Child device color temperature command should emit events and send configuration to parent", + { + { + channel = "capability", + direction = "receive", + message = { + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { temperature = 3000 } } + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(3000)) + }, + { + channel = "device_lifecycle", + direction = "receive", + message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_parent_device, + Configuration:Set({ + parameter_number = 99, + configuration_value = 0, -- This would be calculated based on notification value + size = 4 + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua new file mode 100644 index 0000000000..3c92ecaed2 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -0,0 +1,347 @@ +-- Copyright 2025 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 zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) +local Association = (require "st.zwave.CommandClass.Association")({version=1}) +local t_utils = require "integration_test.utils" +local utils = require "st.utils" + +-- Inovelli VZW32-SN device identifiers +local INOVELLI_MANUFACTURER_ID = 0x031E +local INOVELLI_VZW32_SN_PRODUCT_TYPE = 0x0017 +local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 + +-- Device endpoints with supported command classes +local inovelli_vzw32_sn_endpoints = { + { + command_classes = { + {value = zw.SWITCH_BINARY}, + {value = zw.SWITCH_MULTILEVEL}, + {value = zw.BASIC}, + {value = zw.CONFIGURATION}, + {value = zw.CENTRAL_SCENE}, + {value = zw.ASSOCIATION}, + } + } +} + +-- Create mock device +local mock_inovelli_vzw32_sn = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("inovelli-mmwave-dimmer-vzw32-sn.yml"), + zwave_endpoints = inovelli_vzw32_sn_endpoints, + zwave_manufacturer_id = INOVELLI_MANUFACTURER_ID, + zwave_product_type = INOVELLI_VZW32_SN_PRODUCT_TYPE, + zwave_product_id = INOVELLI_VZW32_SN_PRODUCT_ID +}) + +local function test_init() + test.mock_device.add_test_device(mock_inovelli_vzw32_sn) +end +test.set_test_init_function(test_init) + +-- Test preference change for notification child creation +test.register_message_test( + "Enabling notification child preference should create child device", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", + utils.json_encode({ + old_st_store = { + preferences = { + notificationChild = false + } + }, + preferences = { + notificationChild = true + } + }) + } + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "device_created" }, + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test preference change for configuration parameters +test.register_message_test( + "Changing configuration preference should send Configuration Set command", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", + utils.json_encode({ + old_st_store = { + preferences = { + ledIntensity = 50 + } + }, + preferences = { + ledIntensity = 75 + } + }) + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 1, -- Example parameter number + configuration_value = 75, + size = 1 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {1} -- Mock hub Z-Wave ID + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test multiple preference changes +test.register_message_test( + "Multiple preference changes should send multiple Configuration Set commands", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", + utils.json_encode({ + old_st_store = { + preferences = { + ledIntensity = 50, + ledColorWhenOn = 1, + ledColorWhenOff = 2 + } + }, + preferences = { + ledIntensity = 75, + ledColorWhenOn = 3, + ledColorWhenOff = 4 + } + }) + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 1, + configuration_value = 75, + size = 1 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 2, + configuration_value = 3, + size = 1 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 3, + configuration_value = 4, + size = 1 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {1} + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test boolean preference handling +test.register_message_test( + "Boolean preference should be converted to numeric value", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", + utils.json_encode({ + old_st_store = { + preferences = { + ledEnabled = false + } + }, + preferences = { + ledEnabled = true + } + }) + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 4, + configuration_value = 1, -- true converted to 1 + size = 1 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {1} + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- Test signed integer preference handling +test.register_message_test( + "Large signed integer preference should be handled correctly", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_inovelli_vzw32_sn.id, "added" }, + }, + { + channel = "device_lifecycle", + direction = "receive", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", + utils.json_encode({ + old_st_store = { + preferences = { + largeValue = 100 + } + }, + preferences = { + largeValue = 3000000000 -- Large value that would overflow 32-bit signed int + } + }) + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 5, + configuration_value = -1294967296, -- Correctly calculated signed value + size = 4 + }) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {1} + }) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua new file mode 100644 index 0000000000..0ede97fa99 --- /dev/null +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua @@ -0,0 +1,249 @@ +-- Copyright 2025 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. + +-- Unit tests for Inovelli VZW32-SN utility functions +-- This file tests the helper functions used in the driver + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local zw = require "st.zwave" +local zw_test_utils = require "integration_test.zwave_test_utils" +local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) +local t_utils = require "integration_test.utils" +local utils = require "st.utils" + +-- Import the driver module to test its functions +local inovelli_driver = require "inovelli-vzw32-sn" + +-- Test huePercentToValue function +local function test_hue_percent_to_value() + local huePercentToValue = function(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return utils.round(value / 100 * 255) + end + end + + -- Test edge cases + assert(huePercentToValue(0) == 0, "Value 0 should return 0") + assert(huePercentToValue(1) == 0, "Value 1 should return 0") + assert(huePercentToValue(2) == 0, "Value 2 should return 0") + assert(huePercentToValue(98) == 255, "Value 98 should return 255") + assert(huePercentToValue(99) == 255, "Value 99 should return 255") + assert(huePercentToValue(100) == 255, "Value 100 should return 255") + + -- Test middle values + assert(huePercentToValue(50) == 128, "Value 50 should return 128") + assert(huePercentToValue(25) == 64, "Value 25 should return 64") + assert(huePercentToValue(75) == 191, "Value 75 should return 191") +end + +-- Test preferences_to_numeric_value function +local function test_preferences_to_numeric_value() + local preferences_to_numeric_value = function(new_value) + local numeric = tonumber(new_value) + if numeric == nil then -- in case the value is boolean + numeric = new_value and 1 or 0 + end + return numeric + end + + -- Test numeric values + assert(preferences_to_numeric_value("50") == 50, "String '50' should return 50") + assert(preferences_to_numeric_value(75) == 75, "Number 75 should return 75") + + -- Test boolean values + assert(preferences_to_numeric_value(true) == 1, "Boolean true should return 1") + assert(preferences_to_numeric_value(false) == 0, "Boolean false should return 0") + + -- Test nil values + assert(preferences_to_numeric_value(nil) == 0, "Nil should return 0") +end + +-- Test preferences_calculate_parameter function +local function test_preferences_calculate_parameter() + local preferences_calculate_parameter = function(new_value, type, number) + if type == 4 and new_value > 2147483647 then + return ((4294967296 - new_value) * -1) + elseif type == 2 and new_value > 32767 then + return ((65536 - new_value) * -1) + elseif type == 1 and new_value > 127 then + return ((256 - new_value) * -1) + else + return new_value + end + end + + -- Test normal values + assert(preferences_calculate_parameter(50, 1, 1) == 50, "Small value should remain unchanged") + assert(preferences_calculate_parameter(100, 2, 2) == 100, "Medium value should remain unchanged") + assert(preferences_calculate_parameter(1000, 4, 4) == 1000, "Large value should remain unchanged") + + -- Test overflow cases + assert(preferences_calculate_parameter(200, 1, 1) == -56, "Byte overflow should be handled") + assert(preferences_calculate_parameter(40000, 2, 2) == -25536, "Word overflow should be handled") + assert(preferences_calculate_parameter(3000000000, 4, 4) == -1294967296, "Dword overflow should be handled") +end + +-- Test component_to_endpoint function +local function test_component_to_endpoint() + local component_to_endpoint = function(device, component_id) + local ep_num = component_id:match("switch(%d)") + return { ep_num and tonumber(ep_num) } + end + + -- Test valid component IDs + local result1 = component_to_endpoint(nil, "switch1") + assert(result1[1] == 1, "switch1 should map to endpoint 1") + + local result2 = component_to_endpoint(nil, "switch2") + assert(result2[1] == 2, "switch2 should map to endpoint 2") + + -- Test invalid component IDs + local result3 = component_to_endpoint(nil, "main") + assert(result3[1] == nil, "main should map to nil endpoint") + + local result4 = component_to_endpoint(nil, "button1") + assert(result4[1] == nil, "button1 should map to nil endpoint") +end + +-- Test endpoint_to_component function +local function test_endpoint_to_component() + local mock_device = { + profile = { + components = { + switch1 = {}, + switch2 = {}, + main = {} + } + } + } + + local endpoint_to_component = function(device, ep) + local switch_comp = string.format("switch%d", ep) + if device.profile.components[switch_comp] ~= nil then + return switch_comp + else + return "main" + end + end + + -- Test valid endpoints + assert(endpoint_to_component(mock_device, 1) == "switch1", "Endpoint 1 should map to switch1") + assert(endpoint_to_component(mock_device, 2) == "switch2", "Endpoint 2 should map to switch2") + + -- Test invalid endpoints + assert(endpoint_to_component(mock_device, 3) == "main", "Endpoint 3 should map to main") + assert(endpoint_to_component(mock_device, 0) == "main", "Endpoint 0 should map to main") +end + +-- Test button_to_component function +local function test_button_to_component() + local button_to_component = function(buttonId) + if buttonId > 0 then + return string.format("button%d", buttonId) + end + end + + -- Test valid button IDs + assert(button_to_component(1) == "button1", "Button ID 1 should return button1") + assert(button_to_component(2) == "button2", "Button ID 2 should return button2") + assert(button_to_component(3) == "button3", "Button ID 3 should return button3") + + -- Test invalid button IDs + assert(button_to_component(0) == nil, "Button ID 0 should return nil") + assert(button_to_component(-1) == nil, "Button ID -1 should return nil") +end + +-- Test getNotificationValue function +local function test_get_notification_value() + local mock_device = { + get_latest_state = function(self, component, capability_id, attribute) + if capability_id == "switchLevel" and attribute == "level" then + return 75 + elseif capability_id == "colorControl" and attribute == "hue" then + return 100 + end + return nil + end, + get_parent_device = function(self) + return { + preferences = { + notificationType = 2 + } + } + end + } + + local huePercentToValue = function(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return utils.round(value / 100 * 255) + end + end + + local getNotificationValue = function(device, value) + local notificationValue = 0 + local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 + local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) + local effect = device:get_parent_device().preferences.notificationType or 1 + notificationValue = notificationValue + (effect*16777216) + notificationValue = notificationValue + (huePercentToValue(value or color)*65536) + notificationValue = notificationValue + (level*256) + notificationValue = notificationValue + (255*1) + return notificationValue + end + + -- Test with default values + local result = getNotificationValue(mock_device) + -- Expected: 2*16777216 + 255*65536 + 75*256 + 255*1 = 33554432 + 16711680 + 19200 + 255 = 50285567 + assert(result == 50285567, "Notification value calculation should be correct") +end + +-- Run all tests +local function run_tests() + print("Running Inovelli VZW32-SN utility function tests...") + + test_hue_percent_to_value() + print("✓ huePercentToValue tests passed") + + test_preferences_to_numeric_value() + print("✓ preferences_to_numeric_value tests passed") + + test_preferences_calculate_parameter() + print("✓ preferences_calculate_parameter tests passed") + + test_component_to_endpoint() + print("✓ component_to_endpoint tests passed") + + test_endpoint_to_component() + print("✓ endpoint_to_component tests passed") + + test_button_to_component() + print("✓ button_to_component tests passed") + + test_get_notification_value() + print("✓ getNotificationValue tests passed") + + print("All utility function tests passed!") +end + +-- Execute tests +run_tests() From a2db762a0ef14380cf0b9c383c55aab29aac7cac Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 11:52:43 -0600 Subject: [PATCH 22/32] updating notification profile as requested by ST --- .../profiles/{rgbw-bulb-2700K-6500K.yml => rgbw-bulb.yml} | 6 +----- .../SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) rename drivers/SmartThings/zwave-switch/profiles/{rgbw-bulb-2700K-6500K.yml => rgbw-bulb.yml} (68%) diff --git a/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml similarity index 68% rename from drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml rename to drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml index 8878a04a99..22811d4b32 100644 --- a/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb-2700K-6500K.yml +++ b/drivers/SmartThings/zwave-switch/profiles/rgbw-bulb.yml @@ -1,4 +1,4 @@ -name: rgbw-bulb-2700K-6500K +name: rgbw-bulb components: - id: main capabilities: @@ -8,10 +8,6 @@ components: version: 1 - id: colorTemperature version: 1 - config: - values: - - key: "colorTemperature.value" - range: [ 2700, 6500 ] - id: colorControl version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index ef7e09cc0b..397643b289 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -247,6 +247,7 @@ local device_init = function(self, device) device:emit_event(capabilities.colorControl.saturation(1)) end if device:get_latest_state("main", capabilities.colorTemperature.ID, capabilities.colorTemperature.colorTemperature.NAME) == nil then + device:emit_event(capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) device:emit_event(capabilities.colorTemperature.colorTemperature(6500)) end if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil then @@ -270,7 +271,7 @@ local function info_changed(driver, device, event, args) local preferences = preferencesMap.get_device_parameters(device) if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then if not device:get_child_by_parent_assigned_key('notification') then - add_child(driver,device,'rgbw-bulb-2700K-6500K','notificaiton') + add_child(driver,device,'rgbw-bulb','notificaiton') end end From eaff7da58788a17164e5ae277a2a6ecb36bd38f7 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 13:41:47 -0600 Subject: [PATCH 23/32] update unit test for different rgbw profile --- .../zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua index 7bf04126ab..5f7e26c3ed 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -50,7 +50,7 @@ local mock_parent_device = test.mock_device.build_test_zwave_device({ -- Create mock child device (notification device) local mock_child_device = test.mock_device.build_test_device({ - profile = t_utils.get_profile_definition("rgbw-bulb-2700K-6500K.yml"), + profile = t_utils.get_profile_definition("rgbw-bulb.yml"), parent_device_id = mock_parent_device.id, parent_assigned_child_key = "notification" }) From 1b8f1f36b2c7bcbd934b1f97dafc4c394b945751 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 13:45:05 -0600 Subject: [PATCH 24/32] remove versionget command --- drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 397643b289..fc008bbddd 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -237,7 +237,6 @@ local device_init = function(self, device) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then device:set_component_to_endpoint_fn(component_to_endpoint) device:set_endpoint_to_component_fn(endpoint_to_component) - device:send(Version:Get({})) initialize(device) else if device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) == nil then From aba8cffedda3a95a1ab4bcd7543a47d34a792bd9 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 13:58:11 -0600 Subject: [PATCH 25/32] fix lua errors --- .../test_inovelli_vzw32_sn_preferences.lua | 31 +++++++++---------- .../src/test/test_inovelli_vzw32_sn_utils.lua | 20 ++++-------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua index 3c92ecaed2..d71e8c74c3 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -13,7 +13,6 @@ -- limitations under the License. local test = require "integration_test" -local capabilities = require "st.capabilities" local zw = require "st.zwave" local zw_test_utils = require "integration_test.zwave_test_utils" local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) @@ -66,9 +65,9 @@ test.register_message_test( { channel = "device_lifecycle", direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", utils.json_encode({ old_st_store = { preferences = { @@ -104,9 +103,9 @@ test.register_message_test( { channel = "device_lifecycle", direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", utils.json_encode({ old_st_store = { preferences = { @@ -160,9 +159,9 @@ test.register_message_test( { channel = "device_lifecycle", direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", utils.json_encode({ old_st_store = { preferences = { @@ -244,9 +243,9 @@ test.register_message_test( { channel = "device_lifecycle", direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", utils.json_encode({ old_st_store = { preferences = { @@ -300,9 +299,9 @@ test.register_message_test( { channel = "device_lifecycle", direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", + message = { + mock_inovelli_vzw32_sn.id, + "infoChanged", utils.json_encode({ old_st_store = { preferences = { diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua index 0ede97fa99..a3ffcfedc4 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua @@ -15,17 +15,9 @@ -- Unit tests for Inovelli VZW32-SN utility functions -- This file tests the helper functions used in the driver -local test = require "integration_test" local capabilities = require "st.capabilities" -local zw = require "st.zwave" -local zw_test_utils = require "integration_test.zwave_test_utils" -local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) -local t_utils = require "integration_test.utils" local utils = require "st.utils" --- Import the driver module to test its functions -local inovelli_driver = require "inovelli-vzw32-sn" - -- Test huePercentToValue function local function test_hue_percent_to_value() local huePercentToValue = function(value) @@ -226,22 +218,22 @@ local function run_tests() test_preferences_to_numeric_value() print("✓ preferences_to_numeric_value tests passed") - + test_preferences_calculate_parameter() print("✓ preferences_calculate_parameter tests passed") - + test_component_to_endpoint() print("✓ component_to_endpoint tests passed") - + test_endpoint_to_component() print("✓ endpoint_to_component tests passed") - + test_button_to_component() print("✓ button_to_component tests passed") - + test_get_notification_value() print("✓ getNotificationValue tests passed") - + print("All utility function tests passed!") end From a77f94b100c29321d2facd9685da07399ee2adf3 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 14:17:14 -0600 Subject: [PATCH 26/32] some more fixes for test unit files --- .../src/test/test_inovelli_vzw32_sn_child.lua | 2 +- .../src/test/test_inovelli_vzw32_sn_preferences.lua | 13 +++++++------ .../src/test/test_inovelli_vzw32_sn_utils.lua | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua index 5f7e26c3ed..2d622cb08f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -49,7 +49,7 @@ local mock_parent_device = test.mock_device.build_test_zwave_device({ }) -- Create mock child device (notification device) -local mock_child_device = test.mock_device.build_test_device({ +local mock_child_device = test.mock_device.build_test_child_device({ profile = t_utils.get_profile_definition("rgbw-bulb.yml"), parent_device_id = mock_parent_device.id, parent_assigned_child_key = "notification" diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua index d71e8c74c3..8ef6b29b6b 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -19,6 +19,7 @@ local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4 local Association = (require "st.zwave.CommandClass.Association")({version=1}) local t_utils = require "integration_test.utils" local utils = require "st.utils" +local json = require "st.json" -- Inovelli VZW32-SN device identifiers local INOVELLI_MANUFACTURER_ID = 0x031E @@ -68,7 +69,7 @@ test.register_message_test( message = { mock_inovelli_vzw32_sn.id, "infoChanged", - utils.json_encode({ + json.encode({ old_st_store = { preferences = { notificationChild = false @@ -106,7 +107,7 @@ test.register_message_test( message = { mock_inovelli_vzw32_sn.id, "infoChanged", - utils.json_encode({ + json.encode({ old_st_store = { preferences = { ledIntensity = 50 @@ -162,7 +163,7 @@ test.register_message_test( message = { mock_inovelli_vzw32_sn.id, "infoChanged", - utils.json_encode({ + json.encode({ old_st_store = { preferences = { ledIntensity = 50, @@ -246,7 +247,7 @@ test.register_message_test( message = { mock_inovelli_vzw32_sn.id, "infoChanged", - utils.json_encode({ + json.encode({ old_st_store = { preferences = { ledEnabled = false @@ -302,7 +303,7 @@ test.register_message_test( message = { mock_inovelli_vzw32_sn.id, "infoChanged", - utils.json_encode({ + json.encode({ old_st_store = { preferences = { largeValue = 100 @@ -343,4 +344,4 @@ test.register_message_test( } ) -test.run_registered_tests() +test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua index a3ffcfedc4..017d11f5f1 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua @@ -212,10 +212,10 @@ end -- Run all tests local function run_tests() print("Running Inovelli VZW32-SN utility function tests...") - + test_hue_percent_to_value() print("✓ huePercentToValue tests passed") - + test_preferences_to_numeric_value() print("✓ preferences_to_numeric_value tests passed") From 69bbcaad159c288c965d7bacfdc38b42cc222682 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Wed, 22 Oct 2025 14:25:38 -0600 Subject: [PATCH 27/32] missed an unused variable --- .../zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua index 8ef6b29b6b..eb419b124f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -18,7 +18,6 @@ local zw_test_utils = require "integration_test.zwave_test_utils" local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) local Association = (require "st.zwave.CommandClass.Association")({version=1}) local t_utils = require "integration_test.utils" -local utils = require "st.utils" local json = require "st.json" -- Inovelli VZW32-SN device identifiers From 5597b4d29b1549b27eaaff6b3e0b45084fb025a3 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Thu, 23 Oct 2025 23:30:50 -0600 Subject: [PATCH 28/32] modify unit test files. Changes to device init to be more efficient --- .../src/inovelli-vzw32-sn/init.lua | 17 +- .../src/test/test_inovelli_vzw32_sn.lua | 196 ++++----- .../src/test/test_inovelli_vzw32_sn_child.lua | 365 +++++++++------- .../test_inovelli_vzw32_sn_preferences.lua | 397 +++++------------- .../src/test/test_inovelli_vzw32_sn_utils.lua | 241 ----------- 5 files changed, 409 insertions(+), 807 deletions(-) delete mode 100644 drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index fc008bbddd..06a2be8297 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -200,7 +200,7 @@ local function switch_level_set(driver, device, command) local level = utils.round(command.args.level) level = utils.clamp_value(level, 0, 99) - device:emit_event(level > 0 and capabilities.switch.switch.on() or capabilities.switch.switch.off()) + --device:emit_event(level > 0 and capabilities.switch.switch.on() or capabilities.switch.switch.off()) device:send(SwitchMultilevel:Set({ value=level, duration=command.args.rate or "default" })) @@ -237,7 +237,14 @@ local device_init = function(self, device) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then device:set_component_to_endpoint_fn(component_to_endpoint) device:set_endpoint_to_component_fn(endpoint_to_component) - initialize(device) + --initialize(device) + end +end + +local function device_added(driver, device) + if device.network_type ~= st_device.NETWORK_TYPE_CHILD then + device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) + device:refresh() else if device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) == nil then device:emit_event(capabilities.colorControl.hue(1)) @@ -270,7 +277,7 @@ local function info_changed(driver, device, event, args) local preferences = preferencesMap.get_device_parameters(device) if args.old_st_store.preferences["notificationChild"] ~= device.preferences.notificationChild and args.old_st_store.preferences["notificationChild"] == false and device.preferences.notificationChild == true then if not device:get_child_by_parent_assigned_key('notification') then - add_child(driver,device,'rgbw-bulb','notificaiton') + add_child(driver,device,'rgbw-bulb','notification') end end @@ -280,7 +287,6 @@ local function info_changed(driver, device, event, args) device:send(Configuration:Set({parameter_number = preferences[id].parameter_number, size = preferences[id].size, configuration_value = new_parameter_value})) end end - device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) else log.info("info_changed running more than once. Cancelling this run. Time diff: " .. time_diff) end @@ -352,6 +358,7 @@ local inovelli_vzw32_sn = { lifecycle_handlers = { init = device_init, infoChanged = info_changed, + added = device_added, }, zwave_handlers = { [cc.CENTRAL_SCENE] = { @@ -371,7 +378,7 @@ local inovelli_vzw32_sn = { }, [capabilities.switchLevel.ID] = { [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_set - } + }, }, can_handle = can_handle_inovelli_vzw32 } diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua index 602e0147ad..d0e5eb9936 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -21,6 +21,7 @@ local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({ver local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) +local Association = (require "st.zwave.CommandClass.Association")({version=1}) local t_utils = require "integration_test.utils" -- Inovelli VZW32-SN device identifiers @@ -65,6 +66,18 @@ test.register_message_test( direction = "receive", message = { mock_inovelli_vzw32_sn.id, "added" }, }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Association:Set({ + grouping_identifier = 1, + node_ids = {}, -- Mock hub Z-Wave ID + payload = "\x01", -- Should contain grouping_identifier = 1 + }) + ) + }, { channel = "zwave", direction = "send", @@ -80,145 +93,106 @@ test.register_message_test( ) -- Test switch on command -test.register_message_test( +test.register_coroutine_test( "Switch on command should send Basic Set with ON value", - { - { - channel = "capability", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - { capability = "switch", command = "on", args = {} } - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "on", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, Basic:Set({ value = SwitchBinary.value.ON_ENABLE }) ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + ) + test.wait_for_events() + test.mock_time.advance_time(3) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, SwitchMultilevel:Get({}) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test switch off command -test.register_message_test( +test.register_coroutine_test( "Switch off command should send Basic Set with OFF value", - { - { - channel = "capability", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - { capability = "switch", command = "off", args = {} } - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switch", command = "off", args = {} } + }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, Basic:Set({ value = SwitchBinary.value.OFF_DISABLE }) ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + ) + test.wait_for_events() + test.mock_time.advance_time(3) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, SwitchMultilevel:Get({}) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test switch level command -test.register_message_test( +test.register_coroutine_test( "Switch level command should send SwitchMultilevel Set", - { - { - channel = "capability", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - { capability = "switchLevel", command = "setLevel", args = { level = 50 } } - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") + + test.socket.capability:__queue_receive({ + mock_inovelli_vzw32_sn.id, + { capability = "switchLevel", command = "setLevel", args = { 50 } } + }) + + local expected_command = SwitchMultilevel:Set({ value = 50, duration = "default" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, - SwitchMultilevel:Set({ value = 50, duration = "default" }) + expected_command ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + ) + + test.wait_for_events() + test.mock_time.advance_time(3) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, SwitchMultilevel:Get({}) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) --- Test color control command +-- Test central scene notifications test.register_message_test( - "Color control command should emit events and send configuration", + "Central scene notification should emit button events", { { - channel = "capability", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - { capability = "colorControl", command = "setColor", args = { color = { hue = 100, saturation = 50 } } } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.colorControl.hue(100)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.colorControl.saturation(50)) - }, - { - channel = "device_lifecycle", + channel = "zwave", direction = "receive", - message = mock_inovelli_vzw32_sn:generate_test_message("main", capabilities.switch.switch("on")) + message = { mock_inovelli_vzw32_sn.id, zw_test_utils.zwave_test_build_receive_command(CentralScene:Notification({ + scene_number = 1, + key_attributes=CentralScene.key_attributes.KEY_PRESSED_1_TIME + })) } }, { - channel = "zwave", + channel = "capability", direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 99, - configuration_value = 0, -- This would be calculated based on notification value - size = 4 - }) - ) + message = mock_inovelli_vzw32_sn:generate_test_message("button1", capabilities.button.button.pushed({ + state_change = true + })) }, }, { @@ -226,22 +200,22 @@ test.register_message_test( } ) --- Test central scene notifications +-- Test central scene notifications - button2 pressed 4 times test.register_message_test( - "Central scene notification should emit button events", + "Central scene notification button2 pressed 4 times should emit button events", { { channel = "zwave", direction = "receive", message = { mock_inovelli_vzw32_sn.id, zw_test_utils.zwave_test_build_receive_command(CentralScene:Notification({ - scene_number = 1, - key_attributes = CentralScene.key_attributes.KEY_PRESSED_1_TIME + scene_number = 2, + key_attributes=CentralScene.key_attributes.KEY_PRESSED_4_TIMES })) } }, { - channel = "device_lifecycle", - direction = "receive", - message = mock_inovelli_vzw32_sn:generate_test_message("button1", capabilities.button.button.pushed({ + channel = "capability", + direction = "send", + message = mock_inovelli_vzw32_sn:generate_test_message("button2", capabilities.button.button.pushed_4x({ state_change = true })) }, diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua index 2d622cb08f..14ad4a68ef 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -74,28 +74,33 @@ test.register_message_test( message = { mock_child_device.id, "added" }, }, { - channel = "device_lifecycle", - direction = "receive", + channel = "capability", + direction = "send", message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(1)) }, { - channel = "device_lifecycle", - direction = "receive", + channel = "capability", + direction = "send", message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(1)) }, { - channel = "device_lifecycle", - direction = "receive", + channel = "capability", + direction = "send", + message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ value = {minimum = 2700, maximum = 6500} })) + }, + { + channel = "capability", + direction = "send", message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(6500)) }, { - channel = "device_lifecycle", - direction = "receive", + channel = "capability", + direction = "send", message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(100)) }, { - channel = "device_lifecycle", - direction = "receive", + channel = "capability", + direction = "send", message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) }, }, @@ -105,203 +110,243 @@ test.register_message_test( ) -- Test child device switch on command -test.register_message_test( +test.register_coroutine_test( "Child device switch on should emit events and send configuration to parent", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child_device.id, - { capability = "switch", command = "on", args = {} } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = 100 -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "on", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_parent_device, Configuration:Set({ parameter_number = 99, - configuration_value = 0, -- This would be calculated based on notification value + configuration_value = notificationValue, size = 4 }) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test child device switch off command -test.register_message_test( +test.register_coroutine_test( "Child device switch off should emit events and send configuration to parent", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child_device.id, - { capability = "switch", command = "off", args = {} } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switch", command = "off", args = {} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("off")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_parent_device, Configuration:Set({ parameter_number = 99, - configuration_value = 0, + configuration_value = 0, -- Switch off sends 0 size = 4 }) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test child device level command -test.register_message_test( +test.register_coroutine_test( "Child device level command should emit events and send configuration to parent", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child_device.id, - { capability = "switchLevel", command = "setLevel", args = { level = 75 } } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switchLevel.level(75)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + local level = math.random(1, 99) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local effect = 1 -- Default notificationType + local color = 100 -- Default color for child devices (since device starts with no hue state) + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) -- Use the actual level from command + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "switchLevel", command = "setLevel", args = { level } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switchLevel.level(level)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_parent_device, Configuration:Set({ parameter_number = 99, - configuration_value = 0, -- This would be calculated based on notification value + configuration_value = notificationValue, size = 4 }) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test child device color command -test.register_message_test( +test.register_coroutine_test( "Child device color command should emit events and send configuration to parent", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child_device.id, - { capability = "colorControl", command = "setColor", args = { color = { hue = 200, saturation = 80 } } } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(200)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(80)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + local hue = math.random(0, 100) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + -- Calculate expected configuration value using the same logic as getNotificationValue + local function huePercentToValue(value) + if value <= 2 then + return 0 + elseif value >= 98 then + return 255 + else + return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent + end + end + + local notificationValue = 0 + local level = 100 -- Default level for child devices + local color = math.random(0, 100) -- Default color for child devices (since device starts with no hue state) + local effect = 1 -- Default notificationType + + notificationValue = notificationValue + (effect * 16777216) + notificationValue = notificationValue + (huePercentToValue(color) * 65536) + notificationValue = notificationValue + (level * 256) + notificationValue = notificationValue + (255 * 1) + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(color)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_parent_device, Configuration:Set({ parameter_number = 99, - configuration_value = 0, -- This would be calculated based on notification value + configuration_value = notificationValue, size = 4 }) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) -- Test child device color temperature command -test.register_message_test( +test.register_coroutine_test( "Child device color temperature command should emit events and send configuration to parent", - { - { - channel = "capability", - direction = "receive", - message = { - mock_child_device.id, - { capability = "colorTemperature", command = "setColorTemperature", args = { temperature = 3000 } } - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(3000)) - }, - { - channel = "device_lifecycle", - direction = "receive", - message = mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( + function() + local temp = math.random(2700, 6500) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + + test.socket.capability:__queue_receive({ + mock_child_device.id, + { capability = "colorTemperature", command = "setColorTemperature", args = { temp } } + }) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorControl.hue(100)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(temp)) + ) + + test.socket.capability:__expect_send( + mock_child_device:generate_test_message("main", capabilities.switch.switch("on")) + ) + + test.wait_for_events() + test.mock_time.advance_time(1) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( mock_parent_device, Configuration:Set({ parameter_number = 99, - configuration_value = 0, -- This would be calculated based on notification value + configuration_value = 33514751, -- Calculated: effect(1)*16777216 + hue(255)*65536 + level(100)*256 + 255 size = 4 }) ) - }, - }, - { - inner_block_ordering = "relaxed" - } + ) + end ) test.run_registered_tests() diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua index eb419b124f..21a872b960 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -15,10 +15,9 @@ local test = require "integration_test" local zw = require "st.zwave" local zw_test_utils = require "integration_test.zwave_test_utils" -local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) -local Association = (require "st.zwave.CommandClass.Association")({version=1}) +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) +local Association = (require "st.zwave.CommandClass.Association")({ version=1 }) local t_utils = require "integration_test.utils" -local json = require "st.json" -- Inovelli VZW32-SN device identifiers local INOVELLI_MANUFACTURER_ID = 0x031E @@ -29,12 +28,12 @@ local INOVELLI_VZW32_SN_PRODUCT_ID = 0x0001 local inovelli_vzw32_sn_endpoints = { { command_classes = { - {value = zw.SWITCH_BINARY}, - {value = zw.SWITCH_MULTILEVEL}, - {value = zw.BASIC}, - {value = zw.CONFIGURATION}, - {value = zw.CENTRAL_SCENE}, - {value = zw.ASSOCIATION}, + { value = zw.SWITCH_BINARY }, + { value = zw.SWITCH_MULTILEVEL }, + { value = zw.BASIC }, + { value = zw.CONFIGURATION }, + { value = zw.CENTRAL_SCENE }, + { value = zw.ASSOCIATION }, } } } @@ -53,294 +52,112 @@ local function test_init() end test.set_test_init_function(test_init) --- Test preference change for notification child creation -test.register_message_test( - "Enabling notification child preference should create child device", - { - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "added" }, - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", - json.encode({ - old_st_store = { - preferences = { - notificationChild = false - } - }, - preferences = { - notificationChild = true - } - }) - } - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "device_created" }, - }, - }, - { - inner_block_ordering = "relaxed" - } -) +-- Test parameter 1 (example preference) +do + local new_param_value = 10 + test.register_coroutine_test( + "Parameter 1 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter1 = new_param_value}})) --- Test preference change for configuration parameters -test.register_message_test( - "Changing configuration preference should send Configuration Set command", - { - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "added" }, - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", - json.encode({ - old_st_store = { - preferences = { - ledIntensity = 50 - } - }, - preferences = { - ledIntensity = 75 - } - }) - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 1, -- Example parameter number - configuration_value = 75, - size = 1 - }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 1, + configuration_value = new_param_value, + size = 1 + }) + ) ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Association:Set({ - grouping_identifier = 1, - node_ids = {1} -- Mock hub Z-Wave ID - }) - ) - }, - }, - { - inner_block_ordering = "relaxed" - } -) + end + ) +end --- Test multiple preference changes -test.register_message_test( - "Multiple preference changes should send multiple Configuration Set commands", - { - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "added" }, - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", - json.encode({ - old_st_store = { - preferences = { - ledIntensity = 50, - ledColorWhenOn = 1, - ledColorWhenOff = 2 - } - }, - preferences = { - ledIntensity = 75, - ledColorWhenOn = 3, - ledColorWhenOff = 4 - } - }) - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 1, - configuration_value = 75, - size = 1 - }) - ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 2, - configuration_value = 3, - size = 1 - }) - ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 3, - configuration_value = 4, - size = 1 - }) - ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Association:Set({ - grouping_identifier = 1, - node_ids = {1} - }) - ) - }, - }, - { - inner_block_ordering = "relaxed" - } -) +-- Test parameter 52 (example preference) +do + local new_param_value = 25 + test.register_coroutine_test( + "Parameter 52 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter52 = new_param_value}})) --- Test boolean preference handling -test.register_message_test( - "Boolean preference should be converted to numeric value", - { - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "added" }, - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", - json.encode({ - old_st_store = { - preferences = { - ledEnabled = false - } - }, - preferences = { - ledEnabled = true - } - }) - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 4, - configuration_value = 1, -- true converted to 1 - size = 1 - }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 52, + configuration_value = new_param_value, + size = 1 + }) + ) ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Association:Set({ - grouping_identifier = 1, - node_ids = {1} - }) - ) - }, - }, - { - inner_block_ordering = "relaxed" - } -) + end + ) +end --- Test signed integer preference handling -test.register_message_test( - "Large signed integer preference should be handled correctly", - { - { - channel = "device_lifecycle", - direction = "receive", - message = { mock_inovelli_vzw32_sn.id, "added" }, - }, - { - channel = "device_lifecycle", - direction = "receive", - message = { - mock_inovelli_vzw32_sn.id, - "infoChanged", - json.encode({ - old_st_store = { - preferences = { - largeValue = 100 - } - }, - preferences = { - largeValue = 3000000000 -- Large value that would overflow 32-bit signed int - } - }) - } - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Configuration:Set({ - parameter_number = 5, - configuration_value = -1294967296, -- Correctly calculated signed value - size = 4 - }) +-- Test parameter 158 (example preference) +do + local new_param_value = 5 + test.register_coroutine_test( + "Parameter 158 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter158 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 158, + configuration_value = new_param_value, + size = 1 + }) + ) ) - }, - { - channel = "zwave", - direction = "send", - message = zw_test_utils.zwave_test_build_send_command( - mock_inovelli_vzw32_sn, - Association:Set({ - grouping_identifier = 1, - node_ids = {1} - }) + end + ) +end + +-- Test parameter 101 (2-byte parameter) +do + local new_param_value = -400 + test.register_coroutine_test( + "Parameter 101 should be updated in the device configuration after change", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {parameter101 = new_param_value}})) + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_inovelli_vzw32_sn, + Configuration:Set({ + parameter_number = 101, + configuration_value = new_param_value, + size = 2 + }) + ) ) - }, - }, - { - inner_block_ordering = "relaxed" - } -) + end + ) +end + +-- Test notificationChild preference (special case for child device creation) +do + local new_param_value = true + test.register_coroutine_test( + "notificationChild preference should create child device when enabled", + function() + test.socket.device_lifecycle:__queue_receive(mock_inovelli_vzw32_sn:generate_info_changed({preferences = {notificationChild = new_param_value}})) + + -- Expect child device creation + mock_inovelli_vzw32_sn:expect_device_create({ + type = "EDGE_CHILD", + label = "nil Notification", -- This will be the parent label + "Notification" + profile = "rgbw-bulb", + parent_device_id = mock_inovelli_vzw32_sn.id, + parent_assigned_child_key = "notification" + }) + end + ) +end test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua deleted file mode 100644 index 017d11f5f1..0000000000 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_utils.lua +++ /dev/null @@ -1,241 +0,0 @@ --- Copyright 2025 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. - --- Unit tests for Inovelli VZW32-SN utility functions --- This file tests the helper functions used in the driver - -local capabilities = require "st.capabilities" -local utils = require "st.utils" - --- Test huePercentToValue function -local function test_hue_percent_to_value() - local huePercentToValue = function(value) - if value <= 2 then - return 0 - elseif value >= 98 then - return 255 - else - return utils.round(value / 100 * 255) - end - end - - -- Test edge cases - assert(huePercentToValue(0) == 0, "Value 0 should return 0") - assert(huePercentToValue(1) == 0, "Value 1 should return 0") - assert(huePercentToValue(2) == 0, "Value 2 should return 0") - assert(huePercentToValue(98) == 255, "Value 98 should return 255") - assert(huePercentToValue(99) == 255, "Value 99 should return 255") - assert(huePercentToValue(100) == 255, "Value 100 should return 255") - - -- Test middle values - assert(huePercentToValue(50) == 128, "Value 50 should return 128") - assert(huePercentToValue(25) == 64, "Value 25 should return 64") - assert(huePercentToValue(75) == 191, "Value 75 should return 191") -end - --- Test preferences_to_numeric_value function -local function test_preferences_to_numeric_value() - local preferences_to_numeric_value = function(new_value) - local numeric = tonumber(new_value) - if numeric == nil then -- in case the value is boolean - numeric = new_value and 1 or 0 - end - return numeric - end - - -- Test numeric values - assert(preferences_to_numeric_value("50") == 50, "String '50' should return 50") - assert(preferences_to_numeric_value(75) == 75, "Number 75 should return 75") - - -- Test boolean values - assert(preferences_to_numeric_value(true) == 1, "Boolean true should return 1") - assert(preferences_to_numeric_value(false) == 0, "Boolean false should return 0") - - -- Test nil values - assert(preferences_to_numeric_value(nil) == 0, "Nil should return 0") -end - --- Test preferences_calculate_parameter function -local function test_preferences_calculate_parameter() - local preferences_calculate_parameter = function(new_value, type, number) - if type == 4 and new_value > 2147483647 then - return ((4294967296 - new_value) * -1) - elseif type == 2 and new_value > 32767 then - return ((65536 - new_value) * -1) - elseif type == 1 and new_value > 127 then - return ((256 - new_value) * -1) - else - return new_value - end - end - - -- Test normal values - assert(preferences_calculate_parameter(50, 1, 1) == 50, "Small value should remain unchanged") - assert(preferences_calculate_parameter(100, 2, 2) == 100, "Medium value should remain unchanged") - assert(preferences_calculate_parameter(1000, 4, 4) == 1000, "Large value should remain unchanged") - - -- Test overflow cases - assert(preferences_calculate_parameter(200, 1, 1) == -56, "Byte overflow should be handled") - assert(preferences_calculate_parameter(40000, 2, 2) == -25536, "Word overflow should be handled") - assert(preferences_calculate_parameter(3000000000, 4, 4) == -1294967296, "Dword overflow should be handled") -end - --- Test component_to_endpoint function -local function test_component_to_endpoint() - local component_to_endpoint = function(device, component_id) - local ep_num = component_id:match("switch(%d)") - return { ep_num and tonumber(ep_num) } - end - - -- Test valid component IDs - local result1 = component_to_endpoint(nil, "switch1") - assert(result1[1] == 1, "switch1 should map to endpoint 1") - - local result2 = component_to_endpoint(nil, "switch2") - assert(result2[1] == 2, "switch2 should map to endpoint 2") - - -- Test invalid component IDs - local result3 = component_to_endpoint(nil, "main") - assert(result3[1] == nil, "main should map to nil endpoint") - - local result4 = component_to_endpoint(nil, "button1") - assert(result4[1] == nil, "button1 should map to nil endpoint") -end - --- Test endpoint_to_component function -local function test_endpoint_to_component() - local mock_device = { - profile = { - components = { - switch1 = {}, - switch2 = {}, - main = {} - } - } - } - - local endpoint_to_component = function(device, ep) - local switch_comp = string.format("switch%d", ep) - if device.profile.components[switch_comp] ~= nil then - return switch_comp - else - return "main" - end - end - - -- Test valid endpoints - assert(endpoint_to_component(mock_device, 1) == "switch1", "Endpoint 1 should map to switch1") - assert(endpoint_to_component(mock_device, 2) == "switch2", "Endpoint 2 should map to switch2") - - -- Test invalid endpoints - assert(endpoint_to_component(mock_device, 3) == "main", "Endpoint 3 should map to main") - assert(endpoint_to_component(mock_device, 0) == "main", "Endpoint 0 should map to main") -end - --- Test button_to_component function -local function test_button_to_component() - local button_to_component = function(buttonId) - if buttonId > 0 then - return string.format("button%d", buttonId) - end - end - - -- Test valid button IDs - assert(button_to_component(1) == "button1", "Button ID 1 should return button1") - assert(button_to_component(2) == "button2", "Button ID 2 should return button2") - assert(button_to_component(3) == "button3", "Button ID 3 should return button3") - - -- Test invalid button IDs - assert(button_to_component(0) == nil, "Button ID 0 should return nil") - assert(button_to_component(-1) == nil, "Button ID -1 should return nil") -end - --- Test getNotificationValue function -local function test_get_notification_value() - local mock_device = { - get_latest_state = function(self, component, capability_id, attribute) - if capability_id == "switchLevel" and attribute == "level" then - return 75 - elseif capability_id == "colorControl" and attribute == "hue" then - return 100 - end - return nil - end, - get_parent_device = function(self) - return { - preferences = { - notificationType = 2 - } - } - end - } - - local huePercentToValue = function(value) - if value <= 2 then - return 0 - elseif value >= 98 then - return 255 - else - return utils.round(value / 100 * 255) - end - end - - local getNotificationValue = function(device, value) - local notificationValue = 0 - local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 - local color = utils.round(device:get_latest_state("main", capabilities.colorControl.ID, capabilities.colorControl.hue.NAME) or 100) - local effect = device:get_parent_device().preferences.notificationType or 1 - notificationValue = notificationValue + (effect*16777216) - notificationValue = notificationValue + (huePercentToValue(value or color)*65536) - notificationValue = notificationValue + (level*256) - notificationValue = notificationValue + (255*1) - return notificationValue - end - - -- Test with default values - local result = getNotificationValue(mock_device) - -- Expected: 2*16777216 + 255*65536 + 75*256 + 255*1 = 33554432 + 16711680 + 19200 + 255 = 50285567 - assert(result == 50285567, "Notification value calculation should be correct") -end - --- Run all tests -local function run_tests() - print("Running Inovelli VZW32-SN utility function tests...") - - test_hue_percent_to_value() - print("✓ huePercentToValue tests passed") - - test_preferences_to_numeric_value() - print("✓ preferences_to_numeric_value tests passed") - - test_preferences_calculate_parameter() - print("✓ preferences_calculate_parameter tests passed") - - test_component_to_endpoint() - print("✓ component_to_endpoint tests passed") - - test_endpoint_to_component() - print("✓ endpoint_to_component tests passed") - - test_button_to_component() - print("✓ button_to_component tests passed") - - test_get_notification_value() - print("✓ getNotificationValue tests passed") - - print("All utility function tests passed!") -end - --- Execute tests -run_tests() From 3b8a626842475e3e78653b5ac6eb11e3447d8a20 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Thu, 23 Oct 2025 23:39:58 -0600 Subject: [PATCH 29/32] Remove unused code --- .../src/inovelli-vzw32-sn/init.lua | 36 ------------------- .../test_inovelli_vzw32_sn_preferences.lua | 1 - 2 files changed, 37 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 06a2be8297..2533bf4370 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -115,42 +115,6 @@ local function add_child(driver,parent,profile,child_type) driver:try_create_device(child_metadata) end -local function initialize(device) - if device:get_latest_state("main", capabilities.illuminanceMeasurement.ID, capabilities.illuminanceMeasurement.illuminance.NAME) == nil then - device:emit_event(capabilities.illuminanceMeasurement.illuminance(0)) - end - if device:get_latest_state("main", capabilities.motionSensor.ID, capabilities.motionSensor.motion.NAME) == nil then - device:emit_event(capabilities.motionSensor.motion.active()) - end - if device:get_latest_state("main", capabilities.powerMeter.ID, capabilities.powerMeter.power.NAME) == nil then - device:emit_event(capabilities.powerMeter.power(0)) - end - if device:get_latest_state("main", capabilities.energyMeter.ID, capabilities.energyMeter.energy.NAME) == nil then - device:emit_event(capabilities.energyMeter.energy(0)) - end - if device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) == nil then - device:emit_event(capabilities.switchLevel.level(0)) - end - - for _, component in pairs(device.profile.components) do - if string.find(component.id, "button") ~= nil then - if device:get_latest_state(component.id, capabilities.button.ID, capabilities.button.supportedButtonValues.NAME) == nil then - device:emit_component_event( - component, - capabilities.button.supportedButtonValues( - {"pushed","held","down_hold","pushed_2x","pushed_3x","pushed_4x","pushed_5x"}, - { visibility = { displayed = false } } - ) - ) - device:emit_component_event( - component, - capabilities.button.numberOfButtons({value = 1}, { visibility = { displayed = false } }) - ) - end - end - end -end - local function getNotificationValue(device, value) local notificationValue = 0 local level = device:get_latest_state("main", capabilities.switchLevel.ID, capabilities.switchLevel.level.NAME) or 100 diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua index 21a872b960..a63534ceb9 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_preferences.lua @@ -16,7 +16,6 @@ local test = require "integration_test" local zw = require "st.zwave" local zw_test_utils = require "integration_test.zwave_test_utils" local Configuration = (require "st.zwave.CommandClass.Configuration")({ version=4 }) -local Association = (require "st.zwave.CommandClass.Association")({ version=1 }) local t_utils = require "integration_test.utils" -- Inovelli VZW32-SN device identifiers From 0b818be84ecfbc587050b212f5bc7a4efc9e01b8 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Fri, 24 Oct 2025 00:57:40 -0600 Subject: [PATCH 30/32] linter fixes --- .../src/test/test_inovelli_vzw32_sn.lua | 9 +++--- .../src/test/test_inovelli_vzw32_sn_child.lua | 29 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua index d0e5eb9936..27b63a481f 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn.lua @@ -19,7 +19,6 @@ local zw_test_utils = require "integration_test.zwave_test_utils" local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({version=2}) local SwitchMultilevel = (require "st.zwave.CommandClass.SwitchMultilevel")({version=4}) local Basic = (require "st.zwave.CommandClass.Basic")({version=1}) -local Configuration = (require "st.zwave.CommandClass.Configuration")({version=4}) local CentralScene = (require "st.zwave.CommandClass.CentralScene")({version=3}) local Association = (require "st.zwave.CommandClass.Association")({version=1}) local t_utils = require "integration_test.utils" @@ -149,12 +148,12 @@ test.register_coroutine_test( "Switch level command should send SwitchMultilevel Set", function() test.timer.__create_and_queue_test_time_advance_timer(3, "oneshot") - + test.socket.capability:__queue_receive({ mock_inovelli_vzw32_sn.id, { capability = "switchLevel", command = "setLevel", args = { 50 } } }) - + local expected_command = SwitchMultilevel:Set({ value = 50, duration = "default" }) test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( @@ -162,10 +161,10 @@ test.register_coroutine_test( expected_command ) ) - + test.wait_for_events() test.mock_time.advance_time(3) - + test.socket.zwave:__expect_send( zw_test_utils.zwave_test_build_send_command( mock_inovelli_vzw32_sn, diff --git a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua index 14ad4a68ef..c99cdf5fc8 100644 --- a/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua +++ b/drivers/SmartThings/zwave-switch/src/test/test_inovelli_vzw32_sn_child.lua @@ -114,7 +114,7 @@ test.register_coroutine_test( "Child device switch on should emit events and send configuration to parent", function() test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - + -- Calculate expected configuration value using the same logic as getNotificationValue local function huePercentToValue(value) if value <= 2 then @@ -125,17 +125,17 @@ test.register_coroutine_test( return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent end end - + local notificationValue = 0 local level = 100 -- Default level for child devices local color = 100 -- Default color for child devices (since device starts with no hue state) local effect = 1 -- Default notificationType - + notificationValue = notificationValue + (effect * 16777216) notificationValue = notificationValue + (huePercentToValue(color) * 65536) notificationValue = notificationValue + (level * 256) notificationValue = notificationValue + (255 * 1) - + test.socket.capability:__queue_receive({ mock_child_device.id, { capability = "switch", command = "on", args = {} } @@ -166,7 +166,7 @@ test.register_coroutine_test( "Child device switch off should emit events and send configuration to parent", function() test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - + test.socket.capability:__queue_receive({ mock_child_device.id, { capability = "switch", command = "off", args = {} } @@ -198,7 +198,7 @@ test.register_coroutine_test( function() local level = math.random(1, 99) test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - + -- Calculate expected configuration value using the same logic as getNotificationValue local function huePercentToValue(value) if value <= 2 then @@ -209,16 +209,16 @@ test.register_coroutine_test( return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent end end - + local notificationValue = 0 local effect = 1 -- Default notificationType local color = 100 -- Default color for child devices (since device starts with no hue state) - + notificationValue = notificationValue + (effect * 16777216) notificationValue = notificationValue + (huePercentToValue(color) * 65536) notificationValue = notificationValue + (level * 256) -- Use the actual level from command notificationValue = notificationValue + (255 * 1) - + test.socket.capability:__queue_receive({ mock_child_device.id, { capability = "switchLevel", command = "setLevel", args = { level } } @@ -252,9 +252,8 @@ test.register_coroutine_test( test.register_coroutine_test( "Child device color command should emit events and send configuration to parent", function() - local hue = math.random(0, 100) test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - + -- Calculate expected configuration value using the same logic as getNotificationValue local function huePercentToValue(value) if value <= 2 then @@ -265,17 +264,17 @@ test.register_coroutine_test( return math.floor(value / 100 * 255 + 0.5) -- utils.round equivalent end end - + local notificationValue = 0 local level = 100 -- Default level for child devices local color = math.random(0, 100) -- Default color for child devices (since device starts with no hue state) local effect = 1 -- Default notificationType - + notificationValue = notificationValue + (effect * 16777216) notificationValue = notificationValue + (huePercentToValue(color) * 65536) notificationValue = notificationValue + (level * 256) notificationValue = notificationValue + (255 * 1) - + test.socket.capability:__queue_receive({ mock_child_device.id, { capability = "colorControl", command = "setColor", args = {{ hue = color, saturation = 100 }} } @@ -315,7 +314,7 @@ test.register_coroutine_test( function() local temp = math.random(2700, 6500) test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") - + test.socket.capability:__queue_receive({ mock_child_device.id, { capability = "colorTemperature", command = "setColorTemperature", args = { temp } } From ca49e9183eccf03c2514081eca1ce71212bb900a Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Sat, 25 Oct 2025 13:37:22 -0600 Subject: [PATCH 31/32] remove unused init function --- .../zwave-switch/src/inovelli-vzw32-sn/init.lua | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 2533bf4370..6295cb8f23 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -197,14 +197,6 @@ local function can_handle_inovelli_vzw32(opts, driver, device, ...) return false end -local device_init = function(self, device) - if device.network_type ~= st_device.NETWORK_TYPE_CHILD then - device:set_component_to_endpoint_fn(component_to_endpoint) - device:set_endpoint_to_component_fn(endpoint_to_component) - --initialize(device) - end -end - local function device_added(driver, device) if device.network_type ~= st_device.NETWORK_TYPE_CHILD then device:send(Association:Set({grouping_identifier = 1, node_ids = {driver.environment_info.hub_zwave_id}})) @@ -320,7 +312,6 @@ end local inovelli_vzw32_sn = { NAME = "inovelli vzw32-sn handler", lifecycle_handlers = { - init = device_init, infoChanged = info_changed, added = device_added, }, From 6c324d1d3bb28a2b302a6391b34c5deaaea22e86 Mon Sep 17 00:00:00 2001 From: InovelliUSA Date: Sat, 25 Oct 2025 13:41:21 -0600 Subject: [PATCH 32/32] fixing linter error --- .../src/inovelli-vzw32-sn/init.lua | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua index 6295cb8f23..8f4c5f974b 100644 --- a/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua +++ b/drivers/SmartThings/zwave-switch/src/inovelli-vzw32-sn/init.lua @@ -43,30 +43,6 @@ local INOVELLI_VZW32_SN_FINGERPRINTS = { { mfr = 0x031E, prod = 0x0017, model = 0x0001 } -- Inovelli VZW32-SN } ---- Map component to end_points(channels) ---- ---- @param device st.zwave.Device ---- @param component_id string ID ---- @return table dst_channels destination channels e.g. {2} for Z-Wave channel 2 or {} for unencapsulated -local function component_to_endpoint(device, component_id) - local ep_num = component_id:match("switch(%d)") - return { ep_num and tonumber(ep_num) } -end - ---- Map end_point(channel) to Z-Wave endpoint 9 channel) ---- ---- @param device st.zwave.Device ---- @param ep number the endpoint(Z-Wave channel) ID to find the component for ---- @return string the component ID the endpoint matches to -local function endpoint_to_component(device, ep) - local switch_comp = string.format("switch%d", ep) - if device.profile.components[switch_comp] ~= nil then - return switch_comp - else - return "main" - end -end - local function button_to_component(buttonId) if buttonId > 0 then return string.format("button%d", buttonId)