From ad6d869708acbae4bc85c5e89d2e6db2a739f79a Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 27 Oct 2025 16:13:45 +0100 Subject: [PATCH 1/2] add aeotec door window sensor 8 --- .../SmartThings/zwave-sensor/fingerprints.yml | 5 + .../profiles/aeotec-door-window-sensor-8.yml | 128 +++++++++++++ .../src/aeotec-door-window-sensor-8/init.lua | 168 ++++++++++++++++++ drivers/SmartThings/zwave-sensor/src/init.lua | 1 + .../zwave-sensor/src/preferences.lua | 26 +++ 5 files changed, 328 insertions(+) create mode 100644 drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml create mode 100644 drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua diff --git a/drivers/SmartThings/zwave-sensor/fingerprints.yml b/drivers/SmartThings/zwave-sensor/fingerprints.yml index 727f02a2d6..8b3a35fa3e 100644 --- a/drivers/SmartThings/zwave-sensor/fingerprints.yml +++ b/drivers/SmartThings/zwave-sensor/fingerprints.yml @@ -548,6 +548,11 @@ zwaveManufacturer: productType: 0x0100 productId: 0x0082 deviceProfileName: shelly-wave-motion + - id: "aeotec/contact/8" + deviceLabel: Aeotec Door Window Sensor 8 + manufacturerId: 0x0371 + productId: 0x0037 + deviceProfileName: aeotec-door-window-sensor-8 zwaveGeneric: - id: "GenericSensorAlarm" deviceLabel: Z-Wave Sensor diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml new file mode 100644 index 0000000000..18fe821211 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -0,0 +1,128 @@ +name: aeotec-door-window-sensor-8 +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: temperatureMeasurement + config: + values: + - key: "temperature.value" + range: [-10, 60] + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: dewPoint + version: 1 + - id: moldHealthConcern + version: 1 + - id: tamperAlert + version: 1 + - id: powerSource + version: 1 + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - name: "parameter1" + title: "1 Set threshold Check Time" + description: "When using battery power, follow this configuration, the minimum time is 30 seconds. When using USB power supply, for real-time detection." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 900 + - name: "parameter2" + title: "2 Min. temperature change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 20 + - name: "parameter3" + title: "3 Min. humidity change to report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 50 + - name: "parameter4" + title: "4 Enable led indication" + description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." + required: false + preferenceType: enumeration + definition: + options: + 0: "Off" + 1: "On" + default: 0 + - name: "parameter5" + title: "5 Contact sensor polarity" + description: "This parameter allows to set the states of door/window when the magnet closes to the sensor." + required: false + preferenceType: enumeration + definition: + options: + 0: "Closed = Alarm, Open = Idle" + 1: "Open = Alarm, Closed = Idle" + default: 0 + - name: "parameter13" + title: "13 Mold alarm offset" + desccription: "Increase the humidity threshold." + required: false + preferenceType: integer + definition: + minimum: -10 + maximum : 10 + default: 0 + - name: "parameter23" + title: "23 Low battery threshold" + description: "Report low battery report when level goes under threshold setting." + required: false + preferenceType: integer + definition: + minimum: 10 + maximum : 50 + default: 20 + - name: "parameter24" + title: "24 Periodic Reports" + description: "The period of battery, temperature and humidity report, the minimum time is 30 seconds." + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 2678400 + default: 43200 + - name: "parameter25" + title: "25 Offset value for temperature" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter26" + title: "26 Offset value for Humidity" + description: "Calibrate humidity." + required: false + preferenceType: integer + definition: + minimum: -200 + maximum : 200 + default: 0 + - name: "parameter64" + title: "64 Temperature Scale" + description: "Scale for auto reports and settings." + required: false + preferenceType: enumeration + definition: + options: + 0: "Celsius" + 1: "Fahrenheit" \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua new file mode 100644 index 0000000000..b968cc8269 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -0,0 +1,168 @@ +-- 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 +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorBinary +local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) + +local log = require "log" +local utils = require "st.utils" + +local MoldHealthConcern = capabilities.moldHealthConcern +local ContactSensor = capabilities.contactSensor + +local AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS = { + { manufacturerId = 0x0371, productId = 0x0037 } -- Aeotec Door Window Sensor 8 EU/US/AU +} + +local function can_handle_aeotec_door_window_sensor_8(opts, driver, device, ...) + for _, fingerprint in ipairs(AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + local subdriver = require("aeotec-door-window-sensor-8") + return true, subdriver + end + end + return false +end + +local function added_handler(driver, device) + device:send(Configuration:Get({ parameter_number = 10 })) + + -- Enable binary sensor report + device:send(Configuration:Set({ + parameter_number = 22, + size = 1, + configuration_value = 1 + })) + + device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + + device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) -- Mold + device:send(Battery:Get({})) + + device:emit_event(capabilities.refresh.refresh()) +end + +local function do_refresh(driver, device) + device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) --Mold + + device:send(Battery:Get({})) + + device:send(Configuration:Get({ parameter_number = 10 })) +end + +local function notification_report_handler(self, device, cmd) + local event + + -- DOOR_WINDOW/TILT + if cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL then + if cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_CLOSED then + event = capabilities.contactSensor.contact.closed() + elseif cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_OPEN then + event = capabilities.contactSensor.contact.open() + end + end + + -- POWER + if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then + event = capabilities.powerSource.powerSource.battery() + elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then + event = capabilities.powerSource.powerSource.mains() + elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then + device:send(Battery:Get({})) + end + end + + -- MOLD + if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then + if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then + event = capabilities.moldHealthConcern.moldHealthConcern.good() + elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then + event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + end + end + + if (event ~= nil) then + device:emit_event(event) + end +end + +local function sensor_binary_report_handler(self, device, cmd) + local sensorType = cmd.args.sensor_type + local value = cmd.args.sensor_value + local event + + local field_name = "initial_state_set_" .. sensorType + + if not device:get_field(field_name) then + log.debug("sensor_binary_report_handler") + -- MOLD + if sensorType == SensorBinary.sensor_type.GENERAL then + if value == SensorBinary.sensor_value.IDLE then + event = capabilities.moldHealthConcern.moldHealthConcern.good() + elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then + event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + end + end + + -- DOOR_WINDOW/TILT + if sensorType == SensorBinary.sensor_type.DOOR_WINDOW or sensorType == SensorBinary.sensor_type.TILT then + if value == SensorBinary.sensor_value.IDLE then + event = capabilities.contactSensor.contact.closed() + elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then + event = capabilities.contactSensor.contact.open() + end + end + + if (event ~= nil) then + device:emit_event(event) + device:set_field(field_name, true) + end + end +end + +local aeotec_door_window_sensor_8 = { + supported_capabilities = { + capabilities.powerSource, + }, + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + [cc.SENSOR_BINARY] = { + [SensorBinary.REPORT] = sensor_binary_report_handler + }, + }, + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + lifecycle_handlers = { + added = added_handler, + }, + NAME = "Aeotec Door Window Sesnor 8", + can_handle = can_handle_aeotec_door_window_sensor_8 +} + +return aeotec_door_window_sensor_8 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-sensor/src/init.lua b/drivers/SmartThings/zwave-sensor/src/init.lua index d92a09fe56..b7d3c080bc 100644 --- a/drivers/SmartThings/zwave-sensor/src/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/init.lua @@ -153,6 +153,7 @@ local driver_template = { lazy_load_if_possible("timed-tamper-clear"), lazy_load_if_possible("wakeup-no-poll"), lazy_load_if_possible("apiv6_bugfix"), + lazy_load_if_possible("aeotec-door-window-sensor-8"), }, lifecycle_handlers = { added = added_handler, diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index 4921a996bf..da69e954c6 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -162,7 +162,33 @@ local devices = { motionNotdetRepT = {parameter_number = 160, size = 2}, }, }, + AEOTEC_DOOR_WINDOW_SENSOR_8 = { + MATCHING_MATRIX = { + mfrs = 0x0371, + product_types = {0x0000, 0x0001, 0x0002}, + product_ids = {0x0037} + }, + PARAMETERS = { + parameter1 = {parameter_number = 1, size = 1}, + parameter2 = {parameter_number = 2, size = 1}, + parameter3 = {parameter_number = 3, size = 1}, + parameter4 = {parameter_number = 4, size = 1}, + parameter5 = {parameter_number = 5, size = 1}, + parameter13 = {parameter_number = 13, size = 1}, + parameter23 = {parameter_number = 23, size = 1}, + parameter24 = {parameter_number = 24, size = 4}, + parameter25 = {parameter_number = 25, size = 2}, + parameter26 = {parameter_number = 26, size = 2}, + parameter27 = {parameter_number = 27, size = 1}, + parameter28 = {parameter_number = 28, size = 1}, + parameter33 = {parameter_number = 33, size = 1}, + parameter34 = {parameter_number = 34, size = 1}, + parameter35 = {parameter_number = 35, size = 1}, + parameter64 = {parameter_number = 64, size = 1}, + }, + }, } + local preferences = {} preferences.update_preferences = function(driver, device, args) From c3b353ab922039763b232e4c3825e8bf404456a1 Mon Sep 17 00:00:00 2001 From: mh-zwave Date: Mon, 2 Feb 2026 09:56:17 +0100 Subject: [PATCH 2/2] update --- .../profiles/aeotec-door-window-sensor-8.yml | 89 +++- .../src/aeotec-door-window-sensor-8/init.lua | 127 +++--- .../zwave-sensor/src/preferences.lua | 4 +- .../test/test_aeotec_door_window_sesnor_8.lua | 397 ++++++++++++++++++ 4 files changed, 540 insertions(+), 77 deletions(-) create mode 100644 drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua diff --git a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml index 18fe821211..b741e201ba 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/aeotec-door-window-sensor-8.yml @@ -6,9 +6,9 @@ components: version: 1 - id: temperatureMeasurement config: - values: - - key: "temperature.value" - range: [-10, 60] + values: + - key: "temperature.value" + range: [-10, 60] version: 1 - id: relativeHumidityMeasurement version: 1 @@ -20,12 +20,18 @@ components: version: 1 - id: powerSource version: 1 + - id: threeAxis + version: 1 - id: battery version: 1 - id: refresh version: 1 - categories: +categories: - name: ContactSensor +metadata: + deviceType: ContactSensor + ocfDeviceType: x.com.st.d.sensor.contact + deviceTypeId: ContactSensor preferences: - name: "parameter1" title: "1 Set threshold Check Time" @@ -52,25 +58,15 @@ preferences: minimum: 0 maximum : 255 default: 50 - - name: "parameter4" - title: "4 Enable led indication" - description: "This parameter defines when the green or red LED will indicate events. Disabling all indications may extend battery life. Off means no indications." - required: false - preferenceType: enumeration - definition: - options: - 0: "Off" - 1: "On" - default: 0 - name: "parameter5" - title: "5 Contact sensor polarity" + title: "5 State when the magnet is close" description: "This parameter allows to set the states of door/window when the magnet closes to the sensor." required: false preferenceType: enumeration definition: options: - 0: "Closed = Alarm, Open = Idle" - 1: "Open = Alarm, Closed = Idle" + 0: "Open=magnet far, Closed=magnet near" + 1: "Closed=magnet far, Open=magnet near" default: 0 - name: "parameter13" title: "13 Mold alarm offset" @@ -101,7 +97,7 @@ preferences: default: 43200 - name: "parameter25" title: "25 Offset value for temperature" - description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F" + description: "Calibrate temperature. Scale is defined by Param 64 .eg: Value 15 means 1.5°C or 1.5F." required: false preferenceType: integer definition: @@ -117,6 +113,63 @@ preferences: minimum: -200 maximum : 200 default: 0 + - name: "parameter27" + title: "27 Set tilt sensor mode" + description: "Set tilt sensor mode." + required: false + preferenceType: enumeration + definition: + options: + 0: "Disable" + 1: "Enabled. Needs magnet" + 2: "Enabled. It can used without magnet." + default: 1 + - name: "parameter28" + title: "28 State of tilt in Mode 2" + description: "This parameter allows setting the state of door/window when the sensor is tilted." + required: false + preferenceType: enumeration + definition: + options: + 0: "Opened = tilted, closed = not tilted" + 1: "Closed = tilted, opened = not tilted" + default: 0 + - name: "parameter33" + title: "33 Tilt triggered angle" + description: "With this parameter, you can adjust the tilt triggered angle if the tilt is too low or too strong." + required: false + preferenceType: integer + definition: + minimum: 1 + maximum : 90 + default: 5 + - name: "parameter34" + title: "34 Timeout tilt detection Mode 1" + description: "Set the timeout of tilt detection Mode 1." + required: false + preferenceType: integer + definition: + minimum: 5 + maximum : 60 + default: 5 + - name: "parameter35" + title: "35 Timeout tilt detection Mode 2" + description: "Set the timeout of tilt detection Mode 2." + required: false + preferenceType: integer + definition: + minimum: 5 + maximum : 60 + default: 8 + - name: "parameter36" + title: "36 Min. acc. change to report" + description: "Minimum acceleration change to trigger report" + required: false + preferenceType: integer + definition: + minimum: 0 + maximum : 255 + default: 0 - name: "parameter64" title: "64 Temperature Scale" description: "Scale for auto reports and settings." diff --git a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua index b968cc8269..f28ffdcf4a 100644 --- a/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/aeotec-door-window-sensor-8/init.lua @@ -19,16 +19,18 @@ local cc = require "st.zwave.CommandClass" local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.zwave.CommandClass.Battery local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) ---- @type st.zwave.CommandClass.SensorBinary -local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 }) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 11 }) --- @type st.zwave.CommandClass.Configuration local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) local log = require "log" -local utils = require "st.utils" local MoldHealthConcern = capabilities.moldHealthConcern local ContactSensor = capabilities.contactSensor +local PowerSource = capabilities.powerSource +local ThreeAxis = capabilities.threeAxis +local TamperAlert = capabilities.tamperAlert local AEOTEC_DOOR_WINDOW_SENSOR_8_FINGERPRINTS = { { manufacturerId = 0x0371, productId = 0x0037 } -- Aeotec Door Window Sensor 8 EU/US/AU @@ -47,47 +49,45 @@ end local function added_handler(driver, device) device:send(Configuration:Get({ parameter_number = 10 })) - -- Enable binary sensor report - device:send(Configuration:Set({ - parameter_number = 22, - size = 1, - configuration_value = 1 - })) - device:emit_event(MoldHealthConcern.supportedMoldValues({"good", "moderate"})) + + -- Default value + device:emit_event(MoldHealthConcern.moldHealthConcern.good()) - device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) -- Mold + -- Default value + device:emit_event(PowerSource.powerSource.battery()) + device:send(Battery:Get({})) +end - device:emit_event(capabilities.refresh.refresh()) +local function device_init(driver, device) + device:set_field("three_axis_x", 0) + device:set_field("three_axis_y", 0) + device:set_field("three_axis_z", 0) end local function do_refresh(driver, device) - device:send(SensorBinary:Get({sensor_type = SensorBinary.sensor_type.GENERAL})) --Mold - device:send(Battery:Get({})) - - device:send(Configuration:Get({ parameter_number = 10 })) end local function notification_report_handler(self, device, cmd) local event - -- DOOR_WINDOW/TILT + -- DOOR_WINDOW if cmd.args.notification_type == Notification.notification_type.ACCESS_CONTROL then if cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_CLOSED then - event = capabilities.contactSensor.contact.closed() + event = ContactSensor.contact.closed() elseif cmd.args.event == Notification.event.access_control.WINDOW_DOOR_IS_OPEN then - event = capabilities.contactSensor.contact.open() + event = ContactSensor.contact.open() end end -- POWER if cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then if cmd.args.event == Notification.event.power_management.AC_MAINS_DISCONNECTED then - event = capabilities.powerSource.powerSource.battery() + event = PowerSource.powerSource.battery() elseif cmd.args.event == Notification.event.power_management.AC_MAINS_RE_CONNECTED then - event = capabilities.powerSource.powerSource.mains() + event = PowerSource.powerSource.mains() elseif cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED then device:send(Battery:Get({})) end @@ -96,9 +96,20 @@ local function notification_report_handler(self, device, cmd) -- MOLD if cmd.args.notification_type == Notification.notification_type.WEATHER_ALARM then if cmd.args.event == Notification.event.weather_alarm.STATE_IDLE then - event = capabilities.moldHealthConcern.moldHealthConcern.good() + event = MoldHealthConcern.moldHealthConcern.good() elseif cmd.args.event == Notification.event.weather_alarm.MOISTURE_ALARM then - event = capabilities.moldHealthConcern.moldHealthConcern.moderate() + event = MoldHealthConcern.moldHealthConcern.moderate() + end + end + + -- TAMPER + if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then + if cmd.args.event == Notification.event.home_security.STATE_IDLE then + log.info("STATE_IDLE") + event = TamperAlert.tamper.clear() + elseif cmd.args.event == Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED then + log.info("TAMPERING_PRODUCT_COVER_REMOVED") + event = TamperAlert.tamper.detected() end end @@ -107,51 +118,52 @@ local function notification_report_handler(self, device, cmd) end end -local function sensor_binary_report_handler(self, device, cmd) - local sensorType = cmd.args.sensor_type - local value = cmd.args.sensor_value +local function sensor_multilevel_report_handler(self, device, cmd) local event + local sensor_type = cmd.args.sensor_type + local value = cmd.args.sensor_value + + local x = device:get_field("three_axis_x") or 0 + local y = device:get_field("three_axis_y") or 0 + local z = device:get_field("three_axis_z") or 0 + + local MIN_VAL = -10000 + local MAX_VAL = 10000 + -- log.info(string.format("SensorMultilevel: type=%d, raw=%.1f", sensor_type, value)) + value = math.max(MIN_VAL, math.min(MAX_VAL, value)) + -- log.info(string.format("Clamped: %.1f", value)) + + if (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_X_AXIS) then + x = value + device:set_field("three_axis_x", x) + event = ThreeAxis.threeAxis(x, y, z) + elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS) then + y = value + device:set_field("three_axis_y", y) + event = ThreeAxis.threeAxis(x, y, z) + elseif (sensor_type == SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS) then + z = value + device:set_field("three_axis_z", z) + event = ThreeAxis.threeAxis(x, y, z) + end - local field_name = "initial_state_set_" .. sensorType - - if not device:get_field(field_name) then - log.debug("sensor_binary_report_handler") - -- MOLD - if sensorType == SensorBinary.sensor_type.GENERAL then - if value == SensorBinary.sensor_value.IDLE then - event = capabilities.moldHealthConcern.moldHealthConcern.good() - elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then - event = capabilities.moldHealthConcern.moldHealthConcern.moderate() - end - end - - -- DOOR_WINDOW/TILT - if sensorType == SensorBinary.sensor_type.DOOR_WINDOW or sensorType == SensorBinary.sensor_type.TILT then - if value == SensorBinary.sensor_value.IDLE then - event = capabilities.contactSensor.contact.closed() - elseif value == SensorBinary.sensor_value.DETECTED_AN_EVENT then - event = capabilities.contactSensor.contact.open() - end - end - - if (event ~= nil) then - device:emit_event(event) - device:set_field(field_name, true) - end + if (event ~= nil) then + device:emit_event(event) end end local aeotec_door_window_sensor_8 = { supported_capabilities = { capabilities.powerSource, + capabilities.threeAxis, }, zwave_handlers = { [cc.NOTIFICATION] = { [Notification.REPORT] = notification_report_handler }, - [cc.SENSOR_BINARY] = { - [SensorBinary.REPORT] = sensor_binary_report_handler - }, + -- [cc.SENSOR_MULTILEVEL] = { + -- [SensorMultilevel.REPORT] = sensor_multilevel_report_handler + -- } }, capability_handlers = { [capabilities.refresh.ID] = { @@ -159,9 +171,10 @@ local aeotec_door_window_sensor_8 = { } }, lifecycle_handlers = { - added = added_handler, + init = device_init, + added = added_handler }, - NAME = "Aeotec Door Window Sesnor 8", + NAME = "Aeotec Door Window Sensor 8", can_handle = can_handle_aeotec_door_window_sensor_8 } diff --git a/drivers/SmartThings/zwave-sensor/src/preferences.lua b/drivers/SmartThings/zwave-sensor/src/preferences.lua index da69e954c6..ea2d45f7b7 100644 --- a/drivers/SmartThings/zwave-sensor/src/preferences.lua +++ b/drivers/SmartThings/zwave-sensor/src/preferences.lua @@ -169,10 +169,9 @@ local devices = { product_ids = {0x0037} }, PARAMETERS = { - parameter1 = {parameter_number = 1, size = 1}, + parameter1 = {parameter_number = 1, size = 4}, parameter2 = {parameter_number = 2, size = 1}, parameter3 = {parameter_number = 3, size = 1}, - parameter4 = {parameter_number = 4, size = 1}, parameter5 = {parameter_number = 5, size = 1}, parameter13 = {parameter_number = 13, size = 1}, parameter23 = {parameter_number = 23, size = 1}, @@ -184,6 +183,7 @@ local devices = { parameter33 = {parameter_number = 33, size = 1}, parameter34 = {parameter_number = 34, size = 1}, parameter35 = {parameter_number = 35, size = 1}, + parameter36 = {parameter_number = 36, size = 1}, parameter64 = {parameter_number = 64, size = 1}, }, }, diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua new file mode 100644 index 0000000000..1ccc9ec880 --- /dev/null +++ b/drivers/SmartThings/zwave-sensor/src/test/test_aeotec_door_window_sesnor_8.lua @@ -0,0 +1,397 @@ +-- 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" +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +--- @type st.zwave.CommandClass.Battery +local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 }) +--- @type st.zwave.CommandClass.SensorMultilevel +local SensorMultilevel = (require "st.zwave.CommandClass.SensorMultilevel")({ version = 11 }) +--- @type st.zwave.CommandClass.Configuration +local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 4 }) +local t_utils = require "integration_test.utils" + +local sensor_endpoints = { + { + command_classes = { + {value = zw.BATTERY}, + {value = zw.NOTIFICATION}, + {value = zw.SENSOR_MULTILEVEL}, + {value = zw.CONFIGURATION} + } + } +} + +local mock_sensor = test.mock_device.build_test_zwave_device({ + profile = t_utils.get_profile_definition("aeotec-door-window-sensor-8.yml"), + zwave_endpoints = sensor_endpoints, + zwave_manufacturer_id = 0x0371, + zwave_product_id = 0x0037, +}) + +local function test_init() + test.mock_device.add_test_device(mock_sensor) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Device added lifecycle event for profile", + function() + test.socket.device_lifecycle:__queue_receive({ mock_sensor.id, "added" }) + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Configuration:Get({ + parameter_number = 10 + }) + ) + ) + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.supportedMoldValues({"good", "moderate"})) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) + ) + + test.socket.capability:__expect_send( + mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) + ) + + + test.socket.zwave:__expect_send( + zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + ) + end +) + +test.register_message_test( + "Refresh should generate the correct commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_sensor.id, + { capability = "refresh", command = "refresh", args = {} } + } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_sensor, + Battery:Get({}) + ) + }, + }, + { + inner_block_ordering = "relaxed" + } +) + +-- test.register_message_test( +-- "Notification report STATE_IDLE event should be handled as tamperAlert clear", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.HOME_SECURITY, +-- event = Notification.event.home_security.STATE_IDLE, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report TAMPERING_PRODUCT_COVER_REMOVED event should be handled as tamperAlert detected", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.HOME_SECURITY, +-- event = Notification.event.home_security.TAMPERING_PRODUCT_COVER_REMOVED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Battery report should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Battery:Report({ battery_level = 0x63 })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.battery.battery(99)) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report AC_MAINS_DISCONNECTED event should be handled power source state battery", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.AC_MAINS_DISCONNECTED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.battery()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report AC_MAINS_RE_CONNECTED event should be handled power source state dc", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.AC_MAINS_RE_CONNECTED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.powerSource.powerSource.dc()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report POWER_HAS_BEEN_APPLIED event should be send battery get", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.POWER_MANAGEMENT, +-- event = Notification.event.power_management.POWER_HAS_BEEN_APPLIED, +-- })) } +-- }, +-- { +-- channel = "zwave", +-- direction = "send", +-- message = zw_test_utils.zwave_test_build_send_command( +-- mock_sensor, +-- Battery:Get({}) +-- ) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report WINDOW_DOOR_IS_OPEN event should be handled contact sensor state open", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.ACCESS_CONTROL, +-- event = Notification.event.access_control.WINDOW_DOOR_IS_OPEN, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.open()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report WINDOW_DOOR_IS_CLOSED event should be handled contact sensor state closed", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.ACCESS_CONTROL, +-- event = Notification.event.access_control.WINDOW_DOOR_IS_CLOSED, +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.contactSensor.contact.closed()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Temperature reports should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.TEMPERATURE, +-- scale = SensorMultilevel.scale.temperature.CELSIUS, +-- sensor_value = 21.5 })) +-- } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 21.5, unit = 'C' })) +-- }, +-- } +-- ) + +-- test.register_message_test( +-- "Humidity reports should be handled", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.RELATIVE_HUMIDITY, +-- sensor_value = 70 })) +-- } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.relativeHumidityMeasurement.humidity({ value = 70, })) +-- }, +-- } +-- ) + +-- test.register_message_test( +-- "Sensor multilevel reports dew_point type command should be handled as dew point measurement", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.DEW_POINT, +-- sensor_value = 8, +-- scale = 0 +-- })) } +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.dewPoint.dewpoint({value = 8, unit = "C"})) +-- } +-- } +-- ) + +-- test.register_coroutine_test( +-- "Three Axis reports should be correctly handled", +-- function() +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_X_AXIS, +-- sensor_value = 1.962, +-- scale = SensorMultilevel.scale.acceleration_x_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Y_AXIS, +-- sensor_value = 1.962, +-- scale = SensorMultilevel.scale.acceleration_y_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.zwave:__queue_receive({ +-- mock_sensor.id, +-- SensorMultilevel:Report({ +-- sensor_type = SensorMultilevel.sensor_type.ACCELERATION_Z_AXIS, +-- sensor_value = 3.924, +-- scale = SensorMultilevel.scale.acceleration_z_axis.METERS_PER_SQUARE_SECOND } +-- ) +-- }) +-- test.socket.capability:__expect_send( +-- mock_sensor:generate_test_message("main", +-- capabilities.threeAxis.threeAxis({value = {200, 200, 400}, unit = 'mG'}) +-- ) +-- ) +-- end +-- ) + + +-- test.register_message_test( +-- "Notification report type WEATHER_ALARM event STATE_IDLE should be handled mold healt concern state good", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.WEATHER_ALARM, +-- event = Notification.event.weather_alarm.STATE_IDLE, +-- }))} +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) +-- } +-- } +-- ) + +-- test.register_message_test( +-- "Notification report type WEATHER_ALARM event MOISTURE_ALARM should be handled mold healt concern state moderate", +-- { +-- { +-- channel = "zwave", +-- direction = "receive", +-- message = { mock_sensor.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({ +-- notification_type = Notification.notification_type.WEATHER_ALARM, +-- event = Notification.event.weather_alarm.MOISTURE_ALARM, +-- }))} +-- }, +-- { +-- channel = "capability", +-- direction = "send", +-- message = mock_sensor:generate_test_message("main", capabilities.moldHealthConcern.moldHealthConcern.good()) +-- } +-- } +-- ) + +test.run_registered_tests() \ No newline at end of file