From 0750be2ace0731d1fa12e92674f9e9d0252b0c39 Mon Sep 17 00:00:00 2001 From: wzh <1090741189@qq.com> Date: Fri, 7 Nov 2025 14:18:28 +0800 Subject: [PATCH 1/2] Add Sonoff SNZB-04PR2 Smart Scene Contact into zigbee-contact --- .../zigbee-contact/fingerprints.yml | 10 ++ .../zigbee-contact/src/configurations.lua | 1 - .../src/sonoff/SNZB-04PR2/init.lua | 118 +++++++++++++++ .../zigbee-contact/src/sonoff/init.lua | 139 ++++++++++++++++++ .../src/sonoff/sonoff_utils.lua | 79 ++++++++++ 5 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-contact/src/sonoff/SNZB-04PR2/init.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/sonoff/init.lua create mode 100644 drivers/SmartThings/zigbee-contact/src/sonoff/sonoff_utils.lua diff --git a/drivers/SmartThings/zigbee-contact/fingerprints.yml b/drivers/SmartThings/zigbee-contact/fingerprints.yml index f35ca2f7ba..8aa92f5b79 100644 --- a/drivers/SmartThings/zigbee-contact/fingerprints.yml +++ b/drivers/SmartThings/zigbee-contact/fingerprints.yml @@ -199,6 +199,16 @@ zigbeeManufacturer: manufacturer: Third Reality, Inc model: 3RVS01031Z deviceProfileName: thirdreality-multi-sensor + - id: "SONOFF/SNZB-04P" + deviceLabel: SONOFF Contact Sensor + manufacturer: eWeLink + model: SNZB-04P + deviceProfileName: contact-battery-tamper + - id: "SONOFF/SNZB-04PR2" + deviceLabel: SONOFF Contact Sensor + manufacturer: SONOFF + model: SNZB-04PR2 + deviceProfileName: contact-battery-tamper - id: "Aug. Winkhaus SE/FM.V.ZB" deviceLabel: Funkkontakt FM.V.ZB manufacturer: Aug. Winkhaus SE diff --git a/drivers/SmartThings/zigbee-contact/src/configurations.lua b/drivers/SmartThings/zigbee-contact/src/configurations.lua index aa28e1e43f..080571a3c9 100644 --- a/drivers/SmartThings/zigbee-contact/src/configurations.lua +++ b/drivers/SmartThings/zigbee-contact/src/configurations.lua @@ -37,7 +37,6 @@ local devices = { EWELINK_HEIMAN = { FINGERPRINTS = { { mfr = "eWeLink", model = "DS01" }, - { mfr = "eWeLink", model = "SNZB-04P" }, { mfr = "HEIMAN", model = "DoorSensor-N" } }, CONFIGURATION = { diff --git a/drivers/SmartThings/zigbee-contact/src/sonoff/SNZB-04PR2/init.lua b/drivers/SmartThings/zigbee-contact/src/sonoff/SNZB-04PR2/init.lua new file mode 100644 index 0000000000..1f0982c515 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/sonoff/SNZB-04PR2/init.lua @@ -0,0 +1,118 @@ +--[[ +Description: +Version: 1.0 +Autor: zehongwang +Date: 2025-08-22 16:47:37 +LastEditors: zehongwang +LastEditTime: +--]] +local capabilities = require "st.capabilities" +local zcl_commands = require "st.zigbee.zcl.global_commands" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local sonoff_utils = require "sonoff/sonoff_utils" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local OccupancySensing = zcl_clusters.OccupancySensing +local log = require "log" +local zb_const = require "st.zigbee.constants" +local write_attr_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local data_types = require "st.zigbee.data_types" +local Status = (require "st.zigbee.zcl.types").ZclStatus +local defaults = require "st.zigbee.defaults" +local ZigbeeDriver = require "st.zigbee" +local IASZone = (require "st.zigbee.zcl.clusters").IASZone + + +local tamper = capabilities["samplereturn62595.tamperStatus"] + + +local PREF_TAMPER_INSTALL = 0 +local PREF_TAMPER_REMOVE = 1 + +local FINGERPRINTS = { + { mfr = "SONOFF", model = "SNZB-04PR2" } +} + +local function spilt_attr_handler(driver, device, value, zb_rx) + log.debug("<<< Tamper Value >>>",value.value) + local raw_value = value.value + if raw_value == PREF_TAMPER_INSTALL then + device:emit_event(tamper.tamperProof.Normal()) + log.debug("<<< -Tamper Value- >>>",value.value) + elseif raw_value == PREF_TAMPER_REMOVE then + device:emit_event(tamper.tamperProof.Tampered()) + log.debug("<<< -Tamper Value- >>>",value.value) + end +end + +-- local function contact_status_handler(self, device, value, zb_rx) +-- log.debug("<<< ZoneStatus Value >>>",value.value) +-- if value.value == 1 or value.value == true then +-- device:emit_event(capabilities.contactSensor.contact.open()) +-- log.debug("<<< -ZoneStatus Value- >>>",value.value) +-- elseif value.value == 0 or value.value == false then +-- device:emit_event(capabilities.contactSensor.contact.closed()) +-- log.debug("<<< -ZoneStatus Value- >>>",value.value) +-- end +-- end +local function contact_status_handler(driver, device, zb_rx) + local zone_status_value = zb_rx.body.zcl_body.zone_status.value + log.debug("<<< ZoneStatus Value >>>", zone_status_value) + + if zone_status_value ~= nil then + local contact_bit = zone_status_value & 0x0001 + if contact_bit == 0x0001 then + device:emit_event(capabilities.contactSensor.contact.open()) + log.debug("<<< -Contact OPEN - >>>") + else + device:emit_event(capabilities.contactSensor.contact.closed()) + log.debug("<<< -Contact CLOSED - >>>") + end + else + log.warn("Zone status value is nil") + end +end + + + +local function added_handler(self, device) + --update UI + device:emit_event(tamper.tamperProof.Normal()) +end + +local function device_init(driver, device) +--do notthing +end + +local sonoff_04PR2_contact_handler = { + NAME = "Sonoff contact Handler", + supported_capabilities = { + capabilities.battery, + capabilities.contact + }, + lifecycle_handlers = { + init = device_init, + added = added_handler + }, + zigbee_handlers = { + attr = { + [sonoff_utils.SONOFF_PRIVITE_CLUSTER_ID] = { + [sonoff_utils.SONOFF_SPILT_ATTRIBUTE_ID] = spilt_attr_handler + } + }, + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = contact_status_handler + } + } + }, + can_handle = function(opts, driver, device, ...) + return device:get_model() == "SNZB-04PR2" + end +} + +-- return sonoff_04PR2_contact_handler +defaults.register_for_default_handlers(sonoff_04PR2_contact_handler, sonoff_04PR2_contact_handler.supported_capabilities) +local zigbee_button = ZigbeeDriver("sonoff_04PR2_contact_handler", sonoff_04PR2_contact_handler) +zigbee_button:run() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-contact/src/sonoff/init.lua b/drivers/SmartThings/zigbee-contact/src/sonoff/init.lua new file mode 100644 index 0000000000..52a684994a --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/sonoff/init.lua @@ -0,0 +1,139 @@ +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-04-25 13:38:25 +LastEditors: liangjia +LastEditTime: 2024-04-25 13:53:04 +--]] +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-04-25 13:38:25 +LastEditors: liangjia +LastEditTime: 2024-04-25 13:47:42 +--]] +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-01-19 18:05:31 +LastEditors: liangjia +LastEditTime: 2024-01-20 10:41:41 +--]] +local capabilities = require "st.capabilities" +local zcl_commands = require "st.zigbee.zcl.global_commands" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local sonoff_utils = require "sonoff/sonoff_utils" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local OccupancySensing = zcl_clusters.OccupancySensing +local log = require "log" +local zb_const = require "st.zigbee.constants" +local write_attr_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local data_types = require "st.zigbee.data_types" +local Status = (require "st.zigbee.zcl.types").ZclStatus +local IASZone = (require "st.zigbee.zcl.clusters").IASZone + + +local tamper = capabilities["samplereturn62595.tamperStatus"] + + +local PREF_TAMPER_INSTALL = 0 +local PREF_TAMPER_REMOVE = 1 + +local FINGERPRINTS = { + { mfr = "SONOFF", model = "SNZB-04P" }, + { mfr = "SONOFF", model = "SNZB-04PR2" } +} + +local is_sonoff_products = function(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function spilt_attr_handler(driver, device, value, zb_rx) + log.debug("<<< Tamper Value >>>",value.value) + local raw_value = value.value + if raw_value == PREF_TAMPER_INSTALL then + device:emit_event(tamper.tamperProof.Normal()) + log.debug("<<< -Tamper Value- >>>",value.value) + elseif raw_value == PREF_TAMPER_REMOVE then + device:emit_event(tamper.tamperProof.Tampered()) + log.debug("<<< -Tamper Value- >>>",value.value) + end +end + +-- local function contact_status_handler(self, device, value, zb_rx) +-- log.debug("<<< ZoneStatus Value >>>",value.value) +-- if value.value == 1 or value.value == true then +-- device:emit_event(capabilities.contactSensor.contact.open()) +-- log.debug("<<< -ZoneStatus Value- >>>",value.value) +-- elseif value.value == 0 or value.value == false then +-- device:emit_event(capabilities.contactSensor.contact.closed()) +-- log.debug("<<< -ZoneStatus Value- >>>",value.value) +-- end +-- end +local function contact_status_handler(driver, device, zb_rx) + local zone_status_value = zb_rx.body.zcl_body.zone_status.value + log.debug("<<< ZoneStatus Value >>>", zone_status_value) + + if zone_status_value ~= nil then + local contact_bit = zone_status_value & 0x0001 + if contact_bit == 0x0001 then + device:emit_event(capabilities.contactSensor.contact.open()) + log.debug("<<< -Contact OPEN - >>>") + else + device:emit_event(capabilities.contactSensor.contact.closed()) + log.debug("<<< -Contact CLOSED - >>>") + end + else + log.warn("Zone status value is nil") + end +end + + + +local function added_handler(self, device) + --update UI + device:emit_event(tamper.tamperProof.Normal()) +end + +local function device_init(driver, device) +--do notthing +end + +local sonoff_contact_handler = { + NAME = "Sonoff contact Handler", + lifecycle_handlers = { + init = device_init, + added = added_handler + }, + zigbee_handlers = { + attr = { + [sonoff_utils.SONOFF_PRIVITE_CLUSTER_ID] = { + [sonoff_utils.SONOFF_SPILT_ATTRIBUTE_ID] = spilt_attr_handler + } + }, + cluster = { + [IASZone.ID] = { + [IASZone.client.commands.ZoneStatusChangeNotification.ID] = contact_status_handler + } + } + }, + sub_drivers = { + require("sonoff/SNZB-04PR2") + }, + can_handle = is_sonoff_products + -- can_handle = function(opts, driver, device, ...) + -- return device:get_model() == "SNZB-04P" + -- end +} + +return sonoff_contact_handler diff --git a/drivers/SmartThings/zigbee-contact/src/sonoff/sonoff_utils.lua b/drivers/SmartThings/zigbee-contact/src/sonoff/sonoff_utils.lua new file mode 100644 index 0000000000..5de0ddc77a --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/src/sonoff/sonoff_utils.lua @@ -0,0 +1,79 @@ +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-01-24 12:00:32 +LastEditors: liangjia +LastEditTime: 2024-02-02 15:20:58 +--]] +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-01-19 18:05:31 +LastEditors: liangjia +LastEditTime: 2024-01-20 13:51:20 +--]] +--[[ +Description: +Version: 2.0 +Autor: liangjia +Date: 2024-01-19 15:39:47 +LastEditors: liangjia +LastEditTime: 2024-01-19 16:18:46 +--]] +local capabilities = require "st.capabilities" +local log = require "log" +local zb_const = require "st.zigbee.constants" + +local OCCUPANCY_CLUSTER_ID = 0x0406 +local OCCUPANCY_UTO_ATTRIBUTE_ID = 0x0020 +local MFG_CODE = 0x1286 +local SONOFF_PRIVITE_CLUSTER_ID = 0xFC11 +local SONOFF_ILLUMINATION_LEVEL_ATTRIBUTE_ID = 0x2001 +local SONOFF_SPILT_ATTRIBUTE_ID = 0x2000 + +local sonoff_utils = {} + +local PREF_FREQUENCY_KEY = "prefFrequency" +local PREF_FREQUENCY_VALUE_DEFAULT = 60 + + +local PREF_SENSITIVITY_KEY = "sensitivity" +local PREF_SENSITIVITY_VALUE_DEFAULT = 2 + +local PREF_CHANGED_KEY = "prefChangedKey" +local PREF_CHANGED_VALUE = "prefChangedValue" + +local function motion_detected(device) + device:emit_event(capabilities.motionSensor.motion.active()) +end + +local function get_pref_changed_field(device) + local key = device:get_field(PREF_CHANGED_KEY) or '' + local value = device:get_field(PREF_CHANGED_VALUE) or 0 + return key, value +end + +local function set_pref_changed_field(device, key, value) + log.debug("set_pref_changed_field---->value:",value) + device:set_field(PREF_CHANGED_KEY, key) + device:set_field(PREF_CHANGED_VALUE, value) +end + +sonoff_utils.OCCUPANCY_CLUSTER_ID = OCCUPANCY_CLUSTER_ID +sonoff_utils.OCCUPANCY_UTO_ATTRIBUTE_ID = OCCUPANCY_UTO_ATTRIBUTE_ID +sonoff_utils.MFG_CODE = MFG_CODE + +sonoff_utils.PREF_FREQUENCY_KEY = PREF_FREQUENCY_KEY +sonoff_utils.PREF_FREQUENCY_VALUE_DEFAULT = PREF_FREQUENCY_VALUE_DEFAULT +sonoff_utils.PREF_SENSITIVITY_KEY = PREF_SENSITIVITY_KEY +sonoff_utils.PREF_SENSITIVITY_VALUE_DEFAULT = PREF_SENSITIVITY_VALUE_DEFAULT +sonoff_utils.SONOFF_PRIVITE_CLUSTER_ID = SONOFF_PRIVITE_CLUSTER_ID +sonoff_utils.SONOFF_ILLUMINATION_LEVEL_ATTRIBUTE_ID = SONOFF_ILLUMINATION_LEVEL_ATTRIBUTE_ID +sonoff_utils.SONOFF_SPILT_ATTRIBUTE_ID = SONOFF_SPILT_ATTRIBUTE_ID +sonoff_utils.motion_detected = motion_detected +sonoff_utils.get_pref_changed_field = get_pref_changed_field +sonoff_utils.set_pref_changed_field = set_pref_changed_field + +return sonoff_utils From 92084e2eb4db63b4d9c1c4b5a131697c024867d3 Mon Sep 17 00:00:00 2001 From: wzh <1090741189@qq.com> Date: Fri, 7 Nov 2025 14:52:33 +0800 Subject: [PATCH 2/2] Add Sonoff profile into zigbee-contact --- .../profiles/contact-battery-tamper.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml diff --git a/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml new file mode 100644 index 0000000000..d442aea1b4 --- /dev/null +++ b/drivers/SmartThings/zigbee-contact/profiles/contact-battery-tamper.yml @@ -0,0 +1,16 @@ +name: contact-battery-tamper +components: +- id: main + capabilities: + - id: contactSensor + version: 1 + - id: battery + version: 1 + - id: samplereturn62595.tamperStatus + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor