Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions drivers/SmartThings/zwave-smoke-alarm/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ zwaveManufacturer:
productType: 0x0004
productId: 0x0003
deviceProfileName: co-battery
- id: 0371/0002/0032
deviceLabel: Aeotec SmokeShield
manufacturerId: 0x0371
productType: 0x0002
productId: 0x0032
deviceProfileName: aeotec-smoke-shield
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: aeotec-smoke-shield
components:
- id: main
capabilities:
- id: smokeDetector
version: 1
- id: battery
version: 1
- id: tamperAlert
version: 1
- id: refresh
version: 1
categories:
- name: SmokeDetector
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
-- 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.Battery
local Battery = (require "st.zwave.CommandClass.Battery")({ version=1 })
--- @type st.zwave.CommandClass.WakeUp
local WakeUp = (require "st.zwave.CommandClass.WakeUp")({version=1})

local AEOTEC_SMOKE_SHIELD_FINGERPRINTS = {
{ manufacturerId = 0x0371, productType = 0x0002 , productId = 0x0032 }
}

--- Determine whether the passed device is aeotec smoke shield
---
--- @param driver st.zwave.Driver
--- @param device st.zwave.Device
--- @return boolean true if the device is aeotec smoke shield
local function can_handle_aeotec_smoke_shield(opts, driver, device, cmd, ...)
for _, fingerprint in ipairs(AEOTEC_SMOKE_SHIELD_FINGERPRINTS) do
if device:id_match( fingerprint.manufacturerId, fingerprint.productType, fingerprint.productId) then
return true
end
end
return false
end

local function device_added(self, device)
device:emit_event(capabilities.smokeDetector.smoke.clear())
device:emit_event(capabilities.tamperAlert.tamper.clear())
device:send(Battery:Get({}))
end

local function wakeup_notification_handler(self, device, cmd)
--Note sending WakeUpIntervalGet the first time a device wakes up will happen by default in Lua libs 0.49.x and higher
--This is done to help the hub correctly set the checkInterval for migrated devices.
if not device:get_field("__wakeup_interval_get_sent") then
device:send(WakeUp:IntervalGetV1({}))
device:set_field("__wakeup_interval_get_sent", true)
end
device:emit_event(capabilities.smokeDetector.smoke.clear())
device:send(Battery:Get({}))
end

local aeotec_smoke_shield = {
zwave_handlers = {
[cc.WAKE_UP] = {
[WakeUp.NOTIFICATION] = wakeup_notification_handler
}
},
lifecycle_handlers = {
added = device_added
},
NAME = "Aeotec SmokeShield",
can_handle = can_handle_aeotec_smoke_shield,
health_check = false,
}

return aeotec_smoke_shield
1 change: 1 addition & 0 deletions drivers/SmartThings/zwave-smoke-alarm/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ local driver_template = {
require("zwave-smoke-co-alarm-v2"),
require("fibaro-smoke-sensor"),
require("apiv6_bugfix"),
require("aeotec-smoke-shield"),
},
lifecycle_handlers = {
init = device_init,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
-- Copyright 2025 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

local test = require "integration_test"
local capabilities = require "st.capabilities"
local zw = require "st.zwave"
local zw_test_utils = require "integration_test.zwave_test_utils"
local t_utils = require "integration_test.utils"

local SensorBinary = (require "st.zwave.CommandClass.SensorBinary")({ version = 2 })
local Battery = (require "st.zwave.CommandClass.Battery")({ version = 1 })
local Notification = (require "st.zwave.CommandClass.Notification")({ version = 3 })
local WakeUp = (require "st.zwave.CommandClass.WakeUp")({ version = 1 })

local AEOTEC_MANUFACTURER_ID = 0x0371
local AEOTEC_SMOKE_SHIELD_PRODUCT_TYPE = 0x0002
local AEOTEC_SMOKE_SHIELD_PRODUCT_ID = 0x0032

-- supported comand classes
local sensor_endpoints = {
{
command_classes = {
{value = zw.SENSOR_BINARY},
{value = zw.BATTERY},
{value = zw.NOTIFICATION},
{value = zw.WAKE_UP }
}
}
}

local mock_device = test.mock_device.build_test_zwave_device(
{
profile = t_utils.get_profile_definition("aeotec-smoke-shield.yml"),
zwave_endpoints = sensor_endpoints,
zwave_manufacturer_id = AEOTEC_MANUFACTURER_ID,
zwave_product_type = AEOTEC_SMOKE_SHIELD_PRODUCT_TYPE,
zwave_product_id = AEOTEC_SMOKE_SHIELD_PRODUCT_ID
}
)

local function test_init()
test.mock_device.add_test_device(mock_device)
end
test.set_test_init_function(test_init)

test.register_message_test(
"Sensor Binary report (smoke) should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(SensorBinary:Report({
sensor_type = SensorBinary.sensor_type.SMOKE,
sensor_value = SensorBinary.sensor_value.DETECTED_AN_EVENT
})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected())
}
}
)

test.register_message_test(
"Sensor Binary report (tamper) should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(SensorBinary:Report({
sensor_type = SensorBinary.sensor_type.TAMPER,
sensor_value = SensorBinary.sensor_value.DETECTED_AN_EVENT
})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
}
)

test.register_message_test(
"Notification report (smoke) should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({
notification_type = Notification.notification_type.SMOKE,
event = Notification.event.smoke.DETECTED
})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.detected())
}
}
)

test.register_message_test(
"Notification report (smoke) ALARM_TEST should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({
notification_type = Notification.notification_type.SMOKE,
event = Notification.event.smoke.ALARM_TEST
})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.tested())
}
}
)

test.register_message_test(
"Notification report (smoke) STATE_IDLE should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(Notification:Report({
notification_type = Notification.notification_type.SMOKE,
event = Notification.event.smoke.STATE_IDLE
})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())
}
}
)


test.register_message_test(
"Notification report (tamper) TAMPERING should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.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_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected())
}
}
)

test.register_message_test(
"Notification report (tamper) STATE_IDLE should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.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_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())
}
}
)

test.register_message_test(
"Refresh should generate the correct commands",
{
{
channel = "device_lifecycle",
direction = "receive",
message = { mock_device.id, "added" },
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear())
},
{
channel = "zwave",
direction = "send",
message = zw_test_utils.zwave_test_build_send_command(
mock_device,
Battery:Get({})
)
},
},
{
inner_block_ordering = "relaxed"
}
)

test.register_message_test(
"WakeUp notification should be handled",
{
{
channel = "zwave",
direction = "receive",
message = { mock_device.id, zw_test_utils.zwave_test_build_receive_command(WakeUp:Notification({})) }
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())
},
{
channel = "zwave",
direction = "send",
message = zw_test_utils.zwave_test_build_send_command(
mock_device,
Battery:Get({})
)
},
{
channel = "zwave",
direction = "send",
message = zw_test_utils.zwave_test_build_send_command(
mock_device,
WakeUp:IntervalGet({})
)
},
},
{
inner_block_ordering = "relaxed"
}
)

test.run_registered_tests()