Skip to content

Commit 3a48d3d

Browse files
yezi289KKlimczukS
andauthored
[WWSTCERT-13462, WWSTCERT-13465] Sonoff SWV2C valve support (#3171)
* feat:sonoff swv2c valve * Zigbee Value: 1、Add Sonoff valve subdriver lazy loading. 2、Modify comment language and file name * Address SWV2C review feedback * Configure SWV2C reporting and fix integration tests * Remove duplicate Zigbee valve health check setting * fix: guard SWV2C endpoint 2 reports until child exists * fix: handle SWV2C identify reports and use default battery reporting * Remove redundant power source capability from SWV2C * Reuse valve battery profile for SWV2C --------- Co-authored-by: Konrad K <33450498+KKlimczukS@users.noreply.github.com>
1 parent 3c3946e commit 3a48d3d

6 files changed

Lines changed: 537 additions & 28 deletions

File tree

‎drivers/SmartThings/zigbee-valve/fingerprints.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,13 @@ zigbeeManufacturer :
5959
manufacturer: SONOFF
6060
model: SWV-ZNU
6161
deviceProfileName: valve-battery
62+
- id: SONOFF/SWV-ZF2U
63+
deviceLabel: SONOFF Water Valve 2
64+
manufacturer: SONOFF
65+
model: SWV-ZF2U
66+
deviceProfileName: valve-battery
67+
- id: SONOFF/SWV-ZF2
68+
deviceLabel: SONOFF Water Valve 2
69+
manufacturer: SONOFF
70+
model: SWV-ZF2
71+
deviceProfileName: valve-battery
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: valve
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: valve
6+
version: 1
7+
- id: refresh
8+
version: 1
9+
categories:
10+
- name: WaterValve

‎drivers/SmartThings/zigbee-valve/src/sonoff/can_handle.lua‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
-- Licensed under the Apache License, Version 2.0
33

44
local FINGERPRINTS = {
5+
{ mfr = "SONOFF", model = "SWV-ZF2U" },
6+
{ mfr = "SONOFF", model = "SWV-ZF2" },
57
{ mfr = "SONOFF", model = "SWV-ZFU" },
68
{ mfr = "SONOFF", model = "SWV-ZFE" },
79
{ mfr = "SONOFF", model = "SWV-ZNU" },
8-
{ mfr = "SONOFF", model = "SWV-ZNE" },
10+
{ mfr = "SONOFF", model = "SWV-ZNE" }
911
}
1012

1113
local function sonoff_can_handle(opts, driver, device, ...)

‎drivers/SmartThings/zigbee-valve/src/sonoff/init.lua‎

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,117 @@
33

44
local capabilities = require "st.capabilities"
55
local zcl_clusters = require "st.zigbee.zcl.clusters"
6+
local Identify = zcl_clusters.Identify
67
local OnOff = zcl_clusters.OnOff
78
local PowerConfiguration = zcl_clusters.PowerConfiguration
8-
local utils = require "st.utils"
9-
10-
--- OnOff Property Reporting Handler → Valve Capability Point Event
11-
--- SONOFF reports valve state through the OnOff cluster, so keep an explicit mapping
12-
--- from OnOff reports to valve events while using the parent driver's default commands.
13-
--- @param driver table Driver instance
14-
--- @param device table Device instance
15-
--- @param value table Zigbee attribute value
16-
local function onoff_attr_handler(driver, device, value)
17-
local is_on = value.value ~= false and value.value ~= 0
18-
if is_on then
19-
device:emit_event(capabilities.valve.valve.open())
9+
local st_device = require "st.device"
10+
11+
local CONFIGURATION = {
12+
{
13+
cluster = PowerConfiguration.ID,
14+
attribute = PowerConfiguration.attributes.BatteryPercentageRemaining.ID,
15+
minimum_interval = 30,
16+
maximum_interval = 21600,
17+
data_type = PowerConfiguration.attributes.BatteryPercentageRemaining.base_type,
18+
reportable_change = 1
19+
},
20+
{
21+
cluster = OnOff.ID,
22+
attribute = OnOff.attributes.OnOff.ID,
23+
minimum_interval = 0,
24+
maximum_interval = 600,
25+
data_type = OnOff.attributes.OnOff.base_type
26+
}
27+
}
28+
29+
local function find_child(parent, ep_id)
30+
return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id))
31+
end
32+
33+
local function is_dual_channel(device)
34+
local model = device:get_model()
35+
return model == "SWV-ZF2U" or model == "SWV-ZF2"
36+
end
37+
38+
--- Map OnOff reports to the corresponding valve.
39+
local function onoff_attr_handler(driver, device, value, zb_rx)
40+
local ep = zb_rx.address_header.src_endpoint.value
41+
local target = device
42+
if device.network_type == st_device.NETWORK_TYPE_ZIGBEE and is_dual_channel(device) and ep == 0x02 then
43+
target = find_child(device, 2)
44+
if not target then return end
45+
end
46+
if value.value ~= false and value.value ~= 0 then
47+
target:emit_event(capabilities.valve.valve.open())
2048
else
21-
device:emit_event(capabilities.valve.valve.closed())
49+
target:emit_event(capabilities.valve.valve.closed())
50+
end
51+
end
52+
53+
local function device_init(driver, device)
54+
if device.network_type == st_device.NETWORK_TYPE_ZIGBEE then
55+
if is_dual_channel(device) then
56+
device:set_find_child(find_child)
57+
end
58+
for _, attribute in ipairs(CONFIGURATION) do
59+
device:add_configured_attribute(attribute)
60+
end
61+
end
62+
end
63+
64+
local function do_configure(driver, device)
65+
if device.network_type == st_device.NETWORK_TYPE_ZIGBEE then
66+
device:refresh()
67+
device:configure()
2268
end
2369
end
2470

25-
--- Battery Percentage Attribute Handler
26-
--- Zigbee BatteryPercentageRemaining range 0-200 (0%-100%), needs to be divided by 2
27-
--- @param driver table Driver instance
28-
--- @param device table Device instance
29-
--- @param value table Zigbee attribute value
30-
local function battery_percentage_handler(driver, device, value)
31-
local raw = value.value
32-
local percent = utils.round(raw / 2)
33-
device:emit_event(capabilities.battery.battery(percent))
71+
local function device_added(driver, device)
72+
if device.network_type == st_device.NETWORK_TYPE_ZIGBEE and is_dual_channel(device) then
73+
if find_child(device, 2) == nil then
74+
driver:try_create_device({
75+
type = "EDGE_CHILD",
76+
label = string.format("%s 2", device.label),
77+
profile = "valve",
78+
parent_device_id = device.id,
79+
parent_assigned_child_key = string.format("%02X", 2),
80+
vendor_provided_label = string.format("%s 2", device.label),
81+
})
82+
end
83+
end
84+
device:refresh()
85+
end
86+
87+
local function driver_switched(driver, device)
88+
device_added(driver, device)
89+
end
90+
91+
--- A device button press prompts a refresh of both SWV2C channels.
92+
local function identify_handler(driver, device, zb_rx)
93+
if not is_dual_channel(device) then return end
94+
device.thread:call_with_delay(2, function()
95+
device:send(OnOff.attributes.OnOff:read(device))
96+
device:send(OnOff.attributes.OnOff:read(device):to_endpoint(0x02))
97+
end)
3498
end
3599

36100
local sonoff_valve_handler = {
37101
NAME = "SONOFF Water Valve Handler",
102+
lifecycle_handlers = {
103+
init = device_init,
104+
added = device_added,
105+
doConfigure = do_configure,
106+
driverSwitched = driver_switched,
107+
},
38108
zigbee_handlers = {
39109
attr = {
40-
-- OnOff property reporting → valve event (to compensate for the defect of the parent driver's default mapping being skipped)
41110
[OnOff.ID] = {
42111
[OnOff.attributes.OnOff.ID] = onoff_attr_handler
43-
},
44-
-- Battery percentage attribute reporting → battery event
45-
[PowerConfiguration.ID] = {
46-
[PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_percentage_handler
112+
}
113+
},
114+
cluster = {
115+
[Identify.ID] = {
116+
[Identify.server.commands.Identify.ID] = identify_handler
47117
}
48118
}
49119
},

‎drivers/SmartThings/zigbee-valve/src/test/test_sonoff_valve.lua‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
local test = require "integration_test"
55
local clusters = require "st.zigbee.zcl.clusters"
6+
local Identify = clusters.Identify
67
local OnOff = clusters.OnOff
78
local PowerConfiguration = clusters.PowerConfiguration
89
local capabilities = require "st.capabilities"
@@ -98,6 +99,17 @@ test.register_message_test(
9899
}
99100
)
100101

102+
test.register_message_test(
103+
"Identify command should not trigger dual-channel reads on SWV1C",
104+
{
105+
{
106+
channel = "zigbee",
107+
direction = "receive",
108+
message = { mock_device.id, Identify.server.commands.Identify.build_test_rx(mock_device, 1) }
109+
}
110+
}
111+
)
112+
101113
test.register_coroutine_test(
102114
"Init lifecycle should use default battery percentage reporting configuration",
103115
function ()

0 commit comments

Comments
 (0)