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
5 changes: 5 additions & 0 deletions drivers/SmartThings/zigbee-humidity-sensor/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ zigbeeManufacturer:
manufacturer: eWeLink
model: SNZB-02P
deviceProfileName: humidity-temp-battery
- id: "SONOFF/SNZB-02M"
deviceLabel: "SONOFF SNZB-02M"
manufacturer: SONOFF
model: SNZB-02M
deviceProfileName: sonoff-humidity-temp-press-battery
- id: "SONOFF/SNZB-02DR2"
deviceLabel: "SONOFF SNZB-02DR2"
manufacturer: SONOFF
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: sonoff-humidity-temp-press-battery
components:
- id: main
capabilities:
- id: temperatureMeasurement
version: 1
- id: relativeHumidityMeasurement
version: 1
- id: atmosphericPressureMeasurement
version: 1
- id: battery
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: MultiFunctionalSensor
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Copyright 2025 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

local function can_handle(opts, driver, device, ...)
if device:get_model() == "SNZB-02M" then
return true, require("sonoff.SNZB-02M")
end

return false
end

return can_handle
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--[[
Description: SNZB-02M pressure/humidity/temperature sensor driver
Version: 1.0
Author: GitHub Copilot
--]]

local capabilities = require "st.capabilities"
local clusters = require "st.zigbee.zcl.clusters"
local log = require "log"

local function convert_pressure_value(raw_value)
if raw_value == nil then
return nil
end

if raw_value > 2000 then
return raw_value / 1000.0
end

return raw_value / 10.0
end

local function pressure_report_handler(driver, device, value, zb_rx)
local pressure_value = convert_pressure_value(value.value)
if pressure_value == nil then
log.warn(string.format("SNZB-02M pressure report has no value: %s", tostring(value)))
return
end

log.debug(string.format("SNZB-02M pressure raw: %s, converted: %s kPa", tostring(value.value), tostring(pressure_value)))
device:emit_event(capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = pressure_value, unit = "kPa"}))
end

local function refresh_handler(driver, device, command)
device:send(clusters.TemperatureMeasurement.attributes.MeasuredValue:read(device))
device:send(clusters.RelativeHumidity.attributes.MeasuredValue:read(device))
device:send(clusters.PressureMeasurement.attributes.MeasuredValue:read(device))
device:send(clusters.PowerConfiguration.attributes.BatteryPercentageRemaining:read(device))
end

local can_handle = require "sonoff.SNZB-02M.can_handle"

return {
NAME = "Sonoff SNZB-02M Sensor",
zigbee_handlers = {
attr = {
[clusters.PressureMeasurement.ID] = {
[clusters.PressureMeasurement.attributes.MeasuredValue.ID] = pressure_report_handler
}
}
},
capability_handlers = {
[capabilities.refresh.ID] = {
[capabilities.refresh.commands.refresh.NAME] = refresh_handler
}
},
can_handle = can_handle
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ local sub_drivers = {
lazy_load_if_possible("centralite-sensor"),
lazy_load_if_possible("heiman-sensor"),
lazy_load_if_possible("frient-sensor"),
lazy_load_if_possible("sonoff.SNZB-02M"),
}
return sub_drivers
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
-- Copyright 2025 SmartThings, Inc.
-- Licensed under the Apache License, Version 2.0

local test = require "integration_test"
local t_utils = require "integration_test.utils"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
local clusters = require "st.zigbee.zcl.clusters"
local capabilities = require "st.capabilities"

local PowerConfiguration = clusters.PowerConfiguration
local TemperatureMeasurement = clusters.TemperatureMeasurement
local RelativeHumidity = clusters.RelativeHumidity
local PressureMeasurement = clusters.PressureMeasurement

local mock_device = test.mock_device.build_test_zigbee_device(
{
profile = t_utils.get_profile_definition("sonoff-humidity-temp-press-battery.yml"),
zigbee_endpoints = {
[1] = {
id = 1,
manufacturer = "SONOFF",
model = "SNZB-02M",
server_clusters = { 0x0001, 0x0402, 0x0405, 0x0403 }
}
}
}
)

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
test.mock_device.add_test_device(mock_device)
end

test.set_test_init_function(test_init)

test.register_message_test(
"Pressure report above 2000 should be divided by 1000",
{
{
channel = "zigbee",
direction = "receive",
message = {
mock_device.id,
PressureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 10000)
}
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main",
capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 10.0, unit = "kPa" }))
}
},
{
min_api_version = 14
}
)

test.register_message_test(
"Pressure report at or below 2000 should be divided by 10",
{
{
channel = "zigbee",
direction = "receive",
message = {
mock_device.id,
PressureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 960)
}
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main",
capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 96.0, unit = "kPa" }))
}
},
{
min_api_version = 14
}
)

test.register_message_test(
"Refresh should read temperature, humidity, pressure and battery",
{
{
channel = "capability",
direction = "receive",
message = { mock_device.id, { capability = "refresh", component = "main", command = "refresh", args = {} } }
},
{
channel = "zigbee",
direction = "send",
message = { mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }
},
{
channel = "zigbee",
direction = "send",
message = { mock_device.id, RelativeHumidity.attributes.MeasuredValue:read(mock_device) }
},
{
channel = "zigbee",
direction = "send",
message = { mock_device.id, PressureMeasurement.attributes.MeasuredValue:read(mock_device) }
},
{
channel = "zigbee",
direction = "send",
message = { mock_device.id, PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device) }
}
},
{
inner_block_ordering = "relaxed",
min_api_version = 14
}
)

test.run_registered_tests()
2 changes: 2 additions & 0 deletions tools/run_driver_tests_p.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
DRIVER_DIRS = Path(os.path.abspath(__file__)).parents[1].joinpath("drivers")
DRIVERS = [driver for driver in DRIVER_DIRS.glob("*/*") if driver.is_dir()] # this gets all the children of the children of the drivers directory
CHANGED_DRIVERS = [Path(driver).name for driver in sys.argv[1:]]
if CHANGED_DRIVERS:
DRIVERS = [driver for driver in DRIVERS if driver.name in CHANGED_DRIVERS]

def per_driver_task(driver_dir):
os.chdir(driver_dir.joinpath('src'))
Expand Down
Loading