zigbee-herdsman-converters 25.100.0 → 25.101.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.
@@ -3328,7 +3328,7 @@ exports.definitions = [
3328
3328
  "_TZ3000_n0lphcok",
3329
3329
  "_TZ3000_r80pzsb9",
3330
3330
  ]),
3331
- ...tuya.fingerprint("TS0001", ["_TZ3000_n0lphcok", "_TZ3000_wn65ixz9", "_TZ3000_trdx8uxs"]),
3331
+ ...tuya.fingerprint("TS0001", ["_TZ3000_n0lphcok", "_TZ3000_wn65ixz9", "_TZ3000_trdx8uxs", "_TZ3000_gdsvhfao"]),
3332
3332
  ],
3333
3333
  model: "TS0207_repeater",
3334
3334
  vendor: "Tuya",
@@ -3336,7 +3336,12 @@ exports.definitions = [
3336
3336
  fromZigbee: [fz.linkquality_from_basic],
3337
3337
  toZigbee: [],
3338
3338
  whiteLabel: [
3339
- tuya.whitelabel("Tuya", "TS0001_repeater", "Zigbee signal repeater", ["_TZ3000_n0lphcok", "_TZ3000_wn65ixz9", "_TZ3000_trdx8uxs"]),
3339
+ tuya.whitelabel("Tuya", "TS0001_repeater", "Zigbee signal repeater", [
3340
+ "_TZ3000_n0lphcok",
3341
+ "_TZ3000_wn65ixz9",
3342
+ "_TZ3000_trdx8uxs",
3343
+ "_TZ3000_gdsvhfao",
3344
+ ]),
3340
3345
  ],
3341
3346
  exposes: [],
3342
3347
  },
@@ -5186,34 +5191,56 @@ exports.definitions = [
5186
5191
  },
5187
5192
  {
5188
5193
  fingerprint: tuya.fingerprint("TS0601", ["_TZE200_dzuqwsyg", "_TZE204_dzuqwsyg"]),
5189
- model: "BAC-002",
5194
+ model: "BAC-003",
5190
5195
  vendor: "Tuya",
5191
5196
  description: "FCU thermostat temperature controller",
5192
5197
  extend: [tuya.modernExtend.tuyaBase({ dp: true, forceTimeUpdates: true, timeStart: "1970" })],
5193
5198
  options: [
5194
- e.enum("control_sequence_of_operation", ea.SET, ["cooling_only", "cooling_and_heating_4-pipes"]),
5195
- e.binary("expose_device_state", ea.SET, true, false),
5196
- ],
5197
- exposes: [
5198
5199
  e
5199
- .climate()
5200
- .withLocalTemperature(ea.STATE)
5201
- .withSystemMode(["off", "cool", "heat", "fan_only"], ea.STATE_SET)
5202
- .withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
5203
- .withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET)
5204
- .withPreset(["auto", "manual"])
5205
- .withLocalTemperatureCalibration(-9, 9, 1, ea.STATE_SET),
5206
- e.child_lock(),
5207
- e.max_temperature().withValueMin(35).withValueMax(45),
5208
- e.numeric("deadzone_temperature", ea.STATE_SET).withUnit("°C").withValueMax(5).withValueMin(1),
5209
- e.text("schedule_text", ea.STATE_SET).withDescription(`Weekly schedule in the format "HH:MM/TT HH:MM/TT ...".
5210
- Example for 12 segments:
5211
- "06:00/20 11:30/21 13:30/22 17:30/23 06:00/24 12:00/23 14:30/22 17:30/21 06:00/19 12:30/20 14:30/21 18:30/20".
5212
- Each segment contains:
5213
- - HH:MM: Time in 24-hour format.
5214
- - TT: Temperature in °C.
5215
- Ensure all 12 segments are defined and separated by spaces.`),
5200
+ .enum("control_sequence_of_operation", ea.SET, ["cooling_only", "cooling_and_heating_4-pipes"])
5201
+ .withDescription("Operating environment of the thermostat"),
5202
+ e.binary("expose_device_state", ea.SET, true, false).withDescription("Expose device power state as a separate property when enabled."),
5216
5203
  ],
5204
+ exposes: (device, options) => {
5205
+ const system_modes = ["off", "cool", "heat", "fan_only"];
5206
+ // Device can operate either in 2-pipe or 4-pipe configuration
5207
+ // For 2-pipe configurations remove 'heat' mode
5208
+ switch (options?.control_sequence_of_operation) {
5209
+ case "cooling_only":
5210
+ system_modes.splice(2, 1);
5211
+ break;
5212
+ }
5213
+ const exposes = [
5214
+ e
5215
+ .climate()
5216
+ .withLocalTemperature(ea.STATE)
5217
+ .withSystemMode(system_modes, ea.STATE_SET)
5218
+ .withFanMode(["low", "medium", "high", "auto"], ea.STATE_SET)
5219
+ .withSetpoint("current_heating_setpoint", 5, 35, 1, ea.STATE_SET)
5220
+ .withPreset(["auto", "manual"])
5221
+ .withLocalTemperatureCalibration(-3, 3, 1, ea.STATE_SET),
5222
+ e.child_lock(),
5223
+ e
5224
+ .composite("schedule", "schedule", ea.STATE_SET)
5225
+ .withFeature(e.text("weekdays", ea.SET).withDescription('Schedule (1-5), 4 periods in format "hh:mm/tt".'))
5226
+ .withFeature(e.text("saturday", ea.SET).withDescription('Schedule (6), 4 periods in format "hh:mm/tt".'))
5227
+ .withFeature(e.text("sunday", ea.SET).withDescription('Schedule (7), 4 periods in format "hh:mm/tt".'))
5228
+ .withDescription('Auto-mode schedule, 4 periods each per category. Example: "06:00/20 11:30/21 13:30/22 17:30/23.5".'),
5229
+ e.max_temperature().withValueMin(35).withValueMax(45).withPreset("default", 35, "Default value"),
5230
+ e
5231
+ .numeric("deadzone_temperature", ea.STATE_SET)
5232
+ .withUnit("°C")
5233
+ .withValueMax(5)
5234
+ .withValueMin(1)
5235
+ .withValueStep(1)
5236
+ .withPreset("default", 1, "Default value")
5237
+ .withDescription("The delta between local_temperature and current_heating_setpoint to trigger activity"),
5238
+ ];
5239
+ if (options?.expose_device_state === true) {
5240
+ exposes.unshift(e.binary("state", ea.STATE_SET, "ON", "OFF").withDescription("Turn the thermostat ON or OFF"));
5241
+ }
5242
+ return exposes;
5243
+ },
5217
5244
  meta: {
5218
5245
  publishDuplicateTransaction: true,
5219
5246
  tuyaDatapoints: [
@@ -5228,8 +5255,9 @@ Ensure all 12 segments are defined and separated by spaces.`),
5228
5255
  },
5229
5256
  from: (v, meta, options) => {
5230
5257
  meta.state.system_mode = v === true ? (meta.state.system_mode_device ?? "cool") : "off";
5231
- if (options?.expose_device_state === true)
5258
+ if (options?.expose_device_state === true) {
5232
5259
  return v === true ? "ON" : "OFF";
5260
+ }
5233
5261
  delete meta.state.state;
5234
5262
  },
5235
5263
  },
@@ -5238,22 +5266,22 @@ Ensure all 12 segments are defined and separated by spaces.`),
5238
5266
  2,
5239
5267
  "system_mode",
5240
5268
  {
5269
+ // Extend system_mode to support 'off' in addition to 'cool', 'heat' and 'fan_only'
5241
5270
  to: async (v, meta) => {
5242
- const ep = meta.device.endpoints[0];
5243
- if (v === "off") {
5244
- await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
5245
- await new Promise((r) => setTimeout(r, 120));
5246
- await tuya.sendDataPointBool(ep, 1, false, "dataRequest", 1);
5247
- await tuya.sendDataPointEnum(ep, 2, 0, "dataRequest", 1);
5248
- return;
5271
+ const entity = meta.device.endpoints[0];
5272
+ // Power State
5273
+ await tuya.sendDataPointBool(entity, 1, v !== "off", "dataRequest", 1);
5274
+ switch (v) {
5275
+ case "cool":
5276
+ await tuya.sendDataPointEnum(entity, 2, 0, "dataRequest", 1);
5277
+ break;
5278
+ case "heat":
5279
+ await tuya.sendDataPointEnum(entity, 2, 1, "dataRequest", 1);
5280
+ break;
5281
+ case "fan_only":
5282
+ await tuya.sendDataPointEnum(entity, 2, 2, "dataRequest", 1);
5283
+ break;
5249
5284
  }
5250
- await tuya.sendDataPointBool(ep, 1, true, "dataRequest", 1);
5251
- if (v === "cool")
5252
- await tuya.sendDataPointEnum(ep, 2, 0, "dataRequest", 1);
5253
- if (v === "heat")
5254
- await tuya.sendDataPointEnum(ep, 2, 1, "dataRequest", 1);
5255
- if (v === "fan_only")
5256
- await tuya.sendDataPointEnum(ep, 2, 2, "dataRequest", 1);
5257
5285
  },
5258
5286
  from: (v, meta) => {
5259
5287
  const modes = ["cool", "heat", "fan_only"];
@@ -5262,21 +5290,7 @@ Ensure all 12 segments are defined and separated by spaces.`),
5262
5290
  },
5263
5291
  },
5264
5292
  ],
5265
- [
5266
- 4,
5267
- "preset",
5268
- {
5269
- to: async (v, meta) => {
5270
- const ep = meta.device.endpoints[0];
5271
- await tuya.sendDataPointBool(ep, 4, v === "manual");
5272
- },
5273
- from: (v, meta) => {
5274
- const preset = v ? "manual" : "auto";
5275
- meta.state.preset = preset;
5276
- return preset;
5277
- },
5278
- },
5279
- ],
5293
+ [4, "preset", tuya.valueConverterBasic.lookup({ manual: true, auto: false })],
5280
5294
  [16, "current_heating_setpoint", tuya.valueConverter.raw],
5281
5295
  [19, "max_temperature", tuya.valueConverter.raw],
5282
5296
  [24, "local_temperature", tuya.valueConverter.divideBy10],
@@ -5297,39 +5311,46 @@ Ensure all 12 segments are defined and separated by spaces.`),
5297
5311
  101,
5298
5312
  "schedule",
5299
5313
  {
5300
- from: (v, meta) => {
5301
- const format = (data) => data.reduce((txt, val, i) => {
5302
- if (i % 3 === 0)
5303
- return `${txt}${i > 0 ? " " : ""}${val.toString().padStart(2, "0")}`;
5304
- if (i % 3 === 1)
5305
- return `${txt}:${val.toString().padStart(2, "0")}`;
5306
- return `${txt}/${val / 2}`;
5307
- }, "");
5308
- const weekdays = format(v.slice(0, 12));
5309
- const saturday = format(v.slice(12, 24));
5310
- const sunday = format(v.slice(24, 36));
5311
- const full = `${weekdays} ${saturday} ${sunday}`.trim();
5312
- meta.state.schedule_text = full;
5313
- return full;
5314
+ to: (v, meta) => {
5315
+ const periods = (value) => {
5316
+ const regex = /((?<h>[01][0-9]|2[0-3]):(?<m>[0-5][0-9])\/(?<t>[0-3][0-9](\.[0,5]|)))/gm;
5317
+ const matches = [...value.matchAll(regex)];
5318
+ if (matches.length === 4) {
5319
+ return matches.reduce((arr, m) => {
5320
+ arr.push(Number.parseInt(m.groups.h, 10));
5321
+ arr.push(Number.parseInt(m.groups.m, 10));
5322
+ arr.push(Number.parseFloat(m.groups.t) * 2);
5323
+ return arr;
5324
+ }, []);
5325
+ }
5326
+ logger_1.logger.warning("Ignoring invalid or incomplete schedule", NS);
5327
+ };
5328
+ const schedule = [...periods(v.weekdays), ...periods(v.saturday), ...periods(v.sunday)];
5329
+ return schedule;
5314
5330
  },
5315
- },
5316
- ],
5317
- [
5318
- 202,
5319
- "schedule_text",
5320
- {
5321
- to: async (v, meta) => {
5322
- const regex = /((?<h>[01][0-9]|2[0-3]):(?<m>[0-5][0-9])\/(?<t>[0-3]?[0-9](\.[0,5])?))/gm;
5323
- const matches = [...v.matchAll(regex)];
5324
- if (matches.length !== 12)
5325
- return;
5326
- const result = [];
5327
- for (const m of matches) {
5328
- result.push(Number(m.groups?.h));
5329
- result.push(Number(m.groups?.m));
5330
- result.push(Number(m.groups?.t) * 2);
5331
- }
5332
- await tuya.sendDataPointRaw(meta.device.endpoints[0], 101, Buffer.from(result));
5331
+ from: (v, meta) => {
5332
+ const format = (data) => {
5333
+ return data.reduce((a, v, i) => {
5334
+ switch (i % 3) {
5335
+ // Hour
5336
+ case 0:
5337
+ return `${a}${i > 0 ? " " : ""}${v.toString().padStart(2, "0")}`;
5338
+ // Minute
5339
+ case 1:
5340
+ return `${a}:${v.toString().padStart(2, "0")}`;
5341
+ // Setpoint
5342
+ case 2:
5343
+ return `${a}/${v / 2}`;
5344
+ default:
5345
+ throw new Error(`Unexpected index ${i} in schedule data`);
5346
+ }
5347
+ }, "");
5348
+ };
5349
+ return {
5350
+ weekdays: format(v.slice(0, 12)),
5351
+ saturday: format(v.slice(1 * 12, 2 * 12)),
5352
+ sunday: format(v.slice(2 * 12, 3 * 12)),
5353
+ };
5333
5354
  },
5334
5355
  },
5335
5356
  ],
@@ -22657,8 +22678,8 @@ Ensure all 12 segments are defined and separated by spaces.`),
22657
22678
  .withLocalTemperature(ea.STATE)
22658
22679
  .withLocalTemperatureCalibration(-9.9, 9.9, 0.1, ea.STATE_SET)
22659
22680
  .withSystemMode(["off", "heat"], ea.STATE_SET)
22660
- .withRunningState(["idle", "heat"], ea.STATE),
22661
- e.enum("preset", ea.STATE_SET, ["manual", "auto", "eco"]).withDescription("Preset mode: manual, auto or eco"),
22681
+ .withRunningState(["idle", "heat"], ea.STATE)
22682
+ .withPreset(["auto", "manual", "eco"]),
22662
22683
  e.child_lock(),
22663
22684
  e.battery(),
22664
22685
  e.enum("valve_state", ea.STATE, ["open", "close"]).withDescription("Valve state (open/close)"),