Add SONOFF SWV-ZFE/SWV-ZFU (Hydro ONE) flow-meter valve quirk - #5258
Open
joel wants to merge 1 commit into
Open
Conversation
The SWV-ZFE/SWV-ZFU are the flow-meter variants of the SWV water valve. Extends the existing SWV custom cluster with the irrigation counters on 0xFC11 and exposes leak/supply binary sensors plus real-time and hourly volume/duration sensors. The two real-time counters are reported big-endian by the firmware and are byte-swapped, matching zigbee2mqtt's workaround. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #5258 +/- ##
==========================================
+ Coverage 92.60% 92.61% +0.01%
==========================================
Files 424 424
Lines 14667 14676 +9
==========================================
+ Hits 13582 13592 +10
+ Misses 1085 1084 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a ZHA Quirks v2 definition for the SONOFF SWV-ZFE/SWV-ZFU (“Hydro ONE”) flow-meter water valves by extending the existing Sonoff SWV quirk to expose irrigation volume/duration counters and handle the device’s mixed endianness reporting.
Changes:
- Added
CustomSonoffFlowCluster(0xFC11) with additional irrigation counter attributes for SWV-ZFE/SWV-ZFU. - Registered a new
QuirkBuilderentry for SWV-ZFE (also applying to SWV-ZFU), exposing real-time + hourly irrigation sensors and reusing existing valve-state binary sensors. - Added unit tests covering the 32-bit byte-swap helper and validating the new quirk’s entity metadata and converters.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
zhaquirks/sonoff/swv.py |
Adds a flow-meter-specific cluster + v2 quirk registration, including byte-swapped real-time counters and new volume/duration sensors. |
tests/test_sonoff.py |
Adds tests for the endianness helper and asserts the new SWV-ZFE/SWV-ZFU quirk exposes the expected entities and converters. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed change
Adds support for the SONOFF SWV-ZFE / SWV-ZFU ("Hydro ONE") — the flow-meter variants of the SWV Zigbee smart water valve. Only the plain
SWVmodel is currently covered byzhaquirks/sonoff/swv.py; the ZFE/ZFU expose additional irrigation counters on the manufacturer cluster0xFC11.This PR extends the existing SWV module with a
CustomSonoffFlowCluster(inheriting the existingwater_valve_state) and a secondQuirkBuilderregistration exposing:water_valve_state0x500C)0x5007, L) and duration (0x5006, min)0x501B, L) and duration (0x501C, min)Firmware endianness gotcha: the two real-time counters are reported big-endian (the hourly ones are normal little-endian). The quirk byte-swaps them via
attribute_converter, matching the workaround zigbee2mqtt applies (bigEndianNumericFzConvertinzigbee-herdsman-converterssonoff.ts, modelSWV-ZFE). Attribute IDs are taken from the same converter.Validated on real hardware (SWV-ZFE, HA 2026.8.1, ZBT-1 coordinator): after a 10-minute watering run the quirk reported 94 L / 10 min, and the independently-read (unswapped) hourly counter agreed at 94 L — cross-confirming the endianness handling in both directions.
Entities in Home Assistant
Additional information
Device signature (endpoint 1):
in: 0x0000, 0x0001, 0x0003, 0x0006, 0x0020, 0xFC11, 0xFC57 / out: 0x0003, 0x0019, manufacturerSONOFF, modelSWV-ZFE(manufacturer code 4742).The
SWV-ZFUis registered viaalso_applies_to— it is the US-thread variant of the same "Hydro ONE" product (zigbee2mqtt treats both as one definition with identical attributes); I only own the ZFE.Device diagnostics
swv-zfe-diagnostics.json (captured while running this quirk as a custom quirk)
{ "home_assistant": { "arch": "x86_64", "dev": false, "docker": true, "hassio": false, "installation_type": "Home Assistant Container", "os_name": "Linux", "os_version": "7.0.0-29-generic", "python_version": "3.14.6", "timezone": "Europe/Paris", "version": "2026.8.1", "virtualenv": false, "container_arch": "amd64", "run_as_root": true }, "custom_components": {}, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "hassio", "onboarding" ], "codeowners": [ "dmulcahey", "adminiuga", "puddly", "TheJulianJES" ], "config_flow": true, "dependencies": [ "file_upload", "homeassistant_hardware", "usb" ], "documentation": "https://www.home-assistant.io/integrations/zha", "integration_type": "hub", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp", "zha", "universal_silabs_flasher", "serialx" ], "requirements": [ "zha==2.1.0", "zha-quirks==2.2.0" ], "usb": [ { "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ], "pid": "EA60", "vid": "10C4" }, { "description": "*slzb-07*", "known_devices": [ "smlight slzb-07" ], "pid": "EA60", "vid": "10C4" }, { "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ], "pid": "55D4", "vid": "1A86" }, { "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ], "pid": "EA60", "vid": "10C4" }, { "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ], "pid": "EA60", "vid": "10C4" }, { "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ], "pid": "7523", "vid": "1A86" }, { "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ], "pid": "7523", "vid": "1A86" }, { "description": "*conbee*", "known_devices": [ "Conbee II" ], "pid": "0030", "vid": "1CF1" }, { "description": "*conbee*", "known_devices": [ "Conbee III" ], "pid": "6015", "vid": "0403" }, { "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ], "pid": "8A2A", "vid": "10C4" }, { "description": "*zigate*", "known_devices": [ "ZiGate+" ], "pid": "6015", "vid": "0403" }, { "description": "*zigate*", "known_devices": [ "ZiGate" ], "pid": "EA60", "vid": "10C4" }, { "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ], "pid": "8B34", "vid": "10C4" }, { "description": "*sonoff*max*", "known_devices": [ "SONOFF Dongle Max MG24" ], "pid": "EA60", "vid": "10C4" }, { "description": "*sonoff*lite*mg21*", "known_devices": [ "sonoff zigbee dongle lite mg21" ], "pid": "EA60", "vid": "10C4" } ], "zeroconf": [ { "name": "tube*", "type": "_esphomelib._tcp.local." }, { "name": "*zigate*", "type": "_zigate-zigbee-gateway._tcp.local." }, { "name": "*zigstar*", "type": "_zigstar_gw._tcp.local." }, { "name": "uzg-01*", "type": "_uzg-01._tcp.local." }, { "name": "slzb-06*", "type": "_slzb-06._tcp.local." }, { "name": "xzg*", "type": "_xzg._tcp.local." }, { "name": "czc*", "type": "_czc._tcp.local." }, { "name": "*", "type": "_zigbee-coordinator._tcp.local." } ], "is_built_in": true, "overwrites_built_in": false }, "setup_times": { "null": { "setup": 6.852502701804042e-05 }, "01M034P2BV5805DPYQT9WKE375": { "wait_import_platforms": -0.009394577995408326, "config_entry_setup": 4.198596392991021 } }, "data": { "version": 3, "ieee": "**REDACTED**", "nwk": "0x32AC", "manufacturer": "SONOFF", "model": "SWV-ZFE", "friendly_manufacturer": "SONOFF", "friendly_model": "SWV-ZFE", "name": "SONOFF SWV-ZFE", "quirk_applied": true, "quirk_class": "swv_zfe:(SONOFF / SWV-ZFE)", "exposes_features": [], "manufacturer_code": 4742, "power_source": "Battery or Unknown", "lqi": 192, "rssi": -63, "last_seen": "2026-08-17T19:09:14.994636+00:00", "available": true, "device_type": "EndDevice", "active_coordinator": false, "node_descriptor": { "logical_type": "EndDevice", "complex_descriptor_available": false, "user_descriptor_available": false, "reserved": 0, "aps_flags": 0, "frequency_band": 8, "mac_capability_flags": 128, "manufacturer_code": 4742, "maximum_buffer_size": 74, "maximum_incoming_transfer_size": 404, "server_mask": 10752, "maximum_outgoing_transfer_size": 404, "descriptor_capability_field": 0 }, "endpoints": { "1": { "profile_id": 260, "device_type": { "name": "ON_OFF_OUTPUT", "id": 2 }, "in_clusters": [ { "cluster_id": "0x0000", "endpoint_attribute": "basic", "attributes": [ { "id": "0x0004", "name": "manufacturer", "zcl_type": "string", "value": "SONOFF" }, { "id": "0x0005", "name": "model", "zcl_type": "string", "value": "SWV-ZFE" } ] }, { "cluster_id": "0x0001", "endpoint_attribute": "power", "attributes": [ { "id": "0x0021", "name": "battery_percentage_remaining", "zcl_type": "uint8", "value": 200 }, { "id": "0x0033", "name": "battery_quantity", "zcl_type": "uint8", "unsupported": true }, { "id": "0x0031", "name": "battery_size", "zcl_type": "enum8", "unsupported": true }, { "id": "0x0020", "name": "battery_voltage", "zcl_type": "uint8", "value": 0 } ] }, { "cluster_id": "0x0003", "endpoint_attribute": "identify", "attributes": [] }, { "cluster_id": "0x0006", "endpoint_attribute": "on_off", "attributes": [ { "id": "0x0000", "name": "on_off", "zcl_type": "bool", "value": 0 }, { "id": "0x4003", "name": "start_up_on_off", "zcl_type": "enum8", "unsupported": true } ] }, { "cluster_id": "0x0020", "endpoint_attribute": "poll_control", "attributes": [ { "id": "0x0003", "name": "fast_poll_timeout", "zcl_type": "uint16", "value": 120 } ] }, { "cluster_id": "0xfc11", "endpoint_attribute": null, "attributes": [ { "id": "0x0000", "name": "child_lock", "zcl_type": "bool", "value": 0 }, { "id": "0x501c", "name": "hour_irrigation_duration", "zcl_type": "uint32", "value": 10 }, { "id": "0x501b", "name": "hour_irrigation_volume", "zcl_type": "uint32", "value": 94 }, { "id": "0x5006", "name": "real_time_irrigation_duration", "zcl_type": "uint32", "value": 167772160 }, { "id": "0x5007", "name": "real_time_irrigation_volume", "zcl_type": "uint32", "value": 1577058304 }, { "id": "0x500c", "name": "water_valve_state", "zcl_type": "enum8", "value": 0 } ] }, { "cluster_id": "0xfc57", "endpoint_attribute": "works_with_all_hubs", "attributes": [] } ], "out_clusters": [ { "cluster_id": "0x0003", "endpoint_attribute": "identify", "attributes": [] }, { "cluster_id": "0x0019", "endpoint_attribute": "ota", "attributes": [ { "id": "0x0002", "name": "current_file_version", "zcl_type": "uint32", "value": 4103 } ], "last_query_cmd": { "manufacturer_code": 4742, "image_type": 8204, "current_file_version": 4103, "hardware_version": null } } ] } }, "original_signature": { "manufacturer": "SONOFF", "model": "SWV-ZFE", "node_desc": { "logical_type": 2, "complex_descriptor_available": 0, "user_descriptor_available": 0, "reserved": 0, "aps_flags": 0, "frequency_band": 8, "mac_capability_flags": 128, "manufacturer_code": 4742, "maximum_buffer_size": 74, "maximum_incoming_transfer_size": 404, "server_mask": 10752, "maximum_outgoing_transfer_size": 404, "descriptor_capability_field": 0 }, "endpoints": { "1": { "profile_id": "0x0104", "device_type": "0x0002", "input_clusters": [ "0x0000", "0x0001", "0x0003", "0x0006", "0x0020", "0xfc57", "0xfc11" ], "output_clusters": [ "0x0003", "0x0019" ] } } }, "zha_lib_entities": { "binary_sensor": [ { "fallback_name": "Water leak", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "binary_sensor", "class_name": "BinarySensor", "translation_key": "water_leak", "translation_placeholders": null, "device_class": "moisture", "state_class": null, "entity_category": "diagnostic", "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "is_on": 0, "attribute_name": "water_valve_state" }, { "fallback_name": "Water supply", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "binary_sensor", "class_name": "BinarySensor", "translation_key": "water_supply", "translation_placeholders": null, "device_class": "problem", "state_class": null, "entity_category": "diagnostic", "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "is_on": 0, "attribute_name": "water_valve_state" } ], "button": [ { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "button", "class_name": "IdentifyButton", "translation_key": null, "translation_placeholders": null, "device_class": "identify", "state_class": null, "entity_category": "diagnostic", "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "command": "identify", "args": [ 5 ], "kwargs": {} } ], "sensor": [ { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "LQISensor", "translation_key": "lqi", "translation_placeholders": null, "device_class": null, "state_class": "measurement", "entity_category": "diagnostic", "entity_registry_enabled_default": false, "enabled": false, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 192, "suggested_display_precision": null, "unit": null }, { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "RSSISensor", "translation_key": "rssi", "translation_placeholders": null, "device_class": "signal_strength", "state_class": "measurement", "entity_category": "diagnostic", "entity_registry_enabled_default": false, "enabled": false, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": -63, "suggested_display_precision": null, "unit": "dBm" }, { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "Battery", "translation_key": null, "translation_placeholders": null, "device_class": "battery", "state_class": "measurement", "entity_category": "diagnostic", "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [ "battery_quantity", "battery_size", "battery_voltage" ], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 100.0, "suggested_display_precision": 0, "unit": "%", "battery_size": null, "battery_quantity": null, "battery_voltage": 0.0 }, { "fallback_name": "Hourly irrigation duration", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "Sensor", "translation_key": "hour_irrigation_duration", "translation_placeholders": null, "device_class": null, "state_class": null, "entity_category": null, "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 10, "suggested_display_precision": null, "unit": "min" }, { "fallback_name": "Hourly irrigation volume", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "Sensor", "translation_key": "hour_irrigation_volume", "translation_placeholders": null, "device_class": null, "state_class": null, "entity_category": null, "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 94, "suggested_display_precision": null, "unit": "L" }, { "fallback_name": "Real-time irrigation duration", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "Sensor", "translation_key": "real_time_irrigation_duration", "translation_placeholders": null, "device_class": null, "state_class": null, "entity_category": null, "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 10, "suggested_display_precision": null, "unit": "min" }, { "fallback_name": "Real-time irrigation volume", "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "sensor", "class_name": "Sensor", "translation_key": "real_time_irrigation_volume", "translation_placeholders": null, "device_class": null, "state_class": null, "entity_category": null, "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "native_value": 94, "suggested_display_precision": null, "unit": "L" } ], "switch": [ { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "switch", "class_name": "Switch", "translation_key": "switch", "translation_placeholders": null, "device_class": null, "state_class": null, "entity_category": null, "entity_registry_enabled_default": true, "enabled": true, "primary": true, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "is_on": 0 } ], "update": [ { "fallback_name": null, "unique_id": "**REDACTED**", "migrate_unique_ids": [], "platform": "update", "class_name": "FirmwareUpdateEntity", "translation_key": null, "translation_placeholders": null, "device_class": "firmware", "state_class": null, "entity_category": "config", "entity_registry_enabled_default": true, "enabled": true, "primary": false, "extra_state_attribute_names": [], "device_ieee": "**REDACTED**", "endpoint_id": 1, "available": true, "group_id": null, "installed_version": "0x00001007", "in_progress": false, "update_percentage": null, "latest_version": null, "release_summary": null, "release_notes": null, "release_url": null, "supported_features": 23 } ] }, "neighbors": [], "routes": [] }, "issues": [] }Checklist
pre-commitchecks pass / the code has been formatted using Black🤖 Generated with Claude Code