zigbee-herdsman-converters 25.9.0 → 25.11.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/devices/bosch.d.ts.map +1 -1
  3. package/dist/devices/bosch.js +379 -54
  4. package/dist/devices/bosch.js.map +1 -1
  5. package/dist/devices/custom_devices_diy.d.ts.map +1 -1
  6. package/dist/devices/custom_devices_diy.js +11 -3
  7. package/dist/devices/custom_devices_diy.js.map +1 -1
  8. package/dist/devices/giex.js +2 -2
  9. package/dist/devices/innr.d.ts.map +1 -1
  10. package/dist/devices/innr.js +7 -0
  11. package/dist/devices/innr.js.map +1 -1
  12. package/dist/devices/inovelli.d.ts.map +1 -1
  13. package/dist/devices/inovelli.js +201 -223
  14. package/dist/devices/inovelli.js.map +1 -1
  15. package/dist/devices/lumi.d.ts.map +1 -1
  16. package/dist/devices/lumi.js +7 -2
  17. package/dist/devices/lumi.js.map +1 -1
  18. package/dist/devices/onokom.d.ts.map +1 -1
  19. package/dist/devices/onokom.js +621 -51
  20. package/dist/devices/onokom.js.map +1 -1
  21. package/dist/devices/philips.d.ts.map +1 -1
  22. package/dist/devices/philips.js +17 -2
  23. package/dist/devices/philips.js.map +1 -1
  24. package/dist/devices/seastar_intelligence.js +1 -1
  25. package/dist/devices/seastar_intelligence.js.map +1 -1
  26. package/dist/devices/slacky_diy.d.ts.map +1 -1
  27. package/dist/devices/slacky_diy.js +249 -2
  28. package/dist/devices/slacky_diy.js.map +1 -1
  29. package/dist/devices/sprut.d.ts.map +1 -1
  30. package/dist/devices/sprut.js +124 -0
  31. package/dist/devices/sprut.js.map +1 -1
  32. package/dist/devices/tuya.d.ts.map +1 -1
  33. package/dist/devices/tuya.js +12 -6
  34. package/dist/devices/tuya.js.map +1 -1
  35. package/dist/index.js +2 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/models-index.json +1 -1
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [25.11.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v25.10.0...v25.11.0) (2025-08-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * **add:** AE 287 C ([#9864](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9864)) ([5f927fd](https://github.com/Koenkk/zigbee-herdsman-converters/commit/5f927fd292fbba0953c5a637002d7ffe952489bb))
9
+ * **add:** Drivent ([#9874](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9874)) ([c8dcb6f](https://github.com/Koenkk/zigbee-herdsman-converters/commit/c8dcb6f2bedef654698bd2840beaaf79005d596d))
10
+ * **add:** m5NanoC6 ([#9866](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9866)) ([95ca5db](https://github.com/Koenkk/zigbee-herdsman-converters/commit/95ca5db499581c2687ec830e1f98fa5a2ae3e055))
11
+ * Bosch `BSP-FZ2` (Plug compact II): Support reset of energy meter and lower min value change on electricityMeter reporting ([#9865](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9865)) ([0615f2d](https://github.com/Koenkk/zigbee-herdsman-converters/commit/0615f2d6999ab011ef22b7ee9414ae1181b6df24))
12
+ * Onokom: various improvements ([#9869](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9869)) ([15acfc4](https://github.com/Koenkk/zigbee-herdsman-converters/commit/15acfc4f8e148e5b09546541608e2c1822ee8ec4))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * Create inovelliMMWave modern extend to relocate mmWave commands and support future expansion ([#9870](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9870)) ([a88b08c](https://github.com/Koenkk/zigbee-herdsman-converters/commit/a88b08ca0093d38d9bffeeecd9ad55b5cfbc793f))
18
+ * **detect:** Detect `_TZ3000_fawk5xjv` and `_TZ3000_ok0ggpk7` as a Detect _TZ3000_fawk5xjv and _TZ3000_ok0ggpk7 as a TS0003_switch_3_gang_with_backlight ([#9861](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9861)) ([1b539d2](https://github.com/Koenkk/zigbee-herdsman-converters/commit/1b539d2a9309432fcaf34236feddc523e56027a0))
19
+ * **detect:** Detect `_TZE200_jfw0a4aa` as ZG-102ZM and `_TZE200_hdih4foa` as ZG-204ZH ([#9872](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9872)) ([8437961](https://github.com/Koenkk/zigbee-herdsman-converters/commit/8437961b762ce769cd10fadd0dd2a731aac2a5ed))
20
+ * **detect:** Detect `_TZE284_1lvln0x6` as iHsenso_TS0601_human_presence https://github.com/Koenkk/zigbee2mqtt/issues/28398 ([b60f84f](https://github.com/Koenkk/zigbee-herdsman-converters/commit/b60f84fd14036c12a380f6f04e2b91681c2d5ec0))
21
+ * **detect:** Detect `lumi.light.agl002` as Aqara T2_E26 https://github.com/Koenkk/zigbee2mqtt/issues/26182 ([e221d02](https://github.com/Koenkk/zigbee-herdsman-converters/commit/e221d023ec8d188c4f25ba3b19bdd87c0154b3a0))
22
+ * **ignore:** Bosch `BMCT-SLZ` (Light/shutter control unit II): Only expose attributes compatible with the selected switch type ([#9862](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9862)) ([7eecc4e](https://github.com/Koenkk/zigbee-herdsman-converters/commit/7eecc4e751812a27ce65f38565199dcd63cac809))
23
+
24
+ ## [25.10.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v25.9.0...v25.10.0) (2025-08-27)
25
+
26
+
27
+ ### Features
28
+
29
+ * **add:** 929003808801 ([#9853](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9853)) ([e1e7f12](https://github.com/Koenkk/zigbee-herdsman-converters/commit/e1e7f12bf5210a9a363ee46451908f6c0f5207b7))
30
+ * **add:** 929004297402 https://github.com/Koenkk/zigbee2mqtt/issues/28397 ([d238f93](https://github.com/Koenkk/zigbee-herdsman-converters/commit/d238f93bb8934fc51fe43a895d0b6adc09c1423c))
31
+ * **add:** AirQ_Monitor_S01 ([#9841](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9841)) ([ff8b222](https://github.com/Koenkk/zigbee-herdsman-converters/commit/ff8b222636a76ee0ce98e1cc11bbcfeb601934d9))
32
+ * **add:** FUT068ZR ([#9859](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9859)) ([56105e0](https://github.com/Koenkk/zigbee-herdsman-converters/commit/56105e0016f4e9e695998c91ba2161f8c2cbbf73))
33
+ * Zemismart TB26-3: support more features ([#9849](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9849)) ([067a499](https://github.com/Koenkk/zigbee-herdsman-converters/commit/067a499849b2630fea779cbfa4584ccb55816a70))
34
+
35
+
36
+ ### Bug Fixes
37
+
38
+ * Bosch `BMCT-SLZ`: various enhancements and fixes ([#9852](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9852)) ([2902e7c](https://github.com/Koenkk/zigbee-herdsman-converters/commit/2902e7ca4d9f422f35375501652e3ea79e854d74))
39
+ * Correct vendor from GiEX to GIEX ([#9848](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9848)) ([d214855](https://github.com/Koenkk/zigbee-herdsman-converters/commit/d21485546be6642d3a929c11f0b49bd17473bcb3))
40
+ * Create custom modernExtend classes to use with Inovelli devices ([#9835](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9835)) ([e3e76ad](https://github.com/Koenkk/zigbee-herdsman-converters/commit/e3e76ad2b628a4841b248ad163484c8dd8455354))
41
+ * **detect:** Detect `929003811201` as Philips 5633030P6 ([#9851](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9851)) ([ba349f0](https://github.com/Koenkk/zigbee-herdsman-converters/commit/ba349f0728767df85cbee1884a51f6dc6f250eb5))
42
+ * Tuya `_TZ3000_okaz9tjs`: fix configure failing https://github.com/Koenkk/zigbee-herdsman-converters/issues/9786 ([1d9f8bb](https://github.com/Koenkk/zigbee-herdsman-converters/commit/1d9f8bbc1309d91e71368b9ff71db19b50246bda))
43
+ * Various code fixes ([#9854](https://github.com/Koenkk/zigbee-herdsman-converters/issues/9854)) ([7ba943e](https://github.com/Koenkk/zigbee-herdsman-converters/commit/7ba943e6abaa9978c00d048dfeac80d3721fd4ae))
44
+
3
45
  ## [25.9.0](https://github.com/Koenkk/zigbee-herdsman-converters/compare/v25.8.0...v25.9.0) (2025-08-25)
4
46
 
5
47
 
@@ -1 +1 @@
1
- {"version":3,"file":"bosch.d.ts","sourceRoot":"","sources":["../../src/devices/bosch.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,oBAAoB,EAAyC,MAAM,cAAc,CAAC;AAwuC/F,eAAO,MAAM,WAAW,EAAE,oBAAoB,EA68B7C,CAAC"}
1
+ {"version":3,"file":"bosch.d.ts","sourceRoot":"","sources":["../../src/devices/bosch.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,oBAAoB,EAA6C,MAAM,cAAc,CAAC;AA44CnG,eAAO,MAAM,WAAW,EAAE,oBAAoB,EAmsC7C,CAAC"}
@@ -38,10 +38,12 @@ const zigbee_herdsman_1 = require("zigbee-herdsman");
38
38
  const fz = __importStar(require("../converters/fromZigbee"));
39
39
  const tz = __importStar(require("../converters/toZigbee"));
40
40
  const constants = __importStar(require("../lib/constants"));
41
+ const constants_1 = require("../lib/constants");
41
42
  const exposes = __importStar(require("../lib/exposes"));
42
43
  const logger_1 = require("../lib/logger");
43
44
  const m = __importStar(require("../lib/modernExtend"));
44
45
  const reporting = __importStar(require("../lib/reporting"));
46
+ const reporting_1 = require("../lib/reporting");
45
47
  const globalStore = __importStar(require("../lib/store"));
46
48
  const utils = __importStar(require("../lib/utils"));
47
49
  const e = exposes.presets;
@@ -352,6 +354,38 @@ const boschExtend = {
352
354
  isModernExtend: true,
353
355
  };
354
356
  },
357
+ seMeteringCluster: () => m.deviceAddCustomCluster("seMetering", {
358
+ ID: zigbee_herdsman_1.Zcl.Clusters.seMetering.ID,
359
+ attributes: {},
360
+ commands: {
361
+ resetEnergyReading: {
362
+ ID: 0x80,
363
+ parameters: [],
364
+ },
365
+ },
366
+ commandsResponse: {},
367
+ }),
368
+ resetEnergyReading: () => {
369
+ const exposes = [
370
+ e
371
+ .enum("reset_energy_reading", ea.SET, ["reset"])
372
+ .withDescription("Triggers the reset of the energy reading to 0 kWh.")
373
+ .withCategory("config"),
374
+ ];
375
+ const toZigbee = [
376
+ {
377
+ key: ["reset_energy_reading"],
378
+ convertSet: async (entity, key, value, meta) => {
379
+ await entity.command("seMetering", "resetEnergyReading", {}, manufacturerOptions);
380
+ },
381
+ },
382
+ ];
383
+ return {
384
+ exposes,
385
+ toZigbee,
386
+ isModernExtend: true,
387
+ };
388
+ },
355
389
  doorWindowContact: (hasVibrationSensor) => {
356
390
  const exposes = [
357
391
  e.binary("contact", ea.STATE, false, true).withDescription("Indicates whether the device is opened or closed"),
@@ -789,21 +823,59 @@ const boschExtend = {
789
823
  stopped: 0x00,
790
824
  opening: 0x01,
791
825
  closing: 0x02,
826
+ unknownOne: 0x03,
827
+ unknownTwo: 0x04,
792
828
  };
793
829
  const stateSwitchType = {
794
830
  button: 0x01,
795
831
  button_key_change: 0x02,
796
832
  rocker_switch: 0x03,
797
833
  rocker_switch_key_change: 0x04,
834
+ none: 0x00,
835
+ };
836
+ const stateSwitchMode = {
837
+ coupled: 0x00,
838
+ decoupled: 0x01,
839
+ only_short_press_decoupled: 0x02,
840
+ only_long_press_decoupled: 0x03,
798
841
  };
799
842
  const stateOffOn = {
800
843
  OFF: 0x00,
801
844
  ON: 0x01,
802
845
  };
803
846
  const fromZigbee = [
804
- fz.on_off,
847
+ fz.on_off_force_multiendpoint,
805
848
  fz.power_on_behavior,
806
849
  fz.cover_position_tilt,
850
+ {
851
+ cluster: "boschSpecific",
852
+ type: ["raw"],
853
+ convert: (model, msg, publish, options, meta) => {
854
+ const command = msg.data[4];
855
+ if (command !== 0x03 && command !== 0x04) {
856
+ return;
857
+ }
858
+ let state;
859
+ const status = msg.data[5];
860
+ const duration = msg.data[6] / 10;
861
+ switch (status) {
862
+ case 0:
863
+ state = "press_released";
864
+ break;
865
+ case 1:
866
+ state = duration !== 0 ? "hold" : "hold_released";
867
+ break;
868
+ case 2:
869
+ state = "closed";
870
+ break;
871
+ case 3:
872
+ state = "opened";
873
+ break;
874
+ }
875
+ const triggeredSide = command === 0x03 ? "left" : "right";
876
+ return { action: `${state}_${triggeredSide}`, action_duration: duration };
877
+ },
878
+ },
807
879
  {
808
880
  cluster: "boschSpecific",
809
881
  type: ["attributeReport", "readResponse"],
@@ -819,7 +891,16 @@ const boschExtend = {
819
891
  }
820
892
  }
821
893
  if (data.switchType !== undefined) {
822
- result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === msg.data.switchType);
894
+ const switchType = msg.data.switchType;
895
+ result.switch_type = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === switchType);
896
+ if (switchType !== meta.device.meta.switchType) {
897
+ meta.device.meta.switchType = switchType;
898
+ meta.deviceExposesChanged();
899
+ }
900
+ }
901
+ if (data.switchMode !== undefined) {
902
+ const property = utils.postfixWithEndpointName("switch_mode", msg, model, meta);
903
+ result[property] = Object.keys(stateSwitchMode).find((key) => stateSwitchMode[key] === msg.data.switchMode);
823
904
  }
824
905
  if (data.calibrationOpeningTime !== undefined) {
825
906
  result.calibration_opening_time = msg.data.calibrationOpeningTime / 10;
@@ -840,6 +921,14 @@ const boschExtend = {
840
921
  if (data.motorState !== undefined) {
841
922
  result.motor_state = Object.keys(stateMotor).find((key) => stateMotor[key] === msg.data.motorState);
842
923
  }
924
+ if (data.autoOffEnabled !== undefined) {
925
+ const property = utils.postfixWithEndpointName("auto_off_enabled", msg, model, meta);
926
+ result[property] = msg.data.autoOffEnabled === 1 ? "ON" : "OFF";
927
+ }
928
+ if (data.autoOffTime !== undefined) {
929
+ const property = utils.postfixWithEndpointName("auto_off_time", msg, model, meta);
930
+ result[property] = msg.data.autoOffTime / 60;
931
+ }
843
932
  return result;
844
933
  },
845
934
  },
@@ -848,7 +937,17 @@ const boschExtend = {
848
937
  tz.power_on_behavior,
849
938
  tz.cover_position_tilt,
850
939
  {
851
- key: ["device_mode", "switch_type", "child_lock", "state", "on_time", "off_wait_time"],
940
+ key: [
941
+ "device_mode",
942
+ "switch_type",
943
+ "switch_mode",
944
+ "child_lock",
945
+ "state",
946
+ "on_time",
947
+ "off_wait_time",
948
+ "auto_off_enabled",
949
+ "auto_off_time",
950
+ ],
852
951
  convertSet: async (entity, key, value, meta) => {
853
952
  if (key === "state") {
854
953
  if ("ID" in entity && entity.ID === 1) {
@@ -870,15 +969,44 @@ const boschExtend = {
870
969
  return { state: { device_mode: value } };
871
970
  }
872
971
  if (key === "switch_type") {
873
- const index = utils.getFromLookup(value, stateSwitchType);
874
- await entity.write("boschSpecific", { switchType: index });
972
+ const applyDefaultForSwitchModeAndChildLock = async (endpoint) => {
973
+ const switchModeDefault = utils.getFromLookup("coupled", stateSwitchMode);
974
+ const childLockDefault = utils.getFromLookup("OFF", stateOffOn);
975
+ await endpoint.write("boschSpecific", {
976
+ switchMode: switchModeDefault,
977
+ childLock: childLockDefault,
978
+ });
979
+ await endpoint.read("boschSpecific", ["switchMode", "childLock"]);
980
+ };
981
+ const switchType = utils.getFromLookup(value, stateSwitchType);
982
+ await entity.write("boschSpecific", { switchType: switchType });
983
+ await entity.read("boschSpecific", ["switchType"]);
984
+ await applyDefaultForSwitchModeAndChildLock(entity);
985
+ const leftEndpoint = meta.device.getEndpoint(2);
986
+ await applyDefaultForSwitchModeAndChildLock(leftEndpoint);
987
+ const rightEndpoint = meta.device.getEndpoint(3);
988
+ await applyDefaultForSwitchModeAndChildLock(rightEndpoint);
875
989
  return { state: { switch_type: value } };
876
990
  }
991
+ if (key === "switch_mode") {
992
+ const index = utils.getFromLookup(value, stateSwitchMode);
993
+ await entity.write("boschSpecific", { switchMode: index });
994
+ return { state: { switch_mode: value } };
995
+ }
877
996
  if (key === "child_lock") {
878
997
  const index = utils.getFromLookup(value, stateOffOn);
879
998
  await entity.write("boschSpecific", { childLock: index });
880
999
  return { state: { child_lock: value } };
881
1000
  }
1001
+ if (key === "auto_off_enabled") {
1002
+ const index = utils.getFromLookup(value, stateOffOn);
1003
+ await entity.write("boschSpecific", { autoOffEnabled: index });
1004
+ return { state: { auto_off_enabled: value } };
1005
+ }
1006
+ if (key === "auto_off_time" && typeof value === "number") {
1007
+ await entity.write("boschSpecific", { autoOffTime: value * 60 });
1008
+ return { state: { auto_off_time: value } };
1009
+ }
882
1010
  },
883
1011
  convertGet: async (entity, key, meta) => {
884
1012
  switch (key) {
@@ -895,9 +1023,18 @@ const boschExtend = {
895
1023
  case "switch_type":
896
1024
  await entity.read("boschSpecific", ["switchType"]);
897
1025
  break;
1026
+ case "switch_mode":
1027
+ await entity.read("boschSpecific", ["switchMode"]);
1028
+ break;
898
1029
  case "child_lock":
899
1030
  await entity.read("boschSpecific", ["childLock"]);
900
1031
  break;
1032
+ case "auto_off_enabled":
1033
+ await entity.read("boschSpecific", ["autoOffEnabled"]);
1034
+ break;
1035
+ case "auto_off_time":
1036
+ await entity.read("boschSpecific", ["autoOffTime"]);
1037
+ break;
901
1038
  default:
902
1039
  throw new Error(`Unhandled key boschExtend.bmct.toZigbee.convertGet ${key}`);
903
1040
  }
@@ -1761,7 +1898,17 @@ exports.definitions = [
1761
1898
  model: "BSP-FZ2",
1762
1899
  vendor: "Bosch",
1763
1900
  description: "Plug compact EU",
1764
- extend: [m.onOff(), m.electricityMeter({ voltage: false, current: false })],
1901
+ extend: [
1902
+ m.onOff(),
1903
+ m.electricityMeter({
1904
+ voltage: false,
1905
+ current: false,
1906
+ power: { change: 1 },
1907
+ energy: { change: 1 },
1908
+ }),
1909
+ boschExtend.seMeteringCluster(),
1910
+ boschExtend.resetEnergyReading(),
1911
+ ],
1765
1912
  ota: true,
1766
1913
  whiteLabel: [
1767
1914
  { vendor: "Bosch", model: "BSP-EZ2", description: "Plug compact FR", fingerprint: [{ modelID: "RBSH-SP-ZB-FR" }] },
@@ -1834,48 +1981,130 @@ exports.definitions = [
1834
1981
  description: "Light/shutter control unit II",
1835
1982
  extend: [
1836
1983
  m.deviceEndpoints({ endpoints: { left: 2, right: 3 } }),
1837
- m.electricityMeter({ voltage: false, current: false }),
1984
+ m.electricityMeter({
1985
+ voltage: false,
1986
+ current: false,
1987
+ power: { change: 1 },
1988
+ energy: { change: 1 },
1989
+ }),
1838
1990
  m.deviceAddCustomCluster("boschSpecific", {
1839
1991
  ID: 0xfca0,
1840
1992
  manufacturerCode: zigbee_herdsman_1.Zcl.ManufacturerCode.ROBERT_BOSCH_GMBH,
1841
1993
  attributes: {
1842
1994
  deviceMode: { ID: 0x0000, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
1843
1995
  switchType: { ID: 0x0001, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
1996
+ switchMode: { ID: 0x0031, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
1844
1997
  calibrationOpeningTime: { ID: 0x0002, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
1845
1998
  calibrationClosingTime: { ID: 0x0003, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
1999
+ // 0x0005 isn't used at all when using the Bosch SHC as of 30-06-2025.
2000
+ // As I don't have any shutters, I can't run all calibration steps
2001
+ // successfully. So, keep any comments regarding these
2002
+ // attributes with caution.
1846
2003
  calibrationButtonHoldTime: { ID: 0x0005, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
2004
+ autoOffEnabled: { ID: 0x0006, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
2005
+ autoOffTime: { ID: 0x0007, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
1847
2006
  childLock: { ID: 0x0008, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
2007
+ // 0x000f is only being set when using the automatic calibration.
2008
+ // It's being set to 0 then before sending the calibration
2009
+ // command. Additionally, when changing
2010
+ // the calibrationOpeningTime or calibrationClosingTime in the
2011
+ // Bosch app, it's also being set to 0.
2012
+ // I couldn't find any way to set 0x000f manually in the Bosch app.
1848
2013
  calibrationMotorStartDelay: { ID: 0x000f, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
2014
+ calibrationMotorReverseDirection: { ID: 0x0032, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
1849
2015
  motorState: { ID: 0x0013, type: zigbee_herdsman_1.Zcl.DataType.ENUM8 },
2016
+ // unknownAttributeOne is always being configured as reporting
2017
+ // attribute on endpoint 1 when using the Bosch SHC.
2018
+ // Can't tell what this attribute does (always received
2019
+ // 0x00 as answer on manual lookup).
2020
+ unknownAttributeOne: { ID: 0x0004, type: zigbee_herdsman_1.Zcl.DataType.BITMAP8 },
2021
+ // Attribute is being set to 255 when deactivating the automatic
2022
+ // detection of the motor end position by the Bosch SHC. After
2023
+ // activating the automatic end position detection it's being set
2024
+ // to 0 by the Bosch SHC. Apart from that, there's no way to manually
2025
+ // change the value.
2026
+ calibrationMotorEndPosition: { ID: 0x0021, type: zigbee_herdsman_1.Zcl.DataType.UINT8 },
2027
+ // 0x0033 is used when setting the motor start delay manually
2028
+ // using the Bosch SHC as of 30-06-2025.
2029
+ // If the user wants to automatically detect the delay during
2030
+ // calibration, it's being set to 0 over the Bosch app.
2031
+ calibrationNewMotorStartDelay: { ID: 0x0033, type: zigbee_herdsman_1.Zcl.DataType.UINT16 },
2032
+ // 0x0010 and 0x0011 is being set simultaneously with the same value
2033
+ // when changing the delay for the rotation of the slats on venetian
2034
+ // blinds. Maybe one attribute for each direction?
2035
+ // It's also being configured as reporting attribute when using
2036
+ // venetian blinds.
2037
+ slatRotationDurationOne: { ID: 0x0010, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
2038
+ slatRotationDurationTwo: { ID: 0x0011, type: zigbee_herdsman_1.Zcl.DataType.UINT32 },
2039
+ // 0x002a is only being used when doing an automatic calibration
2040
+ // with the Bosch specific startAutomaticMotorCalibration command.
2041
+ // It's being set to true before starting the calibration process.
2042
+ // This happens regardless of the shutter type. I didn't capture
2043
+ // any packages where this attribute is being actively set to false.
2044
+ // Maybe this activates some "full calibration" flag which is being
2045
+ // set to false by the device itself afterward?
2046
+ unknownAttributeTwo: { ID: 0x002a, type: zigbee_herdsman_1.Zcl.DataType.BOOLEAN },
2047
+ },
2048
+ commands: {
2049
+ // Command being sent by the Bosch SHC when starting an
2050
+ // automatic shutter calibration.
2051
+ startAutomaticMotorCalibration: { ID: 0x00, parameters: [] },
1850
2052
  },
1851
- commands: {},
1852
2053
  commandsResponse: {},
1853
2054
  }),
1854
2055
  boschExtend.bmct(),
2056
+ boschExtend.seMeteringCluster(),
2057
+ boschExtend.resetEnergyReading(),
1855
2058
  ],
1856
2059
  ota: true,
1857
2060
  configure: async (device, coordinatorEndpoint) => {
2061
+ const lightConfiguration = async () => {
2062
+ const endpoint1 = device.getEndpoint(1);
2063
+ await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify"]);
2064
+ await endpoint1.read("boschSpecific", ["switchType"]);
2065
+ const endpoint2 = device.getEndpoint(2);
2066
+ await reporting.bind(endpoint2, coordinatorEndpoint, ["genIdentify", "genOnOff", "boschSpecific"]);
2067
+ await reporting.onOff(endpoint2);
2068
+ await endpoint2.read("genOnOff", ["onOff", "startUpOnOff"]);
2069
+ await endpoint2.read("boschSpecific", [
2070
+ "switchMode",
2071
+ "childLock",
2072
+ "autoOffEnabled",
2073
+ "autoOffTime",
2074
+ ]);
2075
+ const endpoint3 = device.getEndpoint(3);
2076
+ await reporting.bind(endpoint3, coordinatorEndpoint, ["genIdentify", "genOnOff", "boschSpecific"]);
2077
+ await reporting.onOff(endpoint3);
2078
+ await endpoint3.read("genOnOff", ["onOff", "startUpOnOff"]);
2079
+ await endpoint3.read("boschSpecific", [
2080
+ "switchMode",
2081
+ "childLock",
2082
+ "autoOffEnabled",
2083
+ "autoOffTime",
2084
+ ]);
2085
+ };
2086
+ const shutterConfiguration = async () => {
2087
+ const endpoint1 = device.getEndpoint(1);
2088
+ await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify", "closuresWindowCovering", "boschSpecific"]);
2089
+ await reporting.currentPositionLiftPercentage(endpoint1);
2090
+ await endpoint1.read("closuresWindowCovering", ["currentPositionLiftPercentage"]);
2091
+ const payloadMotorState = (0, reporting_1.payload)("motorState", 0, constants_1.repInterval.MAX, 0);
2092
+ await endpoint1.configureReporting("boschSpecific", payloadMotorState);
2093
+ await endpoint1.read("boschSpecific", [
2094
+ "switchType",
2095
+ "switchMode",
2096
+ "motorState",
2097
+ "calibrationOpeningTime",
2098
+ "calibrationClosingTime",
2099
+ "calibrationButtonHoldTime",
2100
+ "calibrationMotorStartDelay",
2101
+ "childLock",
2102
+ ]);
2103
+ };
1858
2104
  const endpoint1 = device.getEndpoint(1);
1859
- await reporting.bind(endpoint1, coordinatorEndpoint, ["genIdentify", "closuresWindowCovering", "boschSpecific"]);
1860
- await reporting.currentPositionLiftPercentage(endpoint1);
1861
- await endpoint1.read("boschSpecific", [
1862
- "deviceMode",
1863
- "switchType",
1864
- "motorState",
1865
- "childLock",
1866
- "calibrationOpeningTime",
1867
- "calibrationClosingTime",
1868
- "calibrationButtonHoldTime",
1869
- "calibrationMotorStartDelay",
1870
- ]);
1871
- const endpoint2 = device.getEndpoint(2);
1872
- await endpoint2.read("boschSpecific", ["childLock"]);
1873
- await reporting.bind(endpoint2, coordinatorEndpoint, ["genIdentify", "genOnOff"]);
1874
- await reporting.onOff(endpoint2);
1875
- const endpoint3 = device.getEndpoint(3);
1876
- await endpoint3.read("boschSpecific", ["childLock"]);
1877
- await reporting.bind(endpoint3, coordinatorEndpoint, ["genIdentify", "genOnOff"]);
1878
- await reporting.onOff(endpoint3);
2105
+ await endpoint1.read("boschSpecific", ["deviceMode"]);
2106
+ await lightConfiguration();
2107
+ await shutterConfiguration();
1879
2108
  },
1880
2109
  exposes: (device, options) => {
1881
2110
  const stateDeviceMode = {
@@ -1893,63 +2122,159 @@ exports.definitions = [
1893
2122
  button_key_change: 0x02,
1894
2123
  rocker_switch: 0x03,
1895
2124
  rocker_switch_key_change: 0x04,
2125
+ none: 0x00,
2126
+ };
2127
+ const stateSwitchMode = {
2128
+ coupled: 0x00,
2129
+ decoupled: 0x01,
2130
+ only_short_press_decoupled: 0x02,
2131
+ only_long_press_decoupled: 0x03,
2132
+ };
2133
+ const commonExposes = (switchType) => {
2134
+ const exposeList = [];
2135
+ exposeList.push(e.enum("switch_type", ea.ALL, Object.keys(stateSwitchType)).withDescription("Module controlled by a rocker switch or a button"));
2136
+ if (switchType !== "none") {
2137
+ let supportedActionTypes;
2138
+ switch (switchType) {
2139
+ case "button":
2140
+ case "button_key_change":
2141
+ supportedActionTypes = [
2142
+ "press_released_left",
2143
+ "press_released_right",
2144
+ "hold_left",
2145
+ "hold_right",
2146
+ "hold_released_left",
2147
+ "hold_released_right",
2148
+ ];
2149
+ break;
2150
+ case "rocker_switch":
2151
+ case "rocker_switch_key_change":
2152
+ supportedActionTypes = ["opened_left", "opened_right", "closed_left", "closed_right"];
2153
+ break;
2154
+ }
2155
+ exposeList.push(e.action(supportedActionTypes), e.action_duration());
2156
+ }
2157
+ return exposeList;
2158
+ };
2159
+ const lightExposes = (endpoint, switchType) => {
2160
+ const exposeList = [];
2161
+ exposeList.push(e.switch().withEndpoint(endpoint), e.power_on_behavior().withEndpoint(endpoint), e
2162
+ .binary("auto_off_enabled", ea.ALL, "ON", "OFF")
2163
+ .withEndpoint(endpoint)
2164
+ .withDescription("Enable/Disable the automatic turn-off feature"), e
2165
+ .numeric("auto_off_time", ea.ALL)
2166
+ .withValueMin(0)
2167
+ .withValueMax(720)
2168
+ .withValueStep(1)
2169
+ .withUnit("min")
2170
+ .withDescription("Turn off the output after the specified amount of time. Only in action when the automatic turn-off is enabled.")
2171
+ .withEndpoint(endpoint));
2172
+ if (switchType !== "none") {
2173
+ let supportedSwitchModes;
2174
+ switch (switchType) {
2175
+ case "button":
2176
+ case "button_key_change":
2177
+ supportedSwitchModes = Object.keys(stateSwitchMode);
2178
+ break;
2179
+ case "rocker_switch":
2180
+ case "rocker_switch_key_change":
2181
+ supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled" || switchMode === "decoupled");
2182
+ break;
2183
+ }
2184
+ exposeList.push(e
2185
+ .enum("switch_mode", ea.ALL, supportedSwitchModes)
2186
+ .withEndpoint(endpoint)
2187
+ .withDescription("Decouple the switch from the corresponding output to use it for other purposes. Please keep in mind that the available options depend on the used switch type."), e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint(endpoint).withDescription("Enable/Disable child lock"));
2188
+ }
2189
+ return exposeList;
1896
2190
  };
1897
- const commonExposes = [
1898
- e.enum("switch_type", ea.ALL, Object.keys(stateSwitchType)).withDescription("Module controlled by a rocker switch or a button"),
1899
- ];
1900
- const lightExposes = [
1901
- e.switch().withEndpoint("left"),
1902
- e.switch().withEndpoint("right"),
1903
- e.power_on_behavior().withEndpoint("left"),
1904
- e.power_on_behavior().withEndpoint("right"),
1905
- e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint("left").withDescription("Enable/Disable child lock"),
1906
- e.binary("child_lock", ea.ALL, "ON", "OFF").withEndpoint("right").withDescription("Enable/Disable child lock"),
1907
- ];
1908
- const coverExposes = [
1909
- e.cover_position(),
1910
- e.enum("motor_state", ea.STATE, Object.keys(stateMotor)).withDescription("Current shutter motor state"),
1911
- e.binary("child_lock", ea.ALL, "ON", "OFF").withDescription("Enable/Disable child lock"),
1912
- e
2191
+ const coverExposes = (switchType) => {
2192
+ const exposeList = [];
2193
+ exposeList.push(e.cover_position(), e.enum("motor_state", ea.STATE, Object.keys(stateMotor)).withDescription("Current shutter motor state"), e
1913
2194
  .numeric("calibration_closing_time", ea.ALL)
1914
2195
  .withUnit("s")
1915
2196
  .withDescription("Calibrate shutter closing time")
1916
2197
  .withValueMin(1)
1917
2198
  .withValueMax(90)
1918
- .withValueStep(0.1),
1919
- e
2199
+ .withValueStep(0.1), e
1920
2200
  .numeric("calibration_opening_time", ea.ALL)
1921
2201
  .withUnit("s")
1922
2202
  .withDescription("Calibrate shutter opening time")
1923
2203
  .withValueMin(1)
1924
2204
  .withValueMax(90)
1925
- .withValueStep(0.1),
1926
- e
2205
+ .withValueStep(0.1), e
1927
2206
  .numeric("calibration_button_hold_time", ea.ALL)
1928
2207
  .withUnit("s")
1929
2208
  .withDescription("Time to hold for long press")
1930
2209
  .withValueMin(0.1)
1931
2210
  .withValueMax(2)
1932
- .withValueStep(0.1),
1933
- e
2211
+ .withValueStep(0.1), e
1934
2212
  .numeric("calibration_motor_start_delay", ea.ALL)
1935
2213
  .withUnit("s")
1936
2214
  .withDescription("Delay between command and motor start")
1937
2215
  .withValueMin(0)
1938
2216
  .withValueMax(20)
1939
- .withValueStep(0.1),
1940
- ];
2217
+ .withValueStep(0.1));
2218
+ if (switchType !== "none") {
2219
+ let supportedSwitchModes;
2220
+ switch (switchType) {
2221
+ case "button":
2222
+ case "button_key_change":
2223
+ supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled" || switchMode === "only_long_press_decoupled");
2224
+ break;
2225
+ case "rocker_switch":
2226
+ case "rocker_switch_key_change":
2227
+ supportedSwitchModes = Object.keys(stateSwitchMode).filter((switchMode) => switchMode === "coupled");
2228
+ break;
2229
+ }
2230
+ exposeList.push(e
2231
+ .enum("switch_mode", ea.ALL, supportedSwitchModes)
2232
+ .withDescription("Decouple the switch from the corresponding output to use it for other purposes. Please keep in mind that the available options depend on the used switch type."), e.binary("child_lock", ea.ALL, "ON", "OFF").withDescription("Enable/Disable child lock"));
2233
+ }
2234
+ return exposeList;
2235
+ };
1941
2236
  if (!utils.isDummyDevice(device)) {
1942
2237
  const deviceModeKey = device.getEndpoint(1).getClusterAttributeValue("boschSpecific", "deviceMode");
1943
2238
  const deviceMode = Object.keys(stateDeviceMode).find((key) => stateDeviceMode[key] === deviceModeKey);
2239
+ const switchTypeKey = device.getEndpoint(1).getClusterAttributeValue("boschSpecific", "switchType");
2240
+ const switchType = Object.keys(stateSwitchType).find((key) => stateSwitchType[key] === switchTypeKey);
1944
2241
  if (deviceMode === "light") {
1945
- return [...commonExposes, ...lightExposes];
2242
+ return [...commonExposes(switchType), ...lightExposes("left", switchType), ...lightExposes("right", switchType)];
1946
2243
  }
1947
2244
  if (deviceMode === "shutter") {
1948
- return [...commonExposes, ...coverExposes];
2245
+ return [...commonExposes(switchType), ...coverExposes(switchType)];
1949
2246
  }
1950
2247
  }
1951
2248
  return [e.enum("device_mode", ea.ALL, Object.keys(stateDeviceMode)).withDescription("Device mode")];
1952
2249
  },
2250
+ onEvent: (event) => {
2251
+ if (event.type !== "deviceInterview") {
2252
+ return;
2253
+ }
2254
+ // During interview, the Bosch BMCT-SLZ is requesting
2255
+ // the zclVersion attribute from the coordinator. As
2256
+ // Z2M doesn't know the zclVersion of the device yet,
2257
+ // the request is left unanswered. This makes the device
2258
+ // believe it dropped out of network every 10 minutes which
2259
+ // not only generates unnecessary network congestion, but
2260
+ // makes the LED on the device blink during that sequence
2261
+ // as well. To prevent that, we have to manually answer
2262
+ // the zclVersion request at the earliest possible stage
2263
+ // and mimic the answer from the Bosch SHC II.
2264
+ event.data.device.customReadResponse = (frame, endpoint) => {
2265
+ const isZclVersionRequest = frame.isCluster("genBasic") && frame.payload.find((i) => i.attrId === 0);
2266
+ if (!isZclVersionRequest) {
2267
+ return false;
2268
+ }
2269
+ const payload = {
2270
+ zclVersion: 1,
2271
+ };
2272
+ endpoint.readResponse(frame.cluster.name, frame.header.transactionSequenceNumber, payload).catch((e) => {
2273
+ logger_1.logger.warning(`Custom zclVersion response failed for '${event.data.device.ieeeAddr}': ${e}`, NS);
2274
+ });
2275
+ return true;
2276
+ };
2277
+ },
1953
2278
  },
1954
2279
  {
1955
2280
  zigbeeModel: ["RBSH-US4BTN-ZB-EU"],