zigbee-herdsman-converters 26.120.0 → 26.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [26.121.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v26.120.0...v26.121.0) (2026-10-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * Tuya MTG235-ZB-RL: expose `distance_report` ([#13520](https://github.com/Koenkk/zigbee-herdsman-converters/issues/13520)) ([28c54f7](https://github.com/Koenkk/zigbee-herdsman-converters/commit/28c54f7e58c8bca909dbf5df5ab3131e0ffe7c38))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **ignore:** bump @types/node from 26.6.4 to 26.6.5 in the minor-patch group ([#13523](https://github.com/Koenkk/zigbee-herdsman-converters/issues/13523)) ([623d6df](https://github.com/Koenkk/zigbee-herdsman-converters/commit/623d6df277884dfe4c2c3c8417db52fd512445c4))
14
+ * Tuya ME201WZ: ignore false and out-of-range level readings ([#13504](https://github.com/Koenkk/zigbee-herdsman-converters/issues/13504)) ([62086f6](https://github.com/Koenkk/zigbee-herdsman-converters/commit/62086f63283f4c23d36188e607befc5471e187d6))
15
+ * Tuya ZS-304Z: support negative humidity and temperature calibration ([#13524](https://github.com/Koenkk/zigbee-herdsman-converters/issues/13524)) ([2d506ca](https://github.com/Koenkk/zigbee-herdsman-converters/commit/2d506caf7c71e33d8fbb5d2c244f1b0a31aa2cf3))
16
+ * Visonic MCT-350 SMA: prevent rapid polling after a battery swap ([#13518](https://github.com/Koenkk/zigbee-herdsman-converters/issues/13518)) ([84a2ab4](https://github.com/Koenkk/zigbee-herdsman-converters/commit/84a2ab479b2859d7823ce28d722f54c9ebb54275))
17
+
3
18
  ## [26.120.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v26.119.0...v26.120.0) (2026-10-16)
4
19
 
5
20
 
@@ -1 +1 @@
1
- {"version":3,"file":"tuya.d.ts","sourceRoot":"","sources":["../../src/devices/tuya.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,oBAAoB,EAAkD,MAAM,cAAc,CAAC;AAkwExG,eAAO,MAAM,WAAW,EAAE,oBAAoB,EAyq3B7C,CAAC"}
1
+ {"version":3,"file":"tuya.d.ts","sourceRoot":"","sources":["../../src/devices/tuya.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,oBAAoB,EAAkD,MAAM,cAAc,CAAC;AAs0ExG,eAAO,MAAM,WAAW,EAAE,oBAAoB,EA4q3B7C,CAAC"}
@@ -2197,6 +2197,81 @@ const sp107eLedChips = {
2197
2197
  TM1814: tuya.enum(8),
2198
2198
  SK6812: tuya.enum(9),
2199
2199
  };
2200
+ // The ME201WZ regularly picks up false echoes (e.g. -1.1 m, 5 m, 0.24 m), sometimes for hours. A real level changes
2201
+ // gradually, so drop readings outside the tank or jumping > 5 cm, unless the new level has been read steadily for 12 hours.
2202
+ const me201wzFilterGlitches = (result, state, device) => {
2203
+ const depth = result.liquid_depth;
2204
+ const percent = result.liquid_level_percent;
2205
+ const lastDepth = state.liquid_depth;
2206
+ const height = (result.installation_height ?? state.installation_height);
2207
+ const depthMax = (result.liquid_depth_max ?? state.liquid_depth_max);
2208
+ let glitch = false;
2209
+ if (depth !== undefined) {
2210
+ if (depth < 0 || (height !== undefined && depth > height)) {
2211
+ glitch = true;
2212
+ }
2213
+ else if (lastDepth !== undefined && Math.abs(depth - lastDepth) > 0.05) {
2214
+ const candidate = globalStore.getValue(device, "me201wzCandidate");
2215
+ if (candidate && Math.abs(depth - candidate.depth) <= 0.03) {
2216
+ glitch = Date.now() - candidate.since < 12 * 60 * 60 * 1000;
2217
+ }
2218
+ else {
2219
+ globalStore.putValue(device, "me201wzCandidate", { depth, since: Date.now() });
2220
+ glitch = true;
2221
+ }
2222
+ }
2223
+ if (!glitch)
2224
+ globalStore.clearValue(device, "me201wzCandidate");
2225
+ }
2226
+ else if (percent !== undefined && lastDepth !== undefined && depthMax) {
2227
+ glitch = Math.abs(percent - Math.min(100, (lastDepth / depthMax) * 100)) > 5 / depthMax;
2228
+ }
2229
+ if (glitch) {
2230
+ delete result.liquid_depth;
2231
+ delete result.liquid_level_percent;
2232
+ delete result.liquid_state;
2233
+ return result;
2234
+ }
2235
+ // Hold back a liquid state that doesn't match the level, publish it once an accepted level matches it.
2236
+ const minSet = (result.min_set ?? state.min_set);
2237
+ const maxSet = (result.max_set ?? state.max_set);
2238
+ const level = (percent ?? state.liquid_level_percent);
2239
+ if (minSet === undefined || maxSet === undefined || level === undefined)
2240
+ return result;
2241
+ const matches = (s) => (s === "low" && level <= minSet + 1) ||
2242
+ (s === "high" && level >= maxSet - 1) ||
2243
+ (s === "normal" && level >= minSet - 1 && level <= maxSet + 1);
2244
+ if (result.liquid_state !== undefined && !matches(result.liquid_state)) {
2245
+ globalStore.putValue(device, "me201wzPendingState", result.liquid_state);
2246
+ delete result.liquid_state;
2247
+ }
2248
+ else if (result.liquid_state === undefined && percent !== undefined) {
2249
+ const pending = globalStore.getValue(device, "me201wzPendingState");
2250
+ if (pending !== undefined && pending !== state.liquid_state && matches(pending))
2251
+ result.liquid_state = pending;
2252
+ }
2253
+ if (result.liquid_state !== undefined)
2254
+ globalStore.clearValue(device, "me201wzPendingState");
2255
+ return result;
2256
+ };
2257
+ // Shared by the liquid state, depth and percent datapoints, which are filtered together once per message.
2258
+ const me201wzFiltered = new WeakMap();
2259
+ const me201wzLevel = {
2260
+ from: (v, meta, options, publish, msg) => {
2261
+ if (!me201wzFiltered.has(msg)) {
2262
+ const dp = Object.fromEntries(msg.data.dpValues.map((d) => [d.dp, tuya.getDataValue(d)]));
2263
+ const result = {};
2264
+ if (dp[1] !== undefined)
2265
+ result.liquid_state = tvc.level.from(dp[1], meta, options);
2266
+ if (dp[2] !== undefined)
2267
+ result.liquid_depth = dp[2] / 100;
2268
+ if (dp[22] !== undefined)
2269
+ result.liquid_level_percent = dp[22];
2270
+ me201wzFiltered.set(msg, me201wzFilterGlitches(result, meta.state, meta.device));
2271
+ }
2272
+ return me201wzFiltered.get(msg);
2273
+ },
2274
+ };
2200
2275
  exports.definitions = [
2201
2276
  {
2202
2277
  fingerprint: tuya.fingerprint("TS0601", ["_TZE284_5qfrnbqs"]),
@@ -19903,12 +19978,13 @@ exports.definitions = [
19903
19978
  "it will continue to maintain presence regardless of whether someone is present or not. If set to unoccupied, the unoccupied " +
19904
19979
  "state will be maintained permanently."),
19905
19980
  ];
19906
- if (!device || device.manufacturerName === "_TZE204_dtzziy1e") {
19907
- // MTG275-ZB-RL: DP 116 switches the continuous target_distance reports (about 1/s) on/off.
19908
- // Requires MCU firmware 1.0.5 or newer.
19981
+ if (!device || ["_TZE204_dtzziy1e", "_TZE204_clrdrnya"].includes(device.manufacturerName)) {
19982
+ // MTG275-ZB-RL / MTG235-ZB-RL: DP 116 switches the continuous target_distance reports (about 1/s) on/off.
19983
+ // Requires MCU firmware 1.0.5 (MTG275-ZB-RL) or 1.0.4 (MTG235-ZB-RL) or newer.
19909
19984
  exps.push(e
19910
19985
  .binary("distance_report", ea.STATE_SET, "ON", "OFF")
19911
- .withDescription("Report target distance continuously (about once per second), requires MCU firmware 1.0.5 or newer"));
19986
+ .withDescription("Report target distance continuously (about once per second), requires MCU firmware 1.0.5 (MTG275-ZB-RL) " +
19987
+ "or 1.0.4 (MTG235-ZB-RL) or newer"));
19912
19988
  }
19913
19989
  return exps;
19914
19990
  },
@@ -23091,14 +23167,14 @@ exports.definitions = [
23091
23167
  ],
23092
23168
  meta: {
23093
23169
  tuyaDatapoints: [
23094
- [1, "liquid_state", tvc.level],
23095
- [2, "liquid_depth", tvc.divideBy100],
23170
+ [1, null, me201wzLevel],
23171
+ [2, null, me201wzLevel],
23096
23172
  [5, "voltage", tvc.divideBy10], // power_level
23097
23173
  [7, "max_set", tvc.raw],
23098
23174
  [8, "min_set", tvc.raw],
23099
23175
  [19, "installation_height", tvc.divideBy100],
23100
23176
  [21, "liquid_depth_max", tvc.divideBy100],
23101
- [22, "liquid_level_percent", tvc.raw],
23177
+ [22, null, me201wzLevel],
23102
23178
  [24, "relay_switch", tvc.onOff],
23103
23179
  [101, "pump_mode", tvc.pumpMode],
23104
23180
  [102, "pump_control", tvc.pumpControl], // auto_pump_control
@@ -25799,9 +25875,9 @@ exports.definitions = [
25799
25875
  [102, "illuminance", tuya.valueConverter.multiplyBy10],
25800
25876
  [103, "soil_sampling", tuya.valueConverter.raw],
25801
25877
  [104, "soil_calibration", tuya.valueConverter.raw],
25802
- [105, "humidity_calibration", tuya.valueConverter.raw],
25878
+ [105, "humidity_calibration", tuya.valueConverter.twosComplement8Bit],
25803
25879
  [106, "illuminance_calibration", tuya.valueConverter.raw],
25804
- [107, "temperature_calibration", tuya.valueConverter.divideBy10],
25880
+ [107, "temperature_calibration", tuya.valueConverter.twosComplement8BitScaled10x],
25805
25881
  // DP 110 is asymmetric: device reports the warning state, but setting it configures the threshold.
25806
25882
  // First entry is used for incoming reports, the second one for setting.
25807
25883
  [110, "soil_moisture_warning", { from: tuya.valueConverter.raw.from }],