From 514002227691290720fae8091cee6d987b383ff6 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Tue, 17 Mar 2026 14:00:38 -0600 Subject: [PATCH 1/9] Initial commit - added safety shutoff --- .../zwave-safety-shutoff/config.yml | 6 ++ .../zwave-safety-shutoff/fingerprints.yml | 26 +++++ .../fireavert-appliance-shutoff-electric.yml | 21 +++++ .../fireavert-appliance-shutoff-gas.yml | 19 ++++ .../init.lua | 94 +++++++++++++++++++ .../fireavert-appliance-shutoff-gas/init.lua | 92 ++++++++++++++++++ .../zwave-safety-shutoff/src/init.lua | 90 ++++++++++++++++++ 7 files changed, 348 insertions(+) create mode 100644 drivers/SmartThings/zwave-safety-shutoff/config.yml create mode 100644 drivers/SmartThings/zwave-safety-shutoff/fingerprints.yml create mode 100644 drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml create mode 100644 drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml create mode 100644 drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua create mode 100644 drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua create mode 100644 drivers/SmartThings/zwave-safety-shutoff/src/init.lua diff --git a/drivers/SmartThings/zwave-safety-shutoff/config.yml b/drivers/SmartThings/zwave-safety-shutoff/config.yml new file mode 100644 index 0000000000..534c2172ac --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/config.yml @@ -0,0 +1,6 @@ +name: 'Z-Wave Appliance Safety' +packageKey: 'zwave-appliance-safety' +permissions: + zwave: {} +description: "SmartThings driver for Z-Wave appliance safety devices." +vendorSupportInformation: "https://support.smartthings.com" diff --git a/drivers/SmartThings/zwave-safety-shutoff/fingerprints.yml b/drivers/SmartThings/zwave-safety-shutoff/fingerprints.yml new file mode 100644 index 0000000000..b741496b9c --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/fingerprints.yml @@ -0,0 +1,26 @@ +zwaveManufacturer: + - id: "FireAvert/Shutoff/Gas" + deviceLabel: FireAvert Shutoff for Gas Appliances + manufacturerId: 0x045D + productType: 0x0004 + productId: 0x1601 + deviceProfileName: fireavert-appliance-shutoff-gas + - id: "FireAvert/Shutoff/120v" + deviceLabel: FireAvert Shutoff for Electric Appliances + manufacturerId: 0x045D + productType: 0x0004 + productId: 0x0601 + deviceProfileName: fireavert-appliance-shutoff-electric + - id: "FireAvert/Shutoff/240v3" + deviceLabel: FireAvert Shutoff for Electric Appliances + manufacturerId: 0x045D + productType: 0x0004 + productId: 0x0602 + deviceProfileName: fireavert-appliance-shutoff-electric + - id: "FireAvert/Shutoff/240v4" + deviceLabel: FireAvert Shutoff for Electric Appliances + manufacturerId: 0x045D + productType: 0x0004 + productId: 0x0603 + deviceProfileName: fireavert-appliance-shutoff-electric + diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml new file mode 100644 index 0000000000..23438bbf8a --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -0,0 +1,21 @@ +name: fireavert-appliance-shutoff-electric +components: +- id: main + capabilities: + - id: soundDetection + version: 1 + config: + values: + - key: supportedSoundTypes.value + enabledValues: + - fireAlarm + - id: powerMeter + version: 1 + categories: + - name: SmokeDetector +- id: switch2 + capabilities: + - id: switch + version: 1 + categories: + - name: SmokeDetector diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml new file mode 100644 index 0000000000..29276e96ef --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml @@ -0,0 +1,19 @@ +name: fireavert-appliance-shutoff-gas +components: +- id: main + capabilities: + - id: soundDetection + version: 1 + config: + values: + - key: supportedSoundTypes.value + enabledValues: + - fireAlarm + categories: + - name: SmokeDetector +- id: switch2 + capabilities: + - id: switch + version: 1 + categories: + - name: SmokeDetector diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua new file mode 100644 index 0000000000..0882fd62cf --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua @@ -0,0 +1,94 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +-- --- @type st.zwave.CommandClass.Configuration +-- local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 1 }) +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +-- --- @type st.zwave.CommandClass.BinarySwitch +-- local BinarySwitch = (require "st.zwave.CommandClass.BinarySwitch")({ version = 1}) + +local FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS = { + { manufacturerId = 0x045D, productType = 0x0004, productId = 0x0601 }, -- FireAvert Appliance Shutoff - 120V + { manufacturerId = 0x045D, productType = 0x0004, productId = 0x0602 }, -- FireAvert Appliance Shutoff - 240V 3 Prong + { manufacturerId = 0x045D, productType = 0x0004, productId = 0x0603 }, -- FireAvert Appliance Shutoff - 240V 4 Prong +} +--- Determine whether the passed device is a FireAvert shutoff device. All devices use the same driver. +local function can_handle_fireavert_appliance_shutoff_e(opts, driver, device, ...) + local isDevice = false + for _, fingerprint in ipairs(FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + isDevice = true + break + end + end + if true == isDevice then + local subdriver = require("fireavert-appliance-shutoff-electric") + return true, subdriver + else return false end +end + +--- Handler for notification report command class from sensor +--- +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.Notification.Report +local function notification_report_handler(self, device, cmd) + local event = nil +-- if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then +-- if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then +-- event = capabilities.motionSensor.motion.active() +-- elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then +-- if cmd.args.event_parameter:byte(1) ~= 3 then +-- event = capabilities.motionSensor.motion.inactive() +-- end +-- end +-- elseif cmd.args.alarm_type == Alarm.z_wave_alarm_type.BURGLAR then +-- if cmd.args.alarm_level == 0xFF then +-- event = capabilities.motionSensor.motion.active() +-- elseif cmd.args.alarm_level == 0 then +-- event = capabilities.motionSensor.motion.inactive() +-- end +-- end + if event ~= nil then device:emit_event(event) end +end + +--- Configuration lifecycle event handler. +--- +--- Send refresh GETs and manufacturer-specific configuration for +--- the FireAvert Appliance Shutoff device +--- +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function do_configure(self, device) + device:refresh() +end + +local fireavert_appliance_shutoff_e = { + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + }, + lifecycle_handlers = { + doConfigure = do_configure, + }, + NAME = "FireAvert Appliance Shutoff - Electric", + can_handle = can_handle_fireavert_appliance_shutoff_e +} + +return fireavert_appliance_shutoff_e diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua new file mode 100644 index 0000000000..b895fd493f --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua @@ -0,0 +1,92 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +-- --- @type st.zwave.CommandClass.Configuration +-- local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 1 }) +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) +-- --- @type st.zwave.CommandClass.BinarySwitch +-- local BinarySwitch = (require "st.zwave.CommandClass.BinarySwitch")({ version = 1}) + +local FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS = { + { manufacturerId = 0x045D, productType = 0x0004, productId = 0x1601 } -- FireAvert Appliance Shutoff - Gas +} +--- Determine whether the passed device is a FireAvert shutoff device. All devices use the same driver. +local function can_handle_fireavert_appliance_shutoff_gas(opts, driver, device, ...) + local isDevice = false + for _, fingerprint in ipairs(FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS) do + if device:id_match(fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then + isDevice = true + break + end + end + if true == isDevice then + local subdriver = require("fireavert-appliance-shutoff-gas") + return true, subdriver + else return false end +end + +--- Handler for notification report command class from sensor +--- +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.Notification.Report +local function notification_report_handler(self, device, cmd) + local event = nil +-- if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then +-- if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then +-- event = capabilities.motionSensor.motion.active() +-- elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then +-- if cmd.args.event_parameter:byte(1) ~= 3 then +-- event = capabilities.motionSensor.motion.inactive() +-- end +-- end +-- elseif cmd.args.alarm_type == Alarm.z_wave_alarm_type.BURGLAR then +-- if cmd.args.alarm_level == 0xFF then +-- event = capabilities.motionSensor.motion.active() +-- elseif cmd.args.alarm_level == 0 then +-- event = capabilities.motionSensor.motion.inactive() +-- end +-- end + if event ~= nil then device:emit_event(event) end +end + +--- Configuration lifecycle event handler. +--- +--- Send refresh GETs and manufacturer-specific configuration for +--- the FireAvert Appliance Shutoff device +--- +--- @param self st.zwave.Driver +--- @param device st.zwave.Device +local function do_configure(self, device) + device:refresh() +end + +local fireavert_appliance_shutoff_g = { + zwave_handlers = { + [cc.NOTIFICATION] = { + [Notification.REPORT] = notification_report_handler + }, + }, + lifecycle_handlers = { + doConfigure = do_configure, + }, + NAME = "FireAvert Appliance Shutoff - Gas", + can_handle = can_handle_fireavert_appliance_shutoff_gas +} + +return fireavert_appliance_shutoff_g diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua new file mode 100644 index 0000000000..5a17dee1f3 --- /dev/null +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -0,0 +1,90 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local capabilities = require "st.capabilities" +--- @type st.zwave.CommandClass +local cc = require "st.zwave.CommandClass" +--- @type st.zwave.Driver +local ZwaveDriver = require "st.zwave.driver" +--- @type st.zwave.defaults +local defaults = require "st.zwave.defaults" + +local preferences = require "preferences" + +local function lazy_load_if_possible(sub_driver_name) + -- gets the current lua libs api version + local version = require "version" + + -- version 9 will include the lazy loading functions + if version.api >= 9 then + return ZwaveDriver.lazy_load_sub_driver(require(sub_driver_name)) + else + return require(sub_driver_name) + end + +end + +--- Handle preference changes +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param event table +--- @param args +local function info_changed(self, device, event, args) + preferences.update_preferences(self, device, args) +end + +local function device_init(self, device) + device:set_update_preferences_fn(preferences.update_preferences) +end + +local function do_configure(driver, device) + device:refresh() + preferences.update_preferences(driver, device) +end + +local initial_events_map = { + [capabilities.soundDetection.ID] = capabilities.soundDetection.soundDetected.noSound(), +} + +local function added_handler(self, device) + for id, event in pairs(initial_events_map) do + if device:supports_capability_by_id(id) then + device:emit_event(event) + end + end +end + +local driver_template = { + supported_capabilities = { + capabilities.soundDetection + }, + sub_drivers = { + lazy_load_if_possible("fireavert-appliance-shutoff-gas"), + lazy_load_if_possible("fireavert-appliance-shutoff-electric"), + }, + lifecycle_handlers = { + added = added_handler, + init = device_init, + infoChanged = info_changed, + doConfigure = do_configure + }, +} + +defaults.register_for_default_handlers(driver_template, + driver_template.supported_capabilities, + {native_capability_attrs_enabled = true}) +--- @type st.zwave.Driver +local safety_shutoff = ZwaveDriver("zwave_appliance_safety", driver_template) +safety_shutoff:run() From 1ef7df821d756eba1c6277eb242b9519d180db9d Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Mon, 23 Mar 2026 15:40:14 -0600 Subject: [PATCH 2/9] wip: fixed issues with bootup and missing handlers. removed references to preferences.lua --- .../fireavert-appliance-shutoff-electric.yml | 10 +-- .../fireavert-appliance-shutoff-gas.yml | 6 +- .../fireavert-appliance-shutoff-gas/init.lua | 37 ++++---- .../zwave-safety-shutoff/src/init.lua | 88 +++++++++++++------ 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml index 23438bbf8a..ead60b07e1 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -9,13 +9,11 @@ components: - key: supportedSoundTypes.value enabledValues: - fireAlarm + - id: switch + version: 1 - id: powerMeter version: 1 - categories: - - name: SmokeDetector -- id: switch2 - capabilities: - - id: switch + - id: remoteControlStatus version: 1 - categories: + categories: - name: SmokeDetector diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml index 29276e96ef..ff24c3799d 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml @@ -9,11 +9,7 @@ components: - key: supportedSoundTypes.value enabledValues: - fireAlarm - categories: - - name: SmokeDetector -- id: switch2 - capabilities: - id: switch version: 1 - categories: + categories: - name: SmokeDetector diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua index b895fd493f..003790c994 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua @@ -15,12 +15,8 @@ local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" --- --- @type st.zwave.CommandClass.Configuration --- local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 1 }) --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- --- @type st.zwave.CommandClass.BinarySwitch --- local BinarySwitch = (require "st.zwave.CommandClass.BinarySwitch")({ version = 1}) local FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS = { { manufacturerId = 0x045D, productType = 0x0004, productId = 0x1601 } -- FireAvert Appliance Shutoff - Gas @@ -47,21 +43,24 @@ end --- @param cmd st.zwave.CommandClass.Notification.Report local function notification_report_handler(self, device, cmd) local event = nil --- if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then --- if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then --- event = capabilities.motionSensor.motion.active() --- elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then --- if cmd.args.event_parameter:byte(1) ~= 3 then --- event = capabilities.motionSensor.motion.inactive() --- end --- end --- elseif cmd.args.alarm_type == Alarm.z_wave_alarm_type.BURGLAR then --- if cmd.args.alarm_level == 0xFF then --- event = capabilities.motionSensor.motion.active() --- elseif cmd.args.alarm_level == 0 then --- event = capabilities.motionSensor.motion.inactive() --- end --- end + --- Z-Wave's closest match is a smoke detection notification, but this more + --- cleanly maps to Sound Detection in SmartThings. + if cmd.args.notification_type == Notification.notification_type.SMOKE then + if cmd.args.event == Notification.smoke.DETECTED then + event = capabilities.soundDetection.soundDetected.fireAlarm() + elseif cmd.args.event == Notification.smoke.STATE_IDLE then + event = capabilities.soundDetection.soundDetected.noSound() + end + end + if cmd.args.notification_type == Notification.notification_type.APPLIANCE then + -- This event is not supported by the SmartThings API yet + -- Eventually this will be implemented as the Appliance::SafetyInterlock notification + -- if cmd.args.event == 0x16 then + -- event = capabilities.remoteControlStatus.remoteControlEnabled.false() + -- elseif cmd.args.event == Notification..STATE_IDLE then + -- event = capabilities.remoteControlStatus.remoteControlEnabled.true() + -- end + end if event ~= nil then device:emit_event(event) end end diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index 5a17dee1f3..97a57b9858 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -19,8 +19,10 @@ local cc = require "st.zwave.CommandClass" local ZwaveDriver = require "st.zwave.driver" --- @type st.zwave.defaults local defaults = require "st.zwave.defaults" - -local preferences = require "preferences" +--- @type st.zwave.CommandClass.ApplicationStatus +local ApplicationStatus = (require "st.zwave.CommandClass.ApplicationStatus")({ version = 1 }) +--- @type st.zwave.CommandClass.SwitchBinary +local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) local function lazy_load_if_possible(sub_driver_name) -- gets the current lua libs api version @@ -35,27 +37,9 @@ local function lazy_load_if_possible(sub_driver_name) end ---- Handle preference changes ---- ---- @param driver st.zwave.Driver ---- @param device st.zwave.Device ---- @param event table ---- @param args -local function info_changed(self, device, event, args) - preferences.update_preferences(self, device, args) -end - -local function device_init(self, device) - device:set_update_preferences_fn(preferences.update_preferences) -end - -local function do_configure(driver, device) - device:refresh() - preferences.update_preferences(driver, device) -end - local initial_events_map = { [capabilities.soundDetection.ID] = capabilities.soundDetection.soundDetected.noSound(), + [capabilities.switch.ID] = capabilities.switch.switch.off() } local function added_handler(self, device) @@ -66,10 +50,49 @@ local function added_handler(self, device) end end +--- Only ever fires when the device attempts to turn the switch back on and this is rejected. +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.ApplicationStatus.ApplicationRejectedRequest +local function app_rejected_handler(driver, device, cmd) + device:emit_event(capabilities.switch.switch.off()) +end + +local function device_init(self, device) + -- TODO: What to do on device initalization +end + +local function info_changed(self, device) + -- TODO: What to do when info changes - what does this do? +end + +--- Handle a Z-Wave Command Class Switch Binary report, translate this to +--- an equivalent SmartThings Capability event, and emit this to the +--- SmartThings infrastructure. +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param cmd st.zwave.CommandClass.SwitchBinary.Report +local function switch_report_handler(driver, device, cmd) + if cmd.args.value == SwitchBinary.value.OFF_DISABLE then + device:emit_event(capabilities.switch.switch.off()) + else + device:emit_event(capabilities.switch.switch.on()) + end +end + +--- Handle a Switch OFF command from the application. +--- Switching on is not allowed in many cases so that behavior +--- is inherited by the subdriver as needed. +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param command ST level capabilitiy command +local function st_switch_off_handler(driver, device, command) + device:send(SwitchBinary:Set({value = 0x00})) +end + local driver_template = { - supported_capabilities = { - capabilities.soundDetection - }, sub_drivers = { lazy_load_if_possible("fireavert-appliance-shutoff-gas"), lazy_load_if_possible("fireavert-appliance-shutoff-electric"), @@ -78,13 +101,22 @@ local driver_template = { added = added_handler, init = device_init, infoChanged = info_changed, - doConfigure = do_configure }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.off.NAME] = st_switch_off_handler + } + }, + zwave_handlers = { + [cc.SWITCH_BINARY] = { + [SwitchBinary.REPORT] = switch_report_handler, + }, + [cc.APPLICATION_STATUS] = { + [ApplicationStatus.APPLICATION_REJECTED_REQUEST] = app_rejected_handler, + } + } } -defaults.register_for_default_handlers(driver_template, - driver_template.supported_capabilities, - {native_capability_attrs_enabled = true}) --- @type st.zwave.Driver local safety_shutoff = ZwaveDriver("zwave_appliance_safety", driver_template) safety_shutoff:run() From 96c1d6d58af772c9a35f30af571106f2fe891704 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Mon, 23 Mar 2026 17:37:18 -0600 Subject: [PATCH 3/9] Added missing sound reference to config files --- .../profiles/fireavert-appliance-shutoff-electric.yml | 1 + .../profiles/fireavert-appliance-shutoff-gas.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml index ead60b07e1..af0c37c7b8 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -8,6 +8,7 @@ components: values: - key: supportedSoundTypes.value enabledValues: + - noSound - fireAlarm - id: switch version: 1 diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml index ff24c3799d..f006ac3649 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml @@ -8,6 +8,7 @@ components: values: - key: supportedSoundTypes.value enabledValues: + - noSound - fireAlarm - id: switch version: 1 From 6436bb23c51ad6ec70d1843baa1858fd3185719a Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Tue, 24 Mar 2026 16:35:26 -0600 Subject: [PATCH 4/9] WIP: Tested Electric and Gas units. Added Application Rejection handling for rejected sets. --- .../init.lua | 44 +++++++------- .../fireavert-appliance-shutoff-gas/init.lua | 13 +---- .../zwave-safety-shutoff/src/init.lua | 57 +++++++++++++++++-- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua index 0882fd62cf..2c12902dfe 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua @@ -15,12 +15,11 @@ local capabilities = require "st.capabilities" --- @type st.zwave.CommandClass local cc = require "st.zwave.CommandClass" --- --- @type st.zwave.CommandClass.Configuration --- local Configuration = (require "st.zwave.CommandClass.Configuration")({ version = 1 }) --- @type st.zwave.CommandClass.Notification local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- --- @type st.zwave.CommandClass.BinarySwitch --- local BinarySwitch = (require "st.zwave.CommandClass.BinarySwitch")({ version = 1}) + +--- This is a notification type that is not available in SmartThings but does exist in the Z-Wave Specification (2025B +). +local APPLIANCE_SAFETY_INTERLOCK_ENGAGED = 0x16 local FIREAVERT_APPLIANCE_SHUTOFF_FINGERPRINTS = { { manufacturerId = 0x045D, productType = 0x0004, productId = 0x0601 }, -- FireAvert Appliance Shutoff - 120V @@ -49,22 +48,27 @@ end --- @param cmd st.zwave.CommandClass.Notification.Report local function notification_report_handler(self, device, cmd) local event = nil --- if cmd.args.notification_type == Notification.notification_type.HOME_SECURITY then --- if cmd.args.event == Notification.event.home_security.MOTION_DETECTION then --- event = capabilities.motionSensor.motion.active() --- elseif cmd.args.event == Notification.event.home_security.STATE_IDLE then --- if cmd.args.event_parameter:byte(1) ~= 3 then --- event = capabilities.motionSensor.motion.inactive() --- end --- end --- elseif cmd.args.alarm_type == Alarm.z_wave_alarm_type.BURGLAR then --- if cmd.args.alarm_level == 0xFF then --- event = capabilities.motionSensor.motion.active() --- elseif cmd.args.alarm_level == 0 then --- event = capabilities.motionSensor.motion.inactive() --- end --- end - if event ~= nil then device:emit_event(event) end + if cmd.args.notification_type == Notification.notification_type.SMOKE then + if cmd.args.event == Notification.event.smoke.DETECTED then + event = capabilities.soundDetection.soundDetected.fireAlarm() + elseif cmd.args.event == Notification.event.smoke.STATE_IDLE then + event = capabilities.soundDetection.soundDetected.noSound() + end + elseif cmd.args.notification_type == Notification.notification_type.APPLIANCE then + if cmd.args.event == APPLIANCE_SAFETY_INTERLOCK_ENGAGED then + -- event = capabilities.remoteControlStatus.remoteControlEnabled.false() + print("Device cannot be remote controlled") + else + -- event = capabilities.remoteControlStatus.remoteControlEnabled.true() + print("Device can be remote controlled") + + end + + end + if event ~= nil then + print("notification event: %s", event) + device:emit_event(event) + end end --- Configuration lifecycle event handler. diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua index 003790c994..a1eed4684c 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-gas/init.lua @@ -46,21 +46,12 @@ local function notification_report_handler(self, device, cmd) --- Z-Wave's closest match is a smoke detection notification, but this more --- cleanly maps to Sound Detection in SmartThings. if cmd.args.notification_type == Notification.notification_type.SMOKE then - if cmd.args.event == Notification.smoke.DETECTED then + if cmd.args.event == Notification.event.smoke.DETECTED then event = capabilities.soundDetection.soundDetected.fireAlarm() - elseif cmd.args.event == Notification.smoke.STATE_IDLE then + elseif cmd.args.event == Notification.event.smoke.STATE_IDLE then event = capabilities.soundDetection.soundDetected.noSound() end end - if cmd.args.notification_type == Notification.notification_type.APPLIANCE then - -- This event is not supported by the SmartThings API yet - -- Eventually this will be implemented as the Appliance::SafetyInterlock notification - -- if cmd.args.event == 0x16 then - -- event = capabilities.remoteControlStatus.remoteControlEnabled.false() - -- elseif cmd.args.event == Notification..STATE_IDLE then - -- event = capabilities.remoteControlStatus.remoteControlEnabled.true() - -- end - end if event ~= nil then device:emit_event(event) end end diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index 97a57b9858..d411cd1690 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -24,6 +24,11 @@ local ApplicationStatus = (require "st.zwave.CommandClass.ApplicationStatus")({ --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) +-- State storage for certain asynchronous events. +local _deviceState = { + ["switch_state"] = true +} + local function lazy_load_if_possible(sub_driver_name) -- gets the current lua libs api version local version = require "version" @@ -55,18 +60,34 @@ end --- @param device st.zwave.Device --- @param cmd st.zwave.CommandClass.ApplicationStatus.ApplicationRejectedRequest local function app_rejected_handler(driver, device, cmd) + print("Application rejected received from device, unable to rearm") + if false == _deviceState["switch_state"] then + --- Switch is currently off and must stay off. + --- Platform assumed that the switch will turn on, so we have to + --- feed it a switch ON handler so it doesn't panic. + device:emit_event(capabilities.switch.switch.on()) + else + --- Switch is currently on but received a reject - This should never happen + --- so the state needs to be updated to reflect this. We know the switch is off + --- in this case. + _deviceState["switch_state"] = false + end + --- Reset the UI switch to match the current relay state. device:emit_event(capabilities.switch.switch.off()) end local function device_init(self, device) + print("Device init: Z-Wave Appliance Safety Shutoff") -- TODO: What to do on device initalization + -- Get binary switch information, Report should handle asynchronously + -- Get current notification status for smoke end local function info_changed(self, device) - -- TODO: What to do when info changes - what does this do? + device_init(self, device) end ---- Handle a Z-Wave Command Class Switch Binary report, translate this to +--- Handle a Z-Wave Command Class Binary Switch report, translate this to --- an equivalent SmartThings Capability event, and emit this to the --- SmartThings infrastructure. --- @@ -74,9 +95,12 @@ end --- @param device st.zwave.Device --- @param cmd st.zwave.CommandClass.SwitchBinary.Report local function switch_report_handler(driver, device, cmd) + -- This is the only place the switch_state mirror should change value. if cmd.args.value == SwitchBinary.value.OFF_DISABLE then + _deviceState["switch_state"] = false; device:emit_event(capabilities.switch.switch.off()) else + _deviceState["switch_state"] = true; device:emit_event(capabilities.switch.switch.on()) end end @@ -87,9 +111,29 @@ end --- --- @param driver st.zwave.Driver --- @param device st.zwave.Device ---- @param command ST level capabilitiy command +--- @param command ST level capability command local function st_switch_off_handler(driver, device, command) - device:send(SwitchBinary:Set({value = 0x00})) + device:send(SwitchBinary:Set({ + target_value = SwitchBinary.value.OFF_DISABLE, + duration = 0 + }) + ) +end + +--- Handle a Switch ON command from the application. +--- Switching on is not allowed in many cases so that behavior +--- is inherited by the subdriver as needed. +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param command ST level capability command +local function st_switch_on_handler(driver, device, command) + device:send(SwitchBinary:Set({ + target_value = SwitchBinary.value.ON_ENABLE, + duration = 0 + }) + ) + print("Switch on sent, waiting for reply") end local driver_template = { @@ -100,11 +144,12 @@ local driver_template = { lifecycle_handlers = { added = added_handler, init = device_init, - infoChanged = info_changed, + infoChanged = info_changed }, capability_handlers = { [capabilities.switch.ID] = { - [capabilities.switch.commands.off.NAME] = st_switch_off_handler + [capabilities.switch.commands.off.NAME] = st_switch_off_handler, + [capabilities.switch.commands.on.NAME] = st_switch_on_handler } }, zwave_handlers = { From 0e3f8e8faa070bc143df538eb81869cc3df48ec3 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Mon, 30 Mar 2026 17:14:52 -0600 Subject: [PATCH 5/9] continued work on device capabilities and UI mapping. --- .../init.lua | 12 ++++-- .../zwave-safety-shutoff/src/init.lua | 42 +++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua index 2c12902dfe..20656de284 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua @@ -56,14 +56,20 @@ local function notification_report_handler(self, device, cmd) end elseif cmd.args.notification_type == Notification.notification_type.APPLIANCE then if cmd.args.event == APPLIANCE_SAFETY_INTERLOCK_ENGAGED then - -- event = capabilities.remoteControlStatus.remoteControlEnabled.false() + event = capabilities.remoteControlStatus.remoteControlEnabled("false") print("Device cannot be remote controlled") else - -- event = capabilities.remoteControlStatus.remoteControlEnabled.true() + event = capabilities.remoteControlStatus.remoteControlEnabled("true") print("Device can be remote controlled") end - + elseif cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then + print("Power Notification: Notification payload: ", cmd.args.event_parameter) + if (cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED) then + event = capabilities.powerMeter.power({value = 1, unit = "W"}) + elseif (cmd.args.event == Notification.event.power_management.STATE_IDLE) then + event = capabilities.powerMeter.power({value = 0, unit = "W"}) + end end if event ~= nil then print("notification event: %s", event) diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index d411cd1690..145ec41a1e 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -61,26 +61,19 @@ end --- @param cmd st.zwave.CommandClass.ApplicationStatus.ApplicationRejectedRequest local function app_rejected_handler(driver, device, cmd) print("Application rejected received from device, unable to rearm") - if false == _deviceState["switch_state"] then - --- Switch is currently off and must stay off. - --- Platform assumed that the switch will turn on, so we have to - --- feed it a switch ON handler so it doesn't panic. - device:emit_event(capabilities.switch.switch.on()) - else - --- Switch is currently on but received a reject - This should never happen - --- so the state needs to be updated to reflect this. We know the switch is off - --- in this case. - _deviceState["switch_state"] = false - end + _deviceState["switch_state"] = false --- Reset the UI switch to match the current relay state. - device:emit_event(capabilities.switch.switch.off()) + device:emit_event(capabilities.switch.switch.off({state_change = true})) end local function device_init(self, device) print("Device init: Z-Wave Appliance Safety Shutoff") -- TODO: What to do on device initalization -- Get binary switch information, Report should handle asynchronously - -- Get current notification status for smoke + device:send(SwitchBinary:Get({})) + _deviceState["switch_state"] = "startup" + -- if (device:supports_capability(capabilities.powerMeter)) then + -- device:send(Notification:Get({notification_type = Notification.notification_type.power_management})) end local function info_changed(self, device) @@ -96,12 +89,24 @@ end --- @param cmd st.zwave.CommandClass.SwitchBinary.Report local function switch_report_handler(driver, device, cmd) -- This is the only place the switch_state mirror should change value. + local isDeviceChanging = false; if cmd.args.value == SwitchBinary.value.OFF_DISABLE then - _deviceState["switch_state"] = false; - device:emit_event(capabilities.switch.switch.off()) + if (_deviceState["switch_state"] ~= "off") then + _deviceState["switch_state"] = "off"; + isDeviceChanging = true; + end + device:emit_event(capabilities.switch.switch.off({state_change = isDeviceChanging})) + --- Also turn off power meter UI element, appliance is obviously not drawing power if + --- the switch is off + if (device:supports_capability(capabilities.powerMeter)) then + device:emit_event(capabilities.powerMeter.power({value = 0, units = "W"})) + end else - _deviceState["switch_state"] = true; - device:emit_event(capabilities.switch.switch.on()) + if (_deviceState["switch_state"] ~= "on") then + _deviceState["switch_state"] = "on"; + isDeviceChanging = true; + end + device:emit_event(capabilities.switch.switch.on({state_change = isDeviceChanging})) end end @@ -118,6 +123,7 @@ local function st_switch_off_handler(driver, device, command) duration = 0 }) ) + device:send(SwitchBinary:Get({})) end --- Handle a Switch ON command from the application. @@ -133,7 +139,7 @@ local function st_switch_on_handler(driver, device, command) duration = 0 }) ) - print("Switch on sent, waiting for reply") + device:send(SwitchBinary:Get({})) end local driver_template = { From c0d4143da973189f5c6e63531d24872f1dda8616 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Thu, 2 Apr 2026 17:03:01 -0600 Subject: [PATCH 6/9] WIP: replaced powermeter with appliance utilization. --- .../fireavert-appliance-shutoff-electric.yml | 39 +++++++++++++++++-- .../fireavert-appliance-shutoff-gas.yml | 1 + .../init.lua | 5 +-- .../zwave-safety-shutoff/src/init.lua | 22 +++-------- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml index af0c37c7b8..1dccc83405 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -1,6 +1,7 @@ name: fireavert-appliance-shutoff-electric components: - id: main + label: "Appliance Information" capabilities: - id: soundDetection version: 1 @@ -10,11 +11,43 @@ components: enabledValues: - noSound - fireAlarm - - id: switch - version: 1 - - id: powerMeter + - id: applianceUtilization version: 1 - id: remoteControlStatus version: 1 + - id: switch + version: 1 categories: - name: SmokeDetector +deviceConfig: + dashboard: + states: + - component: main + capability: soundDetection + label: " {{soundDetection.soundDetected.value}}" + version: 1 + detailView: + - component: main + capability: switch + version: 1 + - component: main + capability: soundDetection + version: 1 + - component: main + capability: applianceUtilization + version: 1 + # label: "Appliance Power State" + # displayType: state + # state: + # value: " {{applianceUtilization.status.value}}" + # valueType: string + # alternatives: + # - key: "inUse" + # value: "Appliance Powered On" + # type: active + # - key: "notInUse" + # value: "Appliance Powered Off" + # type: inactive + - component: main + capability: remoteControlStatus + version: 1 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml index f006ac3649..a62771fcc4 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-gas.yml @@ -1,6 +1,7 @@ name: fireavert-appliance-shutoff-gas components: - id: main + label: "Appliance Control" capabilities: - id: soundDetection version: 1 diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua index 20656de284..a7cb662bf3 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua @@ -61,14 +61,13 @@ local function notification_report_handler(self, device, cmd) else event = capabilities.remoteControlStatus.remoteControlEnabled("true") print("Device can be remote controlled") - end elseif cmd.args.notification_type == Notification.notification_type.POWER_MANAGEMENT then print("Power Notification: Notification payload: ", cmd.args.event_parameter) if (cmd.args.event == Notification.event.power_management.POWER_HAS_BEEN_APPLIED) then - event = capabilities.powerMeter.power({value = 1, unit = "W"}) + event = capabilities.applianceUtilization.status.inUse() elseif (cmd.args.event == Notification.event.power_management.STATE_IDLE) then - event = capabilities.powerMeter.power({value = 0, unit = "W"}) + event = capabilities.applianceUtilization.status.notInUse() end end if event ~= nil then diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index 145ec41a1e..3a623a82d2 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -24,11 +24,6 @@ local ApplicationStatus = (require "st.zwave.CommandClass.ApplicationStatus")({ --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) --- State storage for certain asynchronous events. -local _deviceState = { - ["switch_state"] = true -} - local function lazy_load_if_possible(sub_driver_name) -- gets the current lua libs api version local version = require "version" @@ -39,11 +34,12 @@ local function lazy_load_if_possible(sub_driver_name) else return require(sub_driver_name) end - end local initial_events_map = { [capabilities.soundDetection.ID] = capabilities.soundDetection.soundDetected.noSound(), + [capabilities.applianceUtilization.ID] = capabilities.applianceUtilization.status.notInUse(), + [capabilities.remoteControlStatus.ID] = capabilities.remoteControlStatus.remoteControlEnabled("false"), [capabilities.switch.ID] = capabilities.switch.switch.off() } @@ -71,7 +67,6 @@ local function device_init(self, device) -- TODO: What to do on device initalization -- Get binary switch information, Report should handle asynchronously device:send(SwitchBinary:Get({})) - _deviceState["switch_state"] = "startup" -- if (device:supports_capability(capabilities.powerMeter)) then -- device:send(Notification:Get({notification_type = Notification.notification_type.power_management})) end @@ -89,23 +84,16 @@ end --- @param cmd st.zwave.CommandClass.SwitchBinary.Report local function switch_report_handler(driver, device, cmd) -- This is the only place the switch_state mirror should change value. + -- TODO: fix device changing value local isDeviceChanging = false; if cmd.args.value == SwitchBinary.value.OFF_DISABLE then - if (_deviceState["switch_state"] ~= "off") then - _deviceState["switch_state"] = "off"; - isDeviceChanging = true; - end device:emit_event(capabilities.switch.switch.off({state_change = isDeviceChanging})) --- Also turn off power meter UI element, appliance is obviously not drawing power if --- the switch is off - if (device:supports_capability(capabilities.powerMeter)) then - device:emit_event(capabilities.powerMeter.power({value = 0, units = "W"})) + if (device:supports_capability(capabilities.applianceUtilization)) then + device:emit_event(capabilities.applianceUtilization.status.notInUse()) end else - if (_deviceState["switch_state"] ~= "on") then - _deviceState["switch_state"] = "on"; - isDeviceChanging = true; - end device:emit_event(capabilities.switch.switch.on({state_change = isDeviceChanging})) end end From 290ca2df995445162d4e0cd3b1e994d43275db2c Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Fri, 3 Apr 2026 11:06:03 -0600 Subject: [PATCH 7/9] Moved capabilities for consistency across device types --- .../fireavert-appliance-shutoff-electric.yml | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml index 1dccc83405..4688f8da8e 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -11,43 +11,43 @@ components: enabledValues: - noSound - fireAlarm + - id: switch + version: 1 - id: applianceUtilization version: 1 - id: remoteControlStatus version: 1 - - id: switch - version: 1 categories: - name: SmokeDetector -deviceConfig: - dashboard: - states: - - component: main - capability: soundDetection - label: " {{soundDetection.soundDetected.value}}" - version: 1 - detailView: - - component: main - capability: switch - version: 1 - - component: main - capability: soundDetection - version: 1 - - component: main - capability: applianceUtilization - version: 1 - # label: "Appliance Power State" - # displayType: state - # state: - # value: " {{applianceUtilization.status.value}}" - # valueType: string - # alternatives: - # - key: "inUse" - # value: "Appliance Powered On" - # type: active - # - key: "notInUse" - # value: "Appliance Powered Off" - # type: inactive - - component: main - capability: remoteControlStatus - version: 1 \ No newline at end of file +# deviceConfig: +# dashboard: +# states: +# - component: main +# capability: soundDetection +# label: " {{soundDetection.soundDetected.value}}" +# version: 1 +# detailView: +# - component: main +# capability: switch +# version: 1 +# - component: main +# capability: soundDetection +# version: 1 +# - component: main +# capability: applianceUtilization +# version: 1 +# # label: "Appliance Power State" +# # displayType: state +# # state: +# # value: " {{applianceUtilization.status.value}}" +# # valueType: string +# # alternatives: +# # - key: "inUse" +# # value: "Appliance Powered On" +# # type: active +# # - key: "notInUse" +# # value: "Appliance Powered Off" +# # type: inactive +# - component: main +# capability: remoteControlStatus +# version: 1 \ No newline at end of file From bba27058943c66cec305a95ce45b234a9f9801b1 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Fri, 17 Apr 2026 15:39:52 -0600 Subject: [PATCH 8/9] Fixed issues with detail view on electric units. Added switch handlers for smoke alarms. --- .../fireavert-appliance-shutoff-electric.yml | 71 ++++++++++--------- .../init.lua | 2 +- .../zwave-safety-shutoff/src/init.lua | 44 ++++++++++++ 3 files changed, 84 insertions(+), 33 deletions(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml index 4688f8da8e..0c2c176a24 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml +++ b/drivers/SmartThings/zwave-safety-shutoff/profiles/fireavert-appliance-shutoff-electric.yml @@ -19,35 +19,42 @@ components: version: 1 categories: - name: SmokeDetector -# deviceConfig: -# dashboard: -# states: -# - component: main -# capability: soundDetection -# label: " {{soundDetection.soundDetected.value}}" -# version: 1 -# detailView: -# - component: main -# capability: switch -# version: 1 -# - component: main -# capability: soundDetection -# version: 1 -# - component: main -# capability: applianceUtilization -# version: 1 -# # label: "Appliance Power State" -# # displayType: state -# # state: -# # value: " {{applianceUtilization.status.value}}" -# # valueType: string -# # alternatives: -# # - key: "inUse" -# # value: "Appliance Powered On" -# # type: active -# # - key: "notInUse" -# # value: "Appliance Powered Off" -# # type: inactive -# - component: main -# capability: remoteControlStatus -# version: 1 \ No newline at end of file +deviceConfig: + dashboard: + states: + - component: main + capability: soundDetection + version: 1 + detailView: + - component: main + capability: soundDetection + version: 1 + - component: main + capability: switch + version: 1 + - component: main + capability: applianceUtilization + version: 1 + label: "Appliance Power" + values: + - key: status.value + alternatives: + - key: inUse + value: "Appliance Powered On" + type: active + - key: notInUse + value: "Appliance Powered Off" + type: inactive + - component: main + capability: remoteControlStatus + version: 1 + label: "Safety Control" + values: + - key: remoteControlStatus.value + alternatives: + - key: "true" + value: "Unlocked" + type: active + - key: "false" + value: "Device Locked" + type: inactive diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua index a7cb662bf3..440886b3c6 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/fireavert-appliance-shutoff-electric/init.lua @@ -90,7 +90,7 @@ end local fireavert_appliance_shutoff_e = { zwave_handlers = { [cc.NOTIFICATION] = { - [Notification.REPORT] = notification_report_handler + [Notification.REPORT] = notification_report_handler }, }, lifecycle_handlers = { diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index 3a623a82d2..e08047baff 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -21,6 +21,8 @@ local ZwaveDriver = require "st.zwave.driver" local defaults = require "st.zwave.defaults" --- @type st.zwave.CommandClass.ApplicationStatus local ApplicationStatus = (require "st.zwave.CommandClass.ApplicationStatus")({ version = 1 }) +--- @type st.zwave.CommandClass.Notification +local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 }) --- @type st.zwave.CommandClass.SwitchBinary local SwitchBinary = (require "st.zwave.CommandClass.SwitchBinary")({ version = 2 }) @@ -130,6 +132,44 @@ local function st_switch_on_handler(driver, device, command) device:send(SwitchBinary:Get({})) end +--- Handle a 'Disable sound detection' command from SmartThings. +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param command ST level capability command +local function st_sound_detection_disable_handler(driver, device, command) + device:send(Notification:Set({ + notification_type = Notification.notification_type.SMOKE, + notification_status = Notification.notification_status.OFF + }) + ) + device:send(Notification:Get({ + v1_alarm_type = 0, + notification_type = Notification.notification_type.SMOKE, + event = Notification.event.smoke.DETECTED + }) + ) +end + +--- Handle an 'Enable sound detection' command from SmartThings. +--- +--- @param driver st.zwave.Driver +--- @param device st.zwave.Device +--- @param command ST level capability command +local function st_sound_detection_enable_handler(driver, device, command) + device:send(Notification:Set({ + notification_type = Notification.notification_type.SMOKE, + notification_status = Notification.notification_status.ON + }) + ) + device:send(Notification:Get({ + v1_alarm_type = 0, + notification_type = Notification.notification_type.SMOKE, + event = Notification.event.smoke.DETECTED + }) + ) +end + local driver_template = { sub_drivers = { lazy_load_if_possible("fireavert-appliance-shutoff-gas"), @@ -144,6 +184,10 @@ local driver_template = { [capabilities.switch.ID] = { [capabilities.switch.commands.off.NAME] = st_switch_off_handler, [capabilities.switch.commands.on.NAME] = st_switch_on_handler + }, + [capabilities.soundDetection.ID] = { + [capabilities.soundDetection.commands.disableSoundDetection.NAME] = st_sound_detection_disable_handler, + [capabilities.soundDetection.commands.enableSoundDetection.NAME] = st_sound_detection_enable_handler, } }, zwave_handlers = { From 8dcc52410090f1c067e01f9fd1926d311be4782d Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Mon, 20 Apr 2026 13:08:53 -0600 Subject: [PATCH 9/9] bug: removed extraneous undefined variable reference --- drivers/SmartThings/zwave-safety-shutoff/src/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua index e08047baff..dd93bff40b 100644 --- a/drivers/SmartThings/zwave-safety-shutoff/src/init.lua +++ b/drivers/SmartThings/zwave-safety-shutoff/src/init.lua @@ -59,7 +59,6 @@ end --- @param cmd st.zwave.CommandClass.ApplicationStatus.ApplicationRejectedRequest local function app_rejected_handler(driver, device, cmd) print("Application rejected received from device, unable to rearm") - _deviceState["switch_state"] = false --- Reset the UI switch to match the current relay state. device:emit_event(capabilities.switch.switch.off({state_change = true})) end