matterbridge-zigbee2mqtt 2.4.4 → 2.4.5-dev.2

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
@@ -4,6 +4,20 @@ If you like this project and find it useful, please consider giving it a star on
4
4
 
5
5
  All notable changes to this project will be documented in this file.
6
6
 
7
+ ## [2.4.5] - 2025-02-04
8
+
9
+ ### Changed
10
+
11
+ - [package]: Updated dependencies.
12
+
13
+ ### Fixed
14
+
15
+ - [endpoint]: Fix thermostat bug.
16
+
17
+ <a href="https://www.buymeacoffee.com/luligugithub">
18
+ <img src="./yellow-button.png" alt="Buy me a coffee" width="120">
19
+ </a>
20
+
7
21
  ## [2.4.4] - 2025-02-02
8
22
 
9
23
  ### Changed
package/dist/entity.js CHANGED
@@ -5,12 +5,8 @@ import * as color from 'matterbridge/utils';
5
5
  import { SwitchesTag, NumberTag } from 'matterbridge/matter';
6
6
  import { getClusterNameById, ClusterId } from 'matterbridge/matter/types';
7
7
  import { ElectricalEnergyMeasurement, ElectricalPowerMeasurement, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, BridgedDeviceBasicInformation, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, WindowCovering, DoorLock, ThermostatCluster, Thermostat, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, } from 'matterbridge/matter/clusters';
8
- import { PowerSourceBehavior } from 'matterbridge/matter/behaviors';
9
8
  import EventEmitter from 'events';
10
9
  import { hostname } from 'os';
11
- function require(type, options) {
12
- return { clusterId: ClusterId(1), options };
13
- }
14
10
  export class ZigbeeEntity extends EventEmitter {
15
11
  log;
16
12
  serial = '';
@@ -139,6 +135,12 @@ export class ZigbeeEntity extends EventEmitter {
139
135
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position);
140
136
  }
141
137
  }
138
+ if (key === 'current_heating_setpoint' && 'system_mode' in payload && payload['system_mode'] === 'heat' && isValidNumber(value)) {
139
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, Thermostat.Cluster.id, 'occupiedHeatingSetpoint', value * 100);
140
+ }
141
+ if (key === 'current_heating_setpoint' && 'system_mode' in payload && payload['system_mode'] === 'cool' && isValidNumber(value)) {
142
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, Thermostat.Cluster.id, 'occupiedCoolingSetpoint', value * 100);
143
+ }
142
144
  if (key === 'color_temp' && 'color_mode' in payload && payload['color_mode'] === 'color_temp') {
143
145
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds);
144
146
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorTemperatureMireds', Math.max(147, Math.min(500, typeof value === 'number' ? value : 0)));
@@ -592,10 +594,11 @@ export const z2ms = [
592
594
  { type: 'cover', name: 'position', property: 'position', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'currentPositionLiftPercent100ths' },
593
595
  { type: 'lock', name: 'state', property: 'state', deviceType: doorLockDevice, cluster: DoorLock.Cluster.id, attribute: 'lockState', converter: (value) => { return value === 'LOCK' ? DoorLock.LockState.Locked : DoorLock.LockState.Unlocked; } },
594
596
  { type: 'climate', name: 'local_temperature', property: 'local_temperature', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'localTemperature', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
595
- { type: 'climate', name: 'current_heating_setpoint', property: 'current_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
596
- { type: 'climate', name: 'current_cooling_setpoint', property: 'current_cooling_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
597
+ { type: 'climate', name: 'current_heating_setpoint', property: 'current_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint' },
597
598
  { type: 'climate', name: 'occupied_heating_setpoint', property: 'occupied_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
598
599
  { type: 'climate', name: 'occupied_cooling_setpoint', property: 'occupied_cooling_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
600
+ { type: 'climate', name: 'unoccupied_heating_setpoint', property: 'unoccupied_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
601
+ { type: 'climate', name: 'unoccupied_cooling_setpoint', property: 'unoccupied_cooling_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
599
602
  { type: 'climate', name: 'running_state', property: 'running_state', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'thermostatRunningMode', valueLookup: ['idle', '', '', 'cool', 'heat'] },
600
603
  { type: 'climate', name: 'system_mode', property: 'system_mode', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'systemMode', valueLookup: ['off', 'auto', '', 'cool', 'heat'] },
601
604
  { type: '', name: 'min_temperature_limit', property: 'min_temperature_limit', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'minHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
@@ -890,45 +893,6 @@ export class ZigbeeDevice extends ZigbeeEntity {
890
893
  zigbeeDevice.addBridgedDeviceBasicInformation();
891
894
  zigbeeDevice.addPowerSource();
892
895
  mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(PowerSource.Cluster.id), 1);
893
- const options = require(PowerSourceBehavior.with(PowerSource.Feature.Wired), {
894
- wiredCurrentType: PowerSource.WiredCurrentType.Ac,
895
- description: 'AC Power',
896
- status: PowerSource.PowerSourceStatus.Active,
897
- order: 0,
898
- endpointList: [],
899
- });
900
- mainEndpoint.clusterServersOptions.push(options);
901
- if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
902
- zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
903
- if (!names.includes('color_hs') && !names.includes('color_xy')) {
904
- zigbeeDevice.bridgedDevice.createCtColorControlClusterServer();
905
- mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(ColorControl.Cluster.id), 1);
906
- }
907
- }
908
- if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
909
- const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('current_heating_setpoint');
910
- const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('current_cooling_setpoint');
911
- const minHeating = heat && heat.value_min !== undefined && !isNaN(heat.value_min) ? heat.value_min : 0;
912
- const maxHeating = heat && heat.value_max !== undefined && !isNaN(heat.value_max) ? heat.value_max : 50;
913
- const minCooling = cool && cool.value_min !== undefined && !isNaN(cool.value_min) ? cool.value_min : 0;
914
- const maxCooling = cool && cool.value_max !== undefined && !isNaN(cool.value_max) ? cool.value_max : 50;
915
- zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} Thermostat cluster with heating ${CYAN}${heat ? 'supported' : 'not supported'}${db} cooling ${CYAN}${cool ? 'supported' : 'not supported'}${db} ` +
916
- `minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
917
- if (heat && !cool) {
918
- zigbeeDevice.propertyMap.delete('running_state');
919
- zigbeeDevice.bridgedDevice.createDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating);
920
- mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
921
- }
922
- else if (!heat && cool) {
923
- zigbeeDevice.propertyMap.delete('running_state');
924
- zigbeeDevice.bridgedDevice.createDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling);
925
- mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
926
- }
927
- else if (heat && cool) {
928
- zigbeeDevice.bridgedDevice.createDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling);
929
- mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
930
- }
931
- }
932
896
  for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
933
897
  const deviceClusterServersIdMap = new Map();
934
898
  device.clusterServersIds.forEach((clusterServer) => {
@@ -955,6 +919,40 @@ export class ZigbeeDevice extends ZigbeeEntity {
955
919
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
956
920
  `${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
957
921
  }
922
+ if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
923
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
924
+ if (!names.includes('color_hs') && !names.includes('color_xy')) {
925
+ zigbeeDevice.bridgedDevice.createCtColorControlClusterServer();
926
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(ColorControl.Cluster.id), 1);
927
+ }
928
+ }
929
+ if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
930
+ const system_mode = zigbeeDevice.propertyMap.get('system_mode');
931
+ const system_mode_values = system_mode?.values;
932
+ const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('unoccupied_heating_setpoint');
933
+ const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('unoccupied_cooling_setpoint');
934
+ const minHeating = heat && heat.value_min !== undefined && !isNaN(heat.value_min) ? heat.value_min : 0;
935
+ const maxHeating = heat && heat.value_max !== undefined && !isNaN(heat.value_max) ? heat.value_max : 50;
936
+ const minCooling = cool && cool.value_min !== undefined && !isNaN(cool.value_min) ? cool.value_min : 0;
937
+ const maxCooling = cool && cool.value_max !== undefined && !isNaN(cool.value_max) ? cool.value_max : 50;
938
+ zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} Thermostat cluster with heating ${CYAN}${heat ? 'supported' : '(un)occupied not supported'}${db} cooling ${CYAN}${cool ? 'supported' : '(un)occupied not supported'}${db} ` +
939
+ `system_mode ${CYAN}${system_mode_values ?? 'not supported'}${db} ` +
940
+ `minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
941
+ if ((heat && !cool) || (!system_mode_values?.includes('auto') && system_mode_values?.includes('heat'))) {
942
+ zigbeeDevice.propertyMap.delete('running_state');
943
+ zigbeeDevice.bridgedDevice.createDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating);
944
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
945
+ }
946
+ else if ((!heat && cool) || (!system_mode_values?.includes('auto') && system_mode_values?.includes('cool'))) {
947
+ zigbeeDevice.propertyMap.delete('running_state');
948
+ zigbeeDevice.bridgedDevice.createDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling);
949
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
950
+ }
951
+ else {
952
+ zigbeeDevice.bridgedDevice.createDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling);
953
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
954
+ }
955
+ }
958
956
  zigbeeDevice.bridgedDevice.addClusterServers(mainEndpoint.clusterServersIds);
959
957
  zigbeeDevice.bridgedDevice.addRequiredClusterServers();
960
958
  if (zigbeeDevice.composedType !== '')
@@ -1191,9 +1189,9 @@ export class ZigbeeDevice extends ZigbeeEntity {
1191
1189
  if (request.mode === Thermostat.SetpointRaiseLowerMode.Cool || request.mode === Thermostat.SetpointRaiseLowerMode.Both) {
1192
1190
  const t = zigbeeDevice.bridgedDevice?.getAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', zigbeeDevice.log);
1193
1191
  const setpoint = Math.round(t / 100 + request.amount / 10);
1194
- if (zigbeeDevice.propertyMap.has('current_cooling_setpoint')) {
1195
- zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: setpoint });
1196
- zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_cooling_setpoint: setpoint }));
1192
+ if (zigbeeDevice.propertyMap.has('current_heating_setpoint')) {
1193
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1194
+ zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_heating_setpoint: setpoint }));
1197
1195
  }
1198
1196
  else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint')) {
1199
1197
  zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: setpoint });
@@ -1225,8 +1223,8 @@ export class ZigbeeDevice extends ZigbeeEntity {
1225
1223
  if (zigbeeDevice.bridgedDevice.hasAttributeServer(ThermostatCluster.id, 'occupiedCoolingSetpoint'))
1226
1224
  zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
1227
1225
  zigbeeDevice.log.debug(`Subscribe occupiedCoolingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1228
- if (zigbeeDevice.propertyMap.has('current_cooling_setpoint'))
1229
- zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: Math.round(value / 100) });
1226
+ if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1227
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
1230
1228
  else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint'))
1231
1229
  zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: Math.round(value / 100) });
1232
1230
  zigbeeDevice.noUpdate = true;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.4.4",
3
+ "version": "2.4.5-dev.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-zigbee2mqtt",
9
- "version": "2.4.4",
9
+ "version": "2.4.5-dev.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "moment": "2.30.1",
@@ -35,9 +35,9 @@
35
35
  }
36
36
  },
37
37
  "node_modules/@types/node": {
38
- "version": "22.13.0",
39
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz",
40
- "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==",
38
+ "version": "22.13.1",
39
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
40
+ "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
41
41
  "license": "MIT",
42
42
  "dependencies": {
43
43
  "undici-types": "~6.20.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-zigbee2mqtt",
3
- "version": "2.4.4",
3
+ "version": "2.4.5-dev.2",
4
4
  "description": "Matterbridge zigbee2mqtt plugin",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",