### What happened?
When attempting to read the `seasonal_watering_adjustment` f…rom the **SONOFF Hydro ONE (SWV-ZFE)** smart water valve, Zigbee2MQTT crashes or reports a ZCL command error with `RangeError [ERR_OUT_OF_RANGE]: The value of "offset" is out of range`.
Specifically, running:
```bash
mosquitto_pub -h <mqtt_broker> -t "zigbee2mqtt/<device_name>/get" -m '{"seasonal_watering_adjustment": ""}'
```
produces this error in the Zigbee2MQTT bridge logs:
```
z2m: Publish 'get' 'seasonal_watering_adjustment' to '<device_name>' failed: 'RangeError [ERR_OUT_OF_RANGE]: ZCL command 0xa4c138144fa6ffff/1 customClusterEwelink.read(["seasonalWateringAdjustment"], {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"reservedBits":0,"writeUndiv":false}) failed (The value of "offset" is out of range. It must be >= 0 and <= 21. Received 22)'
```
### What did you expect to happen?
The request should succeed, and Zigbee2MQTT should report the current monthly adjustment multipliers for all 12 months.
### How to reproduce it (minimal and precise)
1. Set the seasonal watering adjustment:
```bash
mosquitto_pub -h <mqtt_broker> -t "zigbee2mqtt/<device_name>/set" -m '{"seasonal_watering_adjustment": {"january": 0.1, "february": 0.1, "march": 1, "april": 1, "may": 1.3, "june": 1.8, "july": 2, "august": 2, "september": 1.5, "october": 1, "november": 0.1, "december": 0.1}}'
```
2. Retrieve the seasonal watering adjustment:
```bash
mosquitto_pub -h <mqtt_broker> -t "zigbee2mqtt/<device_name>/get" -m '{"seasonal_watering_adjustment": ""}'
```
3. Observe the crash/error in the bridge logs.
### Zigbee2MQTT version
2.12.0
### Adapter firmware version
Koenk/Z-Stack_3.x.0_coordinator_20250321
### Adapter
Sonoff 3.0 USB Dongle Plus (ZBDongle-P)
### Setup
Zigbee2MQTT installed in Home Assistant Core 2026.6.4
running in a Qemu/libvirt VM on x86_64 with USB passthrough.
- **Zigbee2MQTT Version:** 2.12.0
- **Device Type:** Sonoff Smart Water Valve SWV-ZFE
- **Device Firmware ID:** 1.0.7 (20260317)
- **Device Firmware Version:** 4103
### Device `database.db` entry
{"id":10,"type":"EndDevice","ieeeAddr":"0xa4c138144fa6ffff","nwkAddr":56373,"manufId":4742,"manufName":"SONOFF","powerSource":"Battery","modelId":"SWV-ZFE","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":2,"inClusterList":[0,1,3,6,32,64599,64529],"outClusterList":[3,25],"clusters":{"genOnOff":{"attributes":{"onOff":1}},"genBasic":{"attributes":{"hwVersion":0}},"genPollCtrl":{"attributes":{"checkinInterval":14400}},"genPowerCfg":{"attributes":{"batteryPercentageRemaining":200}},"customClusterEwelink":{"attributes":{"20504":[0,0,0,0],"manualDefaultSettings":[0,0,6,0,6,0,10,1,0,0,0,30],"childLock":0,"rainDelayEndDatetime":0,"valveAlarmSettings":[7,5,1,5],"realTimeIrrigationDuration":100663296,"realTimeIrrigationVolume":134217728,"hourIrrigationDuration":0,"hourIrrigationVolume":0,"seasonalWateringAdjustment":[1,1,10,12,14,18,20,20,15,10,1,1],"valveAbnormalState":0,"irrigationScheduleStatus":[0,0,1,0,0,0,213,228,0,0,215,76,1,0,0]}}},"binds":[{"cluster":32,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c273da","endpointID":1},{"cluster":6,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c273da","endpointID":1},{"cluster":1,"type":"endpoint","deviceIeeeAddress":"0x00124b0024c273da","endpointID":1}],"configuredReportings":[{"cluster":1,"attrId":33,"minRepIntval":3600,"maxRepIntval":65000,"repChange":10}],"meta":{}}},"appVersion":16,"hwVersion":0,"dateCode":"20260317","swBuildId":"1.0.7","zclVersion":8,"interviewCompleted":true,"interviewState":"SUCCESSFUL","meta":{"configured":"0.0.0"},"lastSeen":1782911609403,"checkinInterval":3600}
### Debug log
The debug logs show the raw buffer response and the resulting crash stack trace:
```log
[2026-06-30 18:43:41] debug: zh:zstack:znp: <-- AREQ: AF - incomingMsg - {"groupid":0,"clusterid":64529,"srcaddr":56373,"srcendpoint":1,"dstendpoint":1,"wasbroadcast":0,"linkquality":0,"securityuse":0,"timestamp":7075077,"transseqnumber":0,"len":22,"data":{"type":"Buffer","data":[24,1,1,30,80,0,72,72,12,0,0,0,0,0,0,0,0,0,0,0,0,0]}}
[2026-06-30 18:43:41] debug: zh:controller: Failed to parse frame: RangeError [ERR_OUT_OF_RANGE]: The value of "offset" is out of range. It must be >= 0 and <= 21. Received 22
[2026-06-30 18:43:41] debug: zh:controller: Received payload: clusterID=64529, address=56373, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=0, frame=undefined
[2026-06-30 18:43:41] debug: zh:controller:endpoint: RangeError: ZCL command 0xa4c138144fa6ffff/1 customClusterEwelink.read(["seasonalWateringAdjustment"], {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"reservedBits":0,"writeUndiv":false}) failed (The value of "offset" is out of range. It must be >= 0 and <= 21. Received 22)
at boundsError (node:internal/buffer:88:9)
at Buffer.readUInt8 (node:internal/buffer:254:5)
at BuffaloZcl.readUInt8 (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/buffalo/buffalo.ts:42:35)
at BuffaloZcl.readArray (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/buffaloZcl.ts:147:34)
at BuffaloZcl.read (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/buffaloZcl.ts:1108:29)
at BuffaloZcl.readArray (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/buffaloZcl.ts:153:36)
at BuffaloZcl.read (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/buffaloZcl.ts:1108:29)
at Object.parse (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/definition/foundation.ts:84:44)
at Function.parsePayloadGlobal (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/zclFrame.ts:193:24)
at Function.parsePayload (/app/node_modules/.pnpm/zigbee-herdsman@10.4.0/node_modules/zigbee-herdsman/src/zspec/zcl/zclFrame.ts:146:29)
```
### Notes
## Analysis & Cause
The underlying issue is a mismatch between how the Sonoff/eWeLink device formats custom array attributes and how standard ZCL Arrays are parsed by `zigbee-herdsman`'s buffalo reader.
### 1. The Attribute Definition
In `zigbee-herdsman-converters` (specifically `src/devices/sonoff.ts`), the custom cluster attributes are defined using `DataType.ARRAY` (0x48):
```typescript
seasonalWateringAdjustment: {ID: 0x501e, type: Zcl.DataType.ARRAY, write: true},
manualDefaultSettings: {ID: 0x501d, type: Zcl.DataType.ARRAY, write: true},
```
### 2. The standard ZCL Array spec vs. eWeLink implementation
According to the Zigbee Cluster Library (ZCL) spec, a `DataType.ARRAY` payload contains:
- `elementType` (1 byte)
- `numberOfElements` (2 bytes, 16-bit little-endian)
- `elements` (N elements, sized based on `elementType`)
However, Sonoff/eWeLink devices do **not** use a 2-byte length field. Instead, they use a **1-byte length** field for arrays inside their custom cluster (e.g. cluster `0xfc11`):
- `elementType` (1 byte, value `0x20` for uint8)
- `numberOfElements` (1 byte, value `0x0c` for 12)
- `elements` (12 bytes)
- `checkCode` / `checksum` (1 byte, appended at the end of custom command payloads)
This results in a total ZCL payload attribute value of 14 bytes (plus 1 byte checksum = 15 bytes).
### 3. Parsing error in `zigbee-herdsman`
In `zigbee-herdsman`'s `src/zspec/zcl/buffaloZcl.ts`, the `readArray()` method parses array attributes as follows:
```typescript
private readArray(): unknown[] {
const values: unknown[] = [];
const elementType = this.readUInt8();
const numberOfElements = this.readLengthUInt16(); // <--- Reads 2 bytes!
if (!Number.isNaN(numberOfElements)) {
for (let i = 0; i < numberOfElements; i++) {
const value = this.read(elementType, {});
values.push(value);
}
}
return values;
}
```
Because `readLengthUInt16()` reads **2 bytes** starting at the length offset:
- The low byte is read as `0x0c` (which is the actual 1-byte length).
- The high byte is read from the first element of the array (January = `0x01`).
- The parser interprets the array length (`numberOfElements`) as `0x010c` (which is **268 elements**).
The loop then attempts to read 268 elements from the buffer. Since the response buffer length is only 22/23 bytes, it quickly runs out of bounds and throws `RangeError [ERR_OUT_OF_RANGE]: The value of "offset" is out of range` on the 13th element read (offset 22).
### 4. Impact on other attributes
This also impacts other attributes defined as `Zcl.DataType.ARRAY` like `manualDefaultSettings`:
- For `manualDefaultSettings` (12 elements), the first element is `mode` (typically `0` for duration).
- If `mode` is `0`, the parsed 2-byte length is `0x000c` (12). This prevents a crash, but all elements are **shifted by 1 byte**, and the actual first element (`mode` = 0) is lost because it is consumed as part of the 2-byte length. If `mode` is set to `1` or `2`, reading this attribute will also crash with a `RangeError`.
#### Payload Buffer Decoding:
The raw payload byte array is:
`[24, 1, 1, 30, 80, 0, 72, 72, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]`
- **Bytes 0–2 (ZCL Header):** `24, 1, 1` (Direction: Server to Client, Command: Read Attributes Response)
- **Bytes 3–4 (Attribute ID):** `30, 80` (`0x501e` low/high byte for `seasonalWateringAdjustment`)
- **Byte 5 (Status):** `0` (`SUCCESS`)
- **Byte 6 (Attribute Data Type):** `72` (`0x48` = `Zcl.DataType.ARRAY`)
- **Byte 7 (Array Element Type):** `72` (`0x48` = `Zcl.DataType.ARRAY`) *(Here is the anomaly: the device reports the elements themselves are also arrays)*
- **Bytes 8–9 (Number of Elements):** `12, 0` (`0x000c` = 12 elements)
- **Bytes 10–21 (Payload Elements):** 12 bytes of `0`s (`0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0`)
Because the parsed element type is `0x48` (ARRAY), the parser calls `readArray()` recursively for each of the 12 elements:
- **Sub-Array 0 (starting at byte 10):** Reads `elementType` = `0` (`NO_DATA`), `numberOfElements` = `0` (reads 2 bytes). Consumes 3 bytes (`0, 0, 0`). Returns `[]`. (New offset is 13)
- **Sub-Array 1 (starting at byte 13):** Reads `elementType` = `0` (`NO_DATA`), `numberOfElements` = `0`. Consumes 3 bytes (`0, 0, 0`). Returns `[]`. (New offset is 16)
- **Sub-Array 2 (starting at byte 16):** Reads `elementType` = `0` (`NO_DATA`), `numberOfElements` = `0`. Consumes 3 bytes (`0, 0, 0`). Returns `[]`. (New offset is 19)
- **Sub-Array 3 (starting at byte 19):** Reads `elementType` = `0` (`NO_DATA`), `numberOfElements` = `0`. Consumes 3 bytes (`0, 0, 0`). Returns `[]`. (New offset is 22)
- **Sub-Array 4 (starting at byte 22):** The parser attempts to read the `elementType` at offset 22.
Since the total ZCL payload is exactly 22 bytes long (indices 0 to 21), offset 22 is out of bounds, throwing the `RangeError [ERR_OUT_OF_RANGE]`.
This shows that when the device memory contains uninitialized/default data (all `0` elements with `0x48` element type), a recursive `readArray` loop is triggered that parses the remaining buffer bytes as child empty arrays and crashes.
---
## Possible Fix
`Zcl.DataType.ARRAY` (0x48) could be avoided for these custom attributes. An option would be to use `OCTET_STR` (0x41) or maybe `LONG_OCTET_STR` (0x42).
With that `seasonalWateringAdjustment`, `manualDefaultSettings`, and other eWeLink array attributes could be redefined as `Zcl.DataType.OCTET_STR`. With this
there would be a 1-byte length prefix followed by raw bytes, matching the device's actual format.
In `fromZigbee` and `toZigbee`, the values could then be treated as raw buffers/Uint8Arrays.
Another option, if the device writes standard datatype bytes in response packets, could implement custom `toZigbee` and `fromZigbee` logic that bypasses the built-in herdsman array parser, or map them to raw custom buffer handlers.