From e4f7e4b0de549aabea238ee17acbea337f9b7fd9 Mon Sep 17 00:00:00 2001 From: Luligu Date: Fri, 6 Dec 2024 21:39:43 +0100 Subject: [PATCH 01/49] Add debug to childEndpoints --- package.json | 2 +- src/platform.ts | 88 ++++++++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index b80173f..5e39c21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.0.11", + "version": "1.0.12-dev.1", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", diff --git a/src/platform.ts b/src/platform.ts index 184f46b..39c351e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -263,37 +263,37 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (bthomeDevice.model === 'Shelly BLU DoorWindow') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Contact', [DeviceTypes.CONTACT_SENSOR]); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR]); + mbDevice.addChildDeviceTypeWithClusterServer('Contact', [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Motion') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Motion', [DeviceTypes.OCCUPANCY_SENSOR]); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR]); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH]); + mbDevice.addChildDeviceTypeWithClusterServer('Motion', [DeviceTypes.OCCUPANCY_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Button1') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.createDefaultSwitchClusterServer(); } else if (bthomeDevice.model === 'Shelly BLU HT') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [DeviceTypes.TEMPERATURE_SENSOR]); - mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [DeviceTypes.HUMIDITY_SENSOR]); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH]); + mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU RC Button 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Input'); - mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); + mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Wall Switch 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Input'); - mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); - mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id]); + mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Trv') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok, 3000, 'Type AA', 2); mbDevice.createDefaultIdentifyClusterServer(); @@ -497,7 +497,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); // Set the powerSource cluster - const childPowerSource = mbDevice.addChildDeviceType('PowerSource', [powerSource]); + const childPowerSource = mbDevice.addChildDeviceType('PowerSource', [powerSource], undefined, config.debug as boolean); const batteryComponent = device.getComponent('battery'); const devicepowerComponent = device.getComponent('devicepower:0'); if (batteryComponent) { @@ -617,7 +617,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; clusterIds.push(ColorControl.Cluster.id); } - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds, undefined, config.debug as boolean); if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) mbDevice.configureColorControlCluster(true, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) @@ -687,7 +687,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (config.switchList && (config.switchList as string[]).includes(device.id)) deviceType = onOffSwitch; if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], [OnOff.Cluster.id]); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], [OnOff.Cluster.id], undefined, config.debug as boolean); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, switchComponent); @@ -722,7 +722,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Cover' || component.name === 'Roller') { const coverComponent = device.getComponent(key); if (coverComponent) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.WINDOW_COVERING], [WindowCovering.Cluster.id]); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.WINDOW_COVERING], [WindowCovering.Cluster.id], undefined, config.debug as boolean); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, coverComponent); @@ -751,7 +751,13 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const pmComponent = device.getComponent(key); if (pmComponent && config.exposePowerMeter === 'matter13') { // Add the Matter 1.3 electricalSensor device type with the ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [electricalSensor], [ElectricalPowerMeasurement.Cluster.id, ElectricalEnergyMeasurement.Cluster.id]); + const child = mbDevice.addChildDeviceTypeWithClusterServer( + key, + [electricalSensor], + [ElectricalPowerMeasurement.Cluster.id, ElectricalEnergyMeasurement.Cluster.id], + undefined, + config.debug as boolean, + ); device.log.debug( `Added ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${child.name}${db} component ${hk}${component.name}:${component.id}${db}`, ); @@ -776,7 +782,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); // Set the state attribute child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(state)); child.addRequiredClusterServers(child); @@ -792,7 +798,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -807,7 +813,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultLatchingSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -823,7 +829,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Gen 1 devices const event = inputComponent.getValue('event') as boolean; if (isValidString(event)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -841,7 +847,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { (config.exposeInputEvent !== 'disabled' || (config.inputEventList && (config.inputEventList as string[]).includes(device.id))) ) { // Gen 2/3 devices with Input type=button - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); device.log.info(`Add device event handler for device ${idn}${device.id}${rs} component ${hk}${component.id}${db} type Button`); @@ -862,7 +868,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Sensor' && config.exposeSensor !== 'disabled') { const sensorComponent = device.getComponent(key); if (sensorComponent?.hasProperty('contact_open') && config.exposeContact !== 'disabled') { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(sensorComponent.getValue('contact_open') === false)); child.addRequiredClusterServers(child); // Add event handler @@ -871,7 +877,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } if (sensorComponent?.hasProperty('motion') && config.exposeMotion !== 'disabled') { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.OCCUPANCY_SENSOR]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.OCCUPANCY_SENSOR], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultOccupancySensingClusterServer(sensorComponent.getValue('motion') === true)); child.addRequiredClusterServers(child); // Add event handler @@ -882,7 +888,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Vibration' && config.exposeVibration !== 'disabled') { const vibrationComponent = device.getComponent(key); if (vibrationComponent?.hasProperty('vibration') && isValidBoolean(vibrationComponent.getValue('vibration'))) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH]); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -893,7 +899,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Temperature' && config.exposeTemperature !== 'disabled') { const tempComponent = device.getComponent(key); if (tempComponent?.hasProperty('value') && isValidNumber(tempComponent.getValue('value'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('value') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); // Add event handler @@ -901,7 +907,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { shellyUpdateHandler(this, mbDevice, device, component, property, value); }); } else if (tempComponent?.hasProperty('tC') && isValidNumber(tempComponent.getValue('tC'), -100, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('tC') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); // Add event handler @@ -912,7 +918,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Humidity' && config.exposeHumidity !== 'disabled') { const humidityComponent = device.getComponent(key); if (humidityComponent?.hasProperty('value') && isValidNumber(humidityComponent.getValue('value'), 0, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('value') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); // Add event handler @@ -921,7 +927,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } if (humidityComponent?.hasProperty('rh') && isValidNumber(humidityComponent.getValue('rh'), 0, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('rh') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); // Add event handler @@ -932,7 +938,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Illuminance' && config.exposeIlluminance !== 'disabled') { const illuminanceComponent = device.getComponent(key); if (illuminanceComponent?.hasProperty('lux') && isValidNumber(illuminanceComponent.getValue('lux'), 0, 10000)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(illuminanceComponent.getValue('lux') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); // Add event handler @@ -954,7 +960,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { let child: MatterbridgeDevice; if (thermostatComponent.getValue('type') === 'heating') { - child = mbDevice.addChildDeviceType(key, [thermostatDevice]); + child = mbDevice.addChildDeviceType(key, [thermostatDevice], undefined, config.debug as boolean); child.createDefaultIdentifyClusterServer(); child.addClusterServer( child.getDefaultHeatingThermostatClusterServer(thermostatComponent.getValue('current_C') as number, thermostatComponent.getValue('target_C') as number, 5, 35), @@ -975,7 +981,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.log, ); } else if (thermostatComponent.getValue('type') === 'cooling') { - child = mbDevice.addChildDeviceType(key, [thermostatDevice]); + child = mbDevice.addChildDeviceType(key, [thermostatDevice], undefined, config.debug as boolean); child.createDefaultIdentifyClusterServer(); child.addClusterServer( child.getDefaultCoolingThermostatClusterServer(thermostatComponent.getValue('current_C') as number, thermostatComponent.getValue('target_C') as number, 5, 35), @@ -1033,7 +1039,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Flood' && config.exposeFlood !== 'disabled') { const floodComponent = device.getComponent(key); if (floodComponent?.hasProperty('flood') && isValidBoolean(floodComponent.getValue('flood'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!(floodComponent.getValue('flood') as boolean))); // Add event handler floodComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1043,7 +1049,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Gas' && config.exposeGas !== 'disabled') { const gasComponent = device.getComponent(key); if (gasComponent?.hasProperty('sensor_state') && isValidString(gasComponent.getValue('alarm_state'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(gasComponent.getValue('alarm_state') === 'none')); // Add event handler gasComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1053,7 +1059,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Smoke' && config.exposeSmoke !== 'disabled') { const smokeComponent = device.getComponent(key); if (smokeComponent?.hasProperty('alarm') && isValidBoolean(smokeComponent.getValue('alarm'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!smokeComponent.getValue('alarm') as boolean)); // Add event handler smokeComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1063,7 +1069,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Lux' && config.exposeLux !== 'disabled') { const luxComponent = device.getComponent(key); if (luxComponent?.hasProperty('value') && isValidNumber(luxComponent.getValue('value'), 0)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], []); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(luxComponent.getValue('value') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); // Add event handler @@ -1074,7 +1080,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Blugw' && config.exposeBlugw !== 'disabled') { const blugwComponent = device.getComponent(key); if (blugwComponent?.hasProperty('sys_led_enable') && isValidBoolean(blugwComponent.getValue('sys_led_enable'))) { - const child = mbDevice.addChildDeviceType(key, [modeSelect]); + const child = mbDevice.addChildDeviceType(key, [modeSelect], undefined, config.debug as boolean); child.addClusterServer( mbDevice.getDefaultModeSelectClusterServer( 'System LED', From bb1cf26676a77b26cab3cb7aae0b3aea43b2bace Mon Sep 17 00:00:00 2001 From: Luligu Date: Fri, 6 Dec 2024 21:55:46 +0100 Subject: [PATCH 02/49] Remove configureColorControlCluster --- src/platform.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 39c351e..ee41f80 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -615,14 +615,22 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { lightComponent.hasProperty('rgb') ) { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; - clusterIds.push(ColorControl.Cluster.id); + // clusterIds.push(ColorControl.Cluster.id); } - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds, undefined, config.debug as boolean); - if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) - mbDevice.configureColorControlCluster(true, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); - else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) - mbDevice.configureColorControlCluster(false, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); - else mbDevice.configureColorControlCluster(true, false, false, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child); + // const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds, undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + child.addClusterServerFromList(child, clusterIds); + if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) { + // mbDevice.configureColorControlCluster(true, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); + child.createDefaultColorControlClusterServer(); + } else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) { + // mbDevice.configureColorControlCluster(false, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); + child.createCtColorControlClusterServer(); + } else if (deviceType === DeviceTypes.COLOR_TEMPERATURE_LIGHT) { + // mbDevice.configureColorControlCluster(true, false, false, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child); + child.createHsColorControlClusterServer(); + } + child.addRequiredClusterServers(child); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, lightComponent); From 9003ba361ac103899e1db73a58ef0cfa65701d5d Mon Sep 17 00:00:00 2001 From: Luligu Date: Fri, 6 Dec 2024 23:55:43 +0100 Subject: [PATCH 03/49] Add name to child endpoint --- package-lock.json | 28 ++++++++++++++-------------- src/platform.ts | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e582af..a676986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.0.11", + "version": "1.0.12-dev.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.0.11", + "version": "1.0.12-dev.1", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", @@ -533,9 +533,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", - "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "license": "MIT", "dependencies": { @@ -2166,9 +2166,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001686", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", - "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", "dev": true, "funding": [ { @@ -2384,9 +2384,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2481,9 +2481,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.68", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz", - "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==", + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", "dev": true, "license": "ISC" }, diff --git a/src/platform.ts b/src/platform.ts index ee41f80..55b9235 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -482,6 +482,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Create a new Matterbridge device const mbDevice = await this.createMutableDevice(bridgedNode, { uniqueStorageKey: device.name }, config.debug as boolean); + mbDevice.log.logName = device.name; mbDevice.createDefaultBridgedDeviceBasicInformationClusterServer( device.name, device.id + (this.postfix ? '-' + this.postfix : ''), @@ -619,6 +620,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } // const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds, undefined, config.debug as boolean); const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServerFromList(child, clusterIds); if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) { // mbDevice.configureColorControlCluster(true, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); @@ -696,6 +698,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], [OnOff.Cluster.id], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, switchComponent); @@ -731,6 +734,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const coverComponent = device.getComponent(key); if (coverComponent) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.WINDOW_COVERING], [WindowCovering.Cluster.id], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, coverComponent); @@ -766,6 +770,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { undefined, config.debug as boolean, ); + child.log.logName = `${device.name} ${key}`; device.log.debug( `Added ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${child.name}${db} component ${hk}${component.name}:${component.id}${db}`, ); @@ -791,6 +796,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; // Set the state attribute child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(state)); child.addRequiredClusterServers(child); @@ -807,6 +813,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -822,6 +829,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultLatchingSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -838,6 +846,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const event = inputComponent.getValue('event') as boolean; if (isValidString(event)) { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -856,6 +865,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { // Gen 2/3 devices with Input type=button const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); device.log.info(`Add device event handler for device ${idn}${device.id}${rs} component ${hk}${component.id}${db} type Button`); @@ -877,6 +887,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const sensorComponent = device.getComponent(key); if (sensorComponent?.hasProperty('contact_open') && config.exposeContact !== 'disabled') { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(sensorComponent.getValue('contact_open') === false)); child.addRequiredClusterServers(child); // Add event handler @@ -897,6 +908,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const vibrationComponent = device.getComponent(key); if (vibrationComponent?.hasProperty('vibration') && isValidBoolean(vibrationComponent.getValue('vibration'))) { const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); // Add event handler @@ -908,6 +920,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const tempComponent = device.getComponent(key); if (tempComponent?.hasProperty('value') && isValidNumber(tempComponent.getValue('value'))) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('value') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); // Add event handler @@ -918,6 +931,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('tC') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); + child.log.logName = `${device.name} ${key}`; // Add event handler tempComponent.on('update', (component: string, property: string, value: ShellyDataType) => { shellyUpdateHandler(this, mbDevice, device, component, property, value); @@ -927,6 +941,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const humidityComponent = device.getComponent(key); if (humidityComponent?.hasProperty('value') && isValidNumber(humidityComponent.getValue('value'), 0, 100)) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('value') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); // Add event handler @@ -936,6 +951,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } if (humidityComponent?.hasProperty('rh') && isValidNumber(humidityComponent.getValue('rh'), 0, 100)) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('rh') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); // Add event handler @@ -947,6 +963,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const illuminanceComponent = device.getComponent(key); if (illuminanceComponent?.hasProperty('lux') && isValidNumber(illuminanceComponent.getValue('lux'), 0, 10000)) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(illuminanceComponent.getValue('lux') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); // Add event handler @@ -969,6 +986,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { let child: MatterbridgeDevice; if (thermostatComponent.getValue('type') === 'heating') { child = mbDevice.addChildDeviceType(key, [thermostatDevice], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.addClusterServer( child.getDefaultHeatingThermostatClusterServer(thermostatComponent.getValue('current_C') as number, thermostatComponent.getValue('target_C') as number, 5, 35), @@ -990,6 +1008,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ); } else if (thermostatComponent.getValue('type') === 'cooling') { child = mbDevice.addChildDeviceType(key, [thermostatDevice], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.addClusterServer( child.getDefaultCoolingThermostatClusterServer(thermostatComponent.getValue('current_C') as number, thermostatComponent.getValue('target_C') as number, 5, 35), @@ -1048,6 +1067,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const floodComponent = device.getComponent(key); if (floodComponent?.hasProperty('flood') && isValidBoolean(floodComponent.getValue('flood'))) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!(floodComponent.getValue('flood') as boolean))); // Add event handler floodComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1058,6 +1078,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const gasComponent = device.getComponent(key); if (gasComponent?.hasProperty('sensor_state') && isValidString(gasComponent.getValue('alarm_state'))) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(gasComponent.getValue('alarm_state') === 'none')); // Add event handler gasComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1068,6 +1089,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const smokeComponent = device.getComponent(key); if (smokeComponent?.hasProperty('alarm') && isValidBoolean(smokeComponent.getValue('alarm'))) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!smokeComponent.getValue('alarm') as boolean)); // Add event handler smokeComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1078,6 +1100,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const luxComponent = device.getComponent(key); if (luxComponent?.hasProperty('value') && isValidNumber(luxComponent.getValue('value'), 0)) { const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(luxComponent.getValue('value') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); // Add event handler @@ -1089,6 +1112,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const blugwComponent = device.getComponent(key); if (blugwComponent?.hasProperty('sys_led_enable') && isValidBoolean(blugwComponent.getValue('sys_led_enable'))) { const child = mbDevice.addChildDeviceType(key, [modeSelect], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; child.addClusterServer( mbDevice.getDefaultModeSelectClusterServer( 'System LED', From e9782b70b32b82f88c047baf884023e07a0d6fd0 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 10:06:12 +0100 Subject: [PATCH 04/49] Add command moveToColor --- src/platform.ts | 51 ++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 55b9235..9effaac 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -61,12 +61,26 @@ import { EndpointOptions, thermostatDevice, modeSelect, + MatterbridgeEndpoint, + Groups, + Identify, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; import { AnsiLogger, BLUE, CYAN, GREEN, LogLevel, TimestampFormat, YELLOW, db, debugStringify, dn, er, hk, idn, nf, nt, rs, wr, zb } from 'matterbridge/logger'; import { NodeStorage, NodeStorageManager } from 'matterbridge/storage'; -import { hslColorToRgbColor, rgbColorToHslColor, isValidIpv4Address, isValidString, isValidNumber, isValidBoolean, isValidArray, isValidObject, waiter } from 'matterbridge/utils'; +import { + hslColorToRgbColor, + rgbColorToHslColor, + isValidIpv4Address, + isValidString, + isValidNumber, + isValidBoolean, + isValidArray, + isValidObject, + waiter, + xyColorToRgbColor, +} from 'matterbridge/utils'; import path from 'path'; import * as fs from 'fs'; @@ -109,11 +123,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { async createMutableDevice(definition: DeviceTypeDefinition | AtLeastOne, options: EndpointOptions = {}, debug = false): Promise { let device: MatterbridgeDevice; - const matterbridge = await import('matterbridge'); - if ('edge' in this.matterbridge && this.matterbridge.edge === true && 'MatterbridgeEndpoint' in matterbridge) { - // Dynamically resolve the MatterbridgeEndpoint class from the imported module and instantiate it without throwing a TypeScript error for old versions of Matterbridge - // eslint-disable-next-line @typescript-eslint/no-explicit-any - device = new (matterbridge as any).MatterbridgeEndpoint(definition, options, debug) as MatterbridgeDevice; + if (this.matterbridge.edge === true) { + device = new MatterbridgeEndpoint(definition, options, debug) as unknown as MatterbridgeDevice; } else device = new MatterbridgeDevice(definition, undefined, debug); return device; } @@ -605,7 +616,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (isLightComponent(lightComponent)) { // Set the device type and clusters based on the light component properties let deviceType = DeviceTypes.ON_OFF_LIGHT; - const clusterIds: ClusterId[] = [OnOff.Cluster.id]; + const clusterIds: ClusterId[] = [Identify.Cluster.id, Groups.Cluster.id, OnOff.Cluster.id]; if (lightComponent.hasProperty('brightness')) { deviceType = DeviceTypes.DIMMABLE_LIGHT; clusterIds.push(LevelControl.Cluster.id); @@ -616,23 +627,18 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { lightComponent.hasProperty('rgb') ) { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; - // clusterIds.push(ColorControl.Cluster.id); } - // const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], clusterIds, undefined, config.debug as boolean); const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; - child.addClusterServerFromList(child, clusterIds); - if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) { - // mbDevice.configureColorControlCluster(true, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); - child.createDefaultColorControlClusterServer(); - } else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) { - // mbDevice.configureColorControlCluster(false, false, true, ColorControl.ColorMode.ColorTemperatureMireds, child); - child.createCtColorControlClusterServer(); - } else if (deviceType === DeviceTypes.COLOR_TEMPERATURE_LIGHT) { - // mbDevice.configureColorControlCluster(true, false, false, ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child); - child.createHsColorControlClusterServer(); + mbDevice.addClusterServerFromList(child, clusterIds); + if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { + mbDevice.log.info(`***Adding color control cluster to ${key}`); + if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getXyColorControlClusterServer()); + else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getCtColorControlClusterServer()); + else child.addClusterServer(mbDevice.getHsColorControlClusterServer()); + } else { + mbDevice.log.info(`***Without color control cluster to ${key}`); } - child.addRequiredClusterServers(child); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, lightComponent); @@ -678,6 +684,11 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const rgb = hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50); shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); }); + mbDevice.addCommandHandler('moveToColor', async ({ request, attributes, endpoint }) => { + attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentXAndCurrentY); + const rgb = xyColorToRgbColor(request.colorX / 65536, request.colorY / 65536); + shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + }); mbDevice.addCommandHandler('moveToColorTemperature', async ({ request, attributes, endpoint }) => { attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds); shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorTemp', undefined, undefined, undefined, request.colorTemperatureMireds); From 518d8b68c9f09e1d7215f6f679938288348515b4 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 10:32:38 +0100 Subject: [PATCH 05/49] Update log --- src/platform.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 9effaac..5273129 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -505,7 +505,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ); mbDevice.addCommandHandler('identify', async ({ request, endpoint }) => { - this.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); }); // Set the powerSource cluster @@ -632,12 +632,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { child.log.logName = `${device.name} ${key}`; mbDevice.addClusterServerFromList(child, clusterIds); if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { - mbDevice.log.info(`***Adding color control cluster to ${key}`); - if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getXyColorControlClusterServer()); + // mbDevice.log.debug(`***Adding color control cluster to ${key}`); + if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getDefaultColorControlClusterServer()); else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getCtColorControlClusterServer()); else child.addClusterServer(mbDevice.getHsColorControlClusterServer()); } else { - mbDevice.log.info(`***Without color control cluster to ${key}`); + // mbDevice.log.debug(`***Without color control cluster to ${key}`); } // Add the electrical measurementa cluster on the same endpoint @@ -663,7 +663,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation); const saturation = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentSaturationAttribute() ?? 0; const rgb = hslColorToRgbColor((request.hue / 254) * 360, (saturation / 254) * 100, 50); - this.log.warn(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + mbDevice.log.debug(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); @@ -673,7 +673,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation); const hue = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentHueAttribute() ?? 0; const rgb = hslColorToRgbColor((hue / 254) * 360, (request.saturation / 254) * 100, 50); - this.log.warn(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + mbDevice.log.debug(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); From d574eaaf3444ceef44cac65bd4af0cbc53ebd61d Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 11:46:03 +0100 Subject: [PATCH 06/49] Add validateDeviceWhiteBlackList for BLU devices --- src/platform.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/platform.ts b/src/platform.ts index 5273129..0e7656b 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -201,7 +201,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.storedDevices.set(discoveredDevice.id, discoveredDevice); await this.saveStoredDevices(); } - if (this.validateWhiteBlackList(discoveredDevice.id)) { + if (this._validateDeviceWhiteBlackList(discoveredDevice.id)) { await this.addDevice(discoveredDevice.id, discoveredDevice.host); } }); @@ -242,6 +242,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.info(`Shelly device ${hk}${device.id}${nf} host ${zb}${device.host}${nf} is a ble gateway. Adding paired BLU devices...`); // Register the BLU devices for (const [key, bthomeDevice] of device.bthomeDevices) { + if (!this._validateDeviceWhiteBlackList(bthomeDevice.name)) continue; this.log.info( `- ${idn}${bthomeDevice.name}${rs}${nf} address ${CYAN}${bthomeDevice.addr}${nf} id ${CYAN}${bthomeDevice.id}${nf} ` + `model ${CYAN}${bthomeDevice.model}${nf} (${CYAN}${bthomeDevice.type}${nf})`, @@ -1578,4 +1579,34 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } return true; } + + // TODO: remove when matterbridge 1.6.6 is released and required + _validateDeviceWhiteBlackList(device: string) { + if (isValidArray(this.config.whiteList, 1) && !this.config.whiteList.includes(device)) { + this.log.info(`Skipping device ${CYAN}${device}${nf} because not in whitelist`); + return false; + } + if (isValidArray(this.config.blackList, 1) && this.config.blackList.includes(device)) { + this.log.info(`Skipping device ${CYAN}${device}${nf} because in blacklist`); + return false; + } + return true; + } + + // TODO: remove when matterbridge 1.6.6 is released and required + _validateEntityBlackList(device: string, entity: string) { + if (isValidArray(this.config.entityBlackList, 1) && this.config.entityBlackList.find((e) => e === entity)) { + this.log.info(`Skipping entity ${CYAN}${entity}${nf} because in entityBlackList`); + return false; + } + if ( + isValidObject(this.config.deviceEntityBlackList, 1) && + device in this.config.deviceEntityBlackList && + (this.config.deviceEntityBlackList as Record)[device].includes(entity) + ) { + this.log.info(`Skipping entity ${CYAN}${entity}${wr} for device ${CYAN}${device}${nf} because in deviceEntityBlackList`); + return false; + } + return true; + } } From 9c2369d668067b2ff434ad84d30f6c72c8351d97 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 12:41:37 +0100 Subject: [PATCH 07/49] Add validateEntityBlackList for components --- CHANGELOG.md | 16 ++++++++++++ matterbridge-shelly.schema.json | 46 ++++++++++++++++++++++++++++----- src/platform.ts | 12 ++++++--- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6321611..154dd6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. +## [1.0.12] - 2024-12-09 + +### Added + +- [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. +- [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. + +### Changed + +- [colorControl]: Refactor configuration of the cluster. +- [package]: Updated dependencies. + + + Buy me a coffee + + ## [1.0.11] - 2024-12-04 ### Added diff --git a/matterbridge-shelly.schema.json b/matterbridge-shelly.schema.json index 69f1b60..54bc56e 100644 --- a/matterbridge-shelly.schema.json +++ b/matterbridge-shelly.schema.json @@ -24,7 +24,11 @@ "exposeSwitch": { "description": "Choose how to expose the shelly switches: as a switch (don't use it for Alexa), light or outlet", "type": "string", - "enum": ["switch", "light", "outlet"], + "enum": [ + "switch", + "light", + "outlet" + ], "default": "outlet" }, "switchList": { @@ -51,7 +55,12 @@ "exposeInput": { "description": "Choose how to expose the shelly inputs: disabled, contact, momentary or latching switch (you may need to pair again the controller when changed)", "type": "string", - "enum": ["disabled", "contact", "momentary", "latching"], + "enum": [ + "disabled", + "contact", + "momentary", + "latching" + ], "default": "disabled" }, "inputContactList": { @@ -78,7 +87,10 @@ "exposeInputEvent": { "description": "Choose weather to expose the shelly input events: momentary or disabled (you may need to pair again the controller when changed)", "type": "string", - "enum": ["momentary", "disabled"], + "enum": [ + "momentary", + "disabled" + ], "default": "disabled" }, "inputEventList": { @@ -91,23 +103,43 @@ "exposePowerMeter": { "description": "Choose how to expose the shelly power meters: disabled, matter13 (will use Matter 1.3 electricalSensor)", "type": "string", - "enum": ["disabled", "matter13"], + "enum": [ + "disabled", + "matter13" + ], "default": "disabled" }, "blackList": { - "description": "The devices in the list will not be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8)", + "description": "The devices in the list will not be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU name (i.e. Shelly BLU HT 7c:c6:b6:65:2d:87)", "type": "array", "items": { "type": "string" } }, "whiteList": { - "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8)", + "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU name (i.e. Shelly BLU HT 7c:c6:b6:65:2d:87). If you add a BLU device in the white list, you need to put also his BLU gateway on the list.", "type": "array", "items": { "type": "string" } }, + "entityBlackList": { + "description": "The components in the list will not be exposed for all devices. Use the component name (i.e. Temperature)", + "type": "array", + "items": { + "type": "string" + } + }, + "deviceEntityBlackList": { + "description": "List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device.", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, "nocacheList": { "description": "The devices in the list will not be loaded from the cache. Use the device id (e.g. shellyplus2pm-5443B23D81F8)", "type": "array", @@ -187,4 +219,4 @@ "default": false } } -} +} \ No newline at end of file diff --git a/src/platform.ts b/src/platform.ts index 0e7656b..3ce72c8 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -563,6 +563,10 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Scan the device components for (const [key, component] of device) { + // Validate the component against the component black list + if (!this._validateEntityBlackList(device.id, component.name)) continue; + if (!this._validateEntityBlackList(device.id, key)) continue; + if (component.name === 'Sys') { // Add update handler from Shelly component.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1543,19 +1547,19 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } if (loadFromCache && fs.existsSync(fileName)) { - this.log.info(`*Loading from cache Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); + this.log.info(`Loading from cache Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); device = await ShellyDevice.create(this.shelly, log, fileName); if (device) { - this.log.info(`*Loaded from cache Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); + this.log.info(`Loaded from cache Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); device.setHost(host); device.cached = true; device.online = true; } } else { - this.log.info(`*Creating Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); + this.log.info(`Creating Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); device = await ShellyDevice.create(this.shelly, log, host); if (device) { - this.log.info(`*Created Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); + this.log.info(`Created Shelly device ${hk}${deviceId}${nf} host ${zb}${host}${nf}`); await device.saveDevicePayloads(this.shelly.dataPath); } } From abd193ffbd4d0046f0b6faac45faf497b20074a1 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 12:53:53 +0100 Subject: [PATCH 08/49] Move identify cluster command to child endpoint --- src/platform.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 3ce72c8..0e65850 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -505,10 +505,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { device.firmware, ); - mbDevice.addCommandHandler('identify', async ({ request, endpoint }) => { - mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); - }); - // Set the powerSource cluster const childPowerSource = mbDevice.addChildDeviceType('PowerSource', [powerSource], undefined, config.debug as boolean); const batteryComponent = device.getComponent('battery'); @@ -649,6 +645,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.addElectricalMeasurements(mbDevice, child, device, lightComponent); // Add command handlers from Matter + child.addCommandHandler('identify', async ({ request, endpoint }) => { + mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + }); mbDevice.addCommandHandler('on', async (data) => { shellyLightCommandHandler(mbDevice, data.endpoint.number, device, 'On', true); }); @@ -720,6 +719,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.addElectricalMeasurements(mbDevice, child, device, switchComponent); // Add command handlers + child.addCommandHandler('identify', async ({ request, endpoint }) => { + mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + }); mbDevice.addCommandHandler('on', async (data) => { shellySwitchCommandHandler(mbDevice, data.endpoint?.number, device, 'On'); }); @@ -756,6 +758,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.addElectricalMeasurements(mbDevice, child, device, coverComponent); // Add command handlers + child.addCommandHandler('identify', async ({ request, endpoint }) => { + mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + }); mbDevice.addCommandHandler('upOrOpen', async (data) => { shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Open', 0); }); @@ -1074,6 +1079,10 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }, mbDevice.log, ); + // Add command handlers + child.addCommandHandler('identify', async ({ request, endpoint }) => { + mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + }); // Add event handler thermostatComponent.on('update', (component: string, property: string, value: ShellyDataType) => { shellyUpdateHandler(this, mbDevice, device, component, property, value); From 25669fcdbc9ae3c6f5269f7adba493e762819d46 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:11:35 +0100 Subject: [PATCH 09/49] Move all cluster commands to child endpoint --- src/platform.ts | 144 +++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 76 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 0e65850..a5f0bb1 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -26,7 +26,6 @@ import { MatterbridgeDevice, MatterbridgeDynamicPlatform, DeviceTypes, - OnOff, OnOffCluster, PlatformConfig, PowerSource, @@ -34,9 +33,7 @@ import { onOffSwitch, powerSource, bridgedNode, - LevelControl, ColorControl, - ClusterId, LevelControlCluster, BooleanStateCluster, ColorControlCluster, @@ -62,12 +59,10 @@ import { thermostatDevice, modeSelect, MatterbridgeEndpoint, - Groups, - Identify, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; -import { AnsiLogger, BLUE, CYAN, GREEN, LogLevel, TimestampFormat, YELLOW, db, debugStringify, dn, er, hk, idn, nf, nt, rs, wr, zb } from 'matterbridge/logger'; +import { AnsiLogger, BLUE, CYAN, GREEN, LogLevel, TimestampFormat, YELLOW, db, debugStringify, dn, er, hk, idn, nf, nt, or, rs, wr, zb } from 'matterbridge/logger'; import { NodeStorage, NodeStorageManager } from 'matterbridge/storage'; import { hslColorToRgbColor, @@ -617,10 +612,10 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (isLightComponent(lightComponent)) { // Set the device type and clusters based on the light component properties let deviceType = DeviceTypes.ON_OFF_LIGHT; - const clusterIds: ClusterId[] = [Identify.Cluster.id, Groups.Cluster.id, OnOff.Cluster.id]; + // const clusterIds: ClusterId[] = [Identify.Cluster.id, Groups.Cluster.id, OnOff.Cluster.id]; if (lightComponent.hasProperty('brightness')) { deviceType = DeviceTypes.DIMMABLE_LIGHT; - clusterIds.push(LevelControl.Cluster.id); + // clusterIds.push(LevelControl.Cluster.id); } if ( (lightComponent.hasProperty('red') && lightComponent.hasProperty('green') && lightComponent.hasProperty('blue') && device.profile !== 'white') || @@ -631,12 +626,15 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; - mbDevice.addClusterServerFromList(child, clusterIds); + child.createDefaultIdentifyClusterServer(); + child.createDefaultGroupsClusterServer(); + child.createDefaultOnOffClusterServer(); + if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { // mbDevice.log.debug(`***Adding color control cluster to ${key}`); - if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getDefaultColorControlClusterServer()); - else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) child.addClusterServer(mbDevice.getCtColorControlClusterServer()); - else child.addClusterServer(mbDevice.getHsColorControlClusterServer()); + if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(child.getDefaultColorControlClusterServer()); + else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); + else child.addClusterServer(child.getHsColorControlClusterServer()); } else { // mbDevice.log.debug(`***Without color control cluster to ${key}`); } @@ -645,57 +643,57 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.addElectricalMeasurements(mbDevice, child, device, lightComponent); // Add command handlers from Matter - child.addCommandHandler('identify', async ({ request, endpoint }) => { - mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + child.addCommandHandler('identify', async ({ request }) => { + child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); }); - mbDevice.addCommandHandler('on', async (data) => { - shellyLightCommandHandler(mbDevice, data.endpoint.number, device, 'On', true); + child.addCommandHandler('on', async () => { + shellyLightCommandHandler(mbDevice, child.number, device, 'On', true); }); - mbDevice.addCommandHandler('off', async (data) => { - shellyLightCommandHandler(mbDevice, data.endpoint.number, device, 'Off', false); + child.addCommandHandler('off', async () => { + shellyLightCommandHandler(mbDevice, child.number, device, 'Off', false); }); - mbDevice.addCommandHandler('toggle', async (data) => { - shellyLightCommandHandler(mbDevice, data.endpoint.number, device, 'Toggle', false); + child.addCommandHandler('toggle', async () => { + shellyLightCommandHandler(mbDevice, child.number, device, 'Toggle', false); }); - mbDevice.addCommandHandler('moveToLevel', async ({ request, endpoint }) => { - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'Level', undefined, request.level); + child.addCommandHandler('moveToLevel', async ({ request }) => { + shellyLightCommandHandler(mbDevice, child.number, device, 'Level', undefined, request.level); }); - mbDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request, endpoint }) => { - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'Level', undefined, request.level); + child.addCommandHandler('moveToLevelWithOnOff', async ({ request }) => { + shellyLightCommandHandler(mbDevice, child.number, device, 'Level', undefined, request.level); }); - mbDevice.addCommandHandler('moveToHue', async ({ request, attributes, endpoint }) => { - attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation); - const saturation = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentSaturationAttribute() ?? 0; + child.addCommandHandler('moveToHue', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); + const saturation = child.getAttribute(ColorControlCluster.id, 'currentSaturation', child.log); const rgb = hslColorToRgbColor((request.hue / 254) * 360, (saturation / 254) * 100, 50); mbDevice.log.debug(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); }, 500); }); - mbDevice.addCommandHandler('moveToSaturation', async ({ request, attributes, endpoint }) => { - attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation); - const hue = child.getClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation))?.getCurrentHueAttribute() ?? 0; + child.addCommandHandler('moveToSaturation', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); + const hue = child.getAttribute(ColorControlCluster.id, 'currentHue', child.log); const rgb = hslColorToRgbColor((hue / 254) * 360, (request.saturation / 254) * 100, 50); mbDevice.log.debug(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); device.colorCommandTimeout = setTimeout(() => { - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); }, 500); }); - mbDevice.addCommandHandler('moveToHueAndSaturation', async ({ request, attributes, endpoint }) => { - attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation); + child.addCommandHandler('moveToHueAndSaturation', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); const rgb = hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50); - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); }); - mbDevice.addCommandHandler('moveToColor', async ({ request, attributes, endpoint }) => { - attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentXAndCurrentY); + child.addCommandHandler('moveToColor', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY, child.log); const rgb = xyColorToRgbColor(request.colorX / 65536, request.colorY / 65536); - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); }); - mbDevice.addCommandHandler('moveToColorTemperature', async ({ request, attributes, endpoint }) => { - attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds); - shellyLightCommandHandler(mbDevice, endpoint.number, device, 'ColorTemp', undefined, undefined, undefined, request.colorTemperatureMireds); + child.addCommandHandler('moveToColorTemperature', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, child.log); + shellyLightCommandHandler(mbDevice, child.number, device, 'ColorTemp', undefined, undefined, undefined, request.colorTemperatureMireds); }); // Add event handler from Shelly @@ -712,36 +710,28 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (config.switchList && (config.switchList as string[]).includes(device.id)) deviceType = onOffSwitch; if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [deviceType], [OnOff.Cluster.id], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; + child.createDefaultIdentifyClusterServer(); + child.createDefaultGroupsClusterServer(); + child.createDefaultOnOffClusterServer(); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, switchComponent); // Add command handlers - child.addCommandHandler('identify', async ({ request, endpoint }) => { - mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + child.addCommandHandler('identify', async ({ request }) => { + child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); }); - mbDevice.addCommandHandler('on', async (data) => { - shellySwitchCommandHandler(mbDevice, data.endpoint?.number, device, 'On'); + child.addCommandHandler('on', async () => { + shellySwitchCommandHandler(mbDevice, child.number, device, 'On'); }); - mbDevice.addCommandHandler('off', async (data) => { - shellySwitchCommandHandler(mbDevice, data.endpoint?.number, device, 'Off'); + child.addCommandHandler('off', async () => { + shellySwitchCommandHandler(mbDevice, child.number, device, 'Off'); }); - mbDevice.addCommandHandler('toggle', async (data) => { - shellySwitchCommandHandler(mbDevice, data.endpoint?.number, device, 'Toggle'); + child.addCommandHandler('toggle', async () => { + shellySwitchCommandHandler(mbDevice, child.number, device, 'Toggle'); }); - if (this.matterbridge.edge) { - child.addCommandHandler('on', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'On'); - }); - child.addCommandHandler('off', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'Off'); - }); - child.addCommandHandler('toggle', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'Toggle'); - }); - } // Add event handler switchComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -751,29 +741,31 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Cover' || component.name === 'Roller') { const coverComponent = device.getComponent(key); if (coverComponent) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.WINDOW_COVERING], [WindowCovering.Cluster.id], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; + child.createDefaultIdentifyClusterServer(); + child.createDefaultWindowCoveringClusterServer(); // Add the electrical measurementa cluster on the same endpoint this.addElectricalMeasurements(mbDevice, child, device, coverComponent); // Add command handlers - child.addCommandHandler('identify', async ({ request, endpoint }) => { - mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + child.addCommandHandler('identify', async ({ request }) => { + child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); }); - mbDevice.addCommandHandler('upOrOpen', async (data) => { - shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Open', 0); + child.addCommandHandler('upOrOpen', async () => { + shellyCoverCommandHandler(mbDevice, child.number, device, 'Open', 0); }); - mbDevice.addCommandHandler('downOrClose', async (data) => { - shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Close', 10000); + child.addCommandHandler('downOrClose', async () => { + shellyCoverCommandHandler(mbDevice, child.number, device, 'Close', 10000); }); - mbDevice.addCommandHandler('stopMotion', async (data) => { - shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Stop'); + child.addCommandHandler('stopMotion', async () => { + shellyCoverCommandHandler(mbDevice, child.number, device, 'Stop'); }); - mbDevice.addCommandHandler('goToLiftPercentage', async (data) => { - if (data.request.liftPercent100thsValue === 0) shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Open', 0); - else if (data.request.liftPercent100thsValue === 10000) shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'Close', 10000); - else shellyCoverCommandHandler(mbDevice, data.endpoint.number, device, 'GoToPosition', data.request.liftPercent100thsValue); + child.addCommandHandler('goToLiftPercentage', async ({ request }) => { + if (request.liftPercent100thsValue === 0) shellyCoverCommandHandler(mbDevice, child.number, device, 'Open', 0); + else if (request.liftPercent100thsValue === 10000) shellyCoverCommandHandler(mbDevice, child.number, device, 'Close', 10000); + else shellyCoverCommandHandler(mbDevice, child.number, device, 'GoToPosition', request.liftPercent100thsValue); }); // Add event handler coverComponent.on('update', (component: string, property: string, value: ShellyDataType) => { @@ -1080,8 +1072,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.log, ); // Add command handlers - child.addCommandHandler('identify', async ({ request, endpoint }) => { - mbDevice.log.info(`Identify command received for endpoint ${endpoint.number} request ${debugStringify(request)}`); + child.addCommandHandler('identify', async ({ request }) => { + child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); }); // Add event handler thermostatComponent.on('update', (component: string, property: string, value: ShellyDataType) => { From 6f354983fe68ac655a5009e65ebfe478ada416b1 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:37:59 +0100 Subject: [PATCH 10/49] Add shellyIdentifyCommandHandler --- src/platform.ts | 18 +++++++++++------- src/platformCommandHadlers.ts | 12 ++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index a5f0bb1..8a3c2ad 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -62,7 +62,7 @@ import { } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; -import { AnsiLogger, BLUE, CYAN, GREEN, LogLevel, TimestampFormat, YELLOW, db, debugStringify, dn, er, hk, idn, nf, nt, or, rs, wr, zb } from 'matterbridge/logger'; +import { AnsiLogger, BLUE, CYAN, GREEN, LogLevel, TimestampFormat, YELLOW, db, dn, er, hk, idn, nf, nt, rs, wr, zb } from 'matterbridge/logger'; import { NodeStorage, NodeStorageManager } from 'matterbridge/storage'; import { hslColorToRgbColor, @@ -85,7 +85,7 @@ import { DiscoveredDevice } from './mdnsScanner.js'; import { ShellyDevice } from './shellyDevice.js'; import { isLightComponent, ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; import { ShellyData, ShellyDataType } from './shellyTypes.js'; -import { shellyCoverCommandHandler, shellyLightCommandHandler, shellySwitchCommandHandler } from './platformCommandHadlers.js'; +import { shellyCoverCommandHandler, shellyIdentifyCommandHandler, shellyLightCommandHandler, shellySwitchCommandHandler } from './platformCommandHadlers.js'; import { shellyUpdateHandler } from './platformUpdateHandler.js'; type ConfigDeviceIp = Record; @@ -363,9 +363,10 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidNumber(rssi, -100, 0) || !isValidNumber(packet_id, 0) || !isValidNumber(last_updated_ts)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); - return; + return; // Shelly device shelly2pmg3-34CDB0770C4C host 192.168.1.166 sent an unknown BLU device address 0c:ef:f6:f1:d7:7b } blu.log.info( `${idn}BLU${rs}${db} observer device update message for BLU device ${idn}${blu?.deviceName ?? addr}${rs}${db}: rssi ${YELLOW}${rssi}${db} packet_id ${YELLOW}${packet_id}${db} last_updated ${YELLOW}${device.getLocalTimeFromLastUpdated(last_updated_ts)}${db}`, @@ -376,6 +377,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -436,6 +438,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -457,6 +460,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -644,7 +648,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Add command handlers from Matter child.addCommandHandler('identify', async ({ request }) => { - child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); + shellyIdentifyCommandHandler(child, component, request); }); child.addCommandHandler('on', async () => { shellyLightCommandHandler(mbDevice, child.number, device, 'On', true); @@ -721,7 +725,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Add command handlers child.addCommandHandler('identify', async ({ request }) => { - child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); + shellyIdentifyCommandHandler(child, component, request); }); child.addCommandHandler('on', async () => { shellySwitchCommandHandler(mbDevice, child.number, device, 'On'); @@ -751,7 +755,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Add command handlers child.addCommandHandler('identify', async ({ request }) => { - child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); + shellyIdentifyCommandHandler(child, component, request); }); child.addCommandHandler('upOrOpen', async () => { shellyCoverCommandHandler(mbDevice, child.number, device, 'Open', 0); @@ -1073,7 +1077,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ); // Add command handlers child.addCommandHandler('identify', async ({ request }) => { - child.log.info(`Identify command received for endpoint ${or}${child.name}${nf}:${or}${child.number}${nf} request ${debugStringify(request)}`); + shellyIdentifyCommandHandler(child, component, request); }); // Add event handler thermostatComponent.on('update', (component: string, property: string, value: ShellyDataType) => { diff --git a/src/platformCommandHadlers.ts b/src/platformCommandHadlers.ts index ceae488..6b3045f 100644 --- a/src/platformCommandHadlers.ts +++ b/src/platformCommandHadlers.ts @@ -22,11 +22,19 @@ */ import { EndpointNumber, MatterbridgeDevice, WindowCovering, WindowCoveringCluster } from 'matterbridge'; -import { db, dn, er, hk, idn, nf, rs, YELLOW } from 'matterbridge/logger'; +import { db, debugStringify, dn, er, hk, idn, nf, or, rs, YELLOW } from 'matterbridge/logger'; import { isValidNumber, isValidObject } from 'matterbridge/utils'; import { ShellyDevice } from './shellyDevice.js'; -import { ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; +import { ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; + +type PrimitiveValues = boolean | number | bigint | string | object | null | undefined; + +export function shellyIdentifyCommandHandler(endpoint: MatterbridgeDevice, component: ShellyComponent, request: Record): void { + endpoint.log.info( + `Identify command received for endpoint ${or}${endpoint.name}${nf}:${or}${endpoint.number}${nf} component ${hk}${component.name}${nf}:${hk}${component.id}${nf} request ${debugStringify(request)}`, + ); +} export function shellySwitchCommandHandler( matterbridgeDevice: MatterbridgeDevice, From 084ead4e37465cf08171ee4b6f5fea5450743076 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:45:37 +0100 Subject: [PATCH 11/49] Add log to validate methods --- src/platform.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 8a3c2ad..61db087 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -363,7 +363,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidNumber(rssi, -100, 0) || !isValidNumber(packet_id, 0) || !isValidNumber(last_updated_ts)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; // Shelly device shelly2pmg3-34CDB0770C4C host 192.168.1.166 sent an unknown BLU device address 0c:ef:f6:f1:d7:7b @@ -377,7 +377,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -438,7 +438,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -460,7 +460,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -1590,22 +1590,22 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } // TODO: remove when matterbridge 1.6.6 is released and required - _validateDeviceWhiteBlackList(device: string) { + _validateDeviceWhiteBlackList(device: string, log = true) { if (isValidArray(this.config.whiteList, 1) && !this.config.whiteList.includes(device)) { - this.log.info(`Skipping device ${CYAN}${device}${nf} because not in whitelist`); + if (log) this.log.info(`Skipping device ${CYAN}${device}${nf} because not in whitelist`); return false; } if (isValidArray(this.config.blackList, 1) && this.config.blackList.includes(device)) { - this.log.info(`Skipping device ${CYAN}${device}${nf} because in blacklist`); + if (log) this.log.info(`Skipping device ${CYAN}${device}${nf} because in blacklist`); return false; } return true; } // TODO: remove when matterbridge 1.6.6 is released and required - _validateEntityBlackList(device: string, entity: string) { + _validateEntityBlackList(device: string, entity: string, log = true) { if (isValidArray(this.config.entityBlackList, 1) && this.config.entityBlackList.find((e) => e === entity)) { - this.log.info(`Skipping entity ${CYAN}${entity}${nf} because in entityBlackList`); + if (log) this.log.info(`Skipping entity ${CYAN}${entity}${nf} because in entityBlackList`); return false; } if ( @@ -1613,7 +1613,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { device in this.config.deviceEntityBlackList && (this.config.deviceEntityBlackList as Record)[device].includes(entity) ) { - this.log.info(`Skipping entity ${CYAN}${entity}${wr} for device ${CYAN}${device}${nf} because in deviceEntityBlackList`); + if (log) this.log.info(`Skipping entity ${CYAN}${entity}${wr} for device ${CYAN}${device}${nf} because in deviceEntityBlackList`); return false; } return true; From 0841565f81fd8e1c84a93759bec8966383ec574f Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:45:51 +0100 Subject: [PATCH 12/49] Bump version to 1.1.0-dev.1 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e39c21..09be49f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.0.12-dev.1", + "version": "1.1.0-dev.1", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 3424d24f1b946994c639302e409cfe96e2e1cea0 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:53:23 +0100 Subject: [PATCH 13/49] Dev 1.1.0-dev.1 --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a676986..6a1a7e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.0.12-dev.1", + "version": "1.1.0-dev.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.0.12-dev.1", + "version": "1.1.0-dev.1", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", From 463bf2d415fa4663177247aa6cdf9f5a1a46de26 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 14:53:57 +0100 Subject: [PATCH 14/49] Update changelog for version 1.1.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 154dd6e..c85b2d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.0.12] - 2024-12-09 +## [1.1.0] - 2024-12-09 ### Added From 55531d2a5bd27543b9d6d7ee932c8e1d2abf74ce Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 15:51:50 +0100 Subject: [PATCH 15/49] Refactor shellySwitchCommandHandler and shellyLightCommandHandler --- CHANGELOG.md | 3 + src/platform.ts | 244 ++++++++++++++++------------------ src/platformCommandHadlers.ts | 97 +++++--------- 3 files changed, 152 insertions(+), 192 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c85b2d2..9bce393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva ### Added +- [shelly]: Add shellyIdentifyCommandHandler +- [shelly]: Refactor shellySwitchCommandHandler +- [shelly]: Refactor shellyLightCommandHandler - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. diff --git a/src/platform.ts b/src/platform.ts index 61db087..03384bc 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -83,7 +83,7 @@ import * as fs from 'fs'; import { Shelly } from './shelly.js'; import { DiscoveredDevice } from './mdnsScanner.js'; import { ShellyDevice } from './shellyDevice.js'; -import { isLightComponent, ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; +import { isLightComponent, isSwitchComponent, ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; import { ShellyData, ShellyDataType } from './shellyTypes.js'; import { shellyCoverCommandHandler, shellyIdentifyCommandHandler, shellyLightCommandHandler, shellySwitchCommandHandler } from './platformCommandHadlers.js'; import { shellyUpdateHandler } from './platformUpdateHandler.js'; @@ -611,137 +611,129 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { device.log.notice(`Shelly device ${idn}${device.name}${rs}${nt} id ${hk}${device.id}${nt} host ${zb}${device.host}${nt} is sleeping`); } }); - } else if (component.name === 'Light' || component.name === 'Rgb') { - const lightComponent = device.getComponent(key); - if (isLightComponent(lightComponent)) { - // Set the device type and clusters based on the light component properties - let deviceType = DeviceTypes.ON_OFF_LIGHT; - // const clusterIds: ClusterId[] = [Identify.Cluster.id, Groups.Cluster.id, OnOff.Cluster.id]; - if (lightComponent.hasProperty('brightness')) { - deviceType = DeviceTypes.DIMMABLE_LIGHT; - // clusterIds.push(LevelControl.Cluster.id); - } - if ( - (lightComponent.hasProperty('red') && lightComponent.hasProperty('green') && lightComponent.hasProperty('blue') && device.profile !== 'white') || - (lightComponent.hasProperty('temp') && device.profile !== 'color') || - lightComponent.hasProperty('rgb') - ) { - deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; - } - const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); - child.log.logName = `${device.name} ${key}`; - child.createDefaultIdentifyClusterServer(); - child.createDefaultGroupsClusterServer(); - child.createDefaultOnOffClusterServer(); - if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); - if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { - // mbDevice.log.debug(`***Adding color control cluster to ${key}`); - if (lightComponent.hasProperty('temp') && lightComponent.hasProperty('mode')) child.addClusterServer(child.getDefaultColorControlClusterServer()); - else if (lightComponent.hasProperty('temp') && !lightComponent.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); - else child.addClusterServer(child.getHsColorControlClusterServer()); - } else { - // mbDevice.log.debug(`***Without color control cluster to ${key}`); - } + } else if (isLightComponent(component)) { + // Set the device type and clusters based on the light component properties + let deviceType = DeviceTypes.ON_OFF_LIGHT; + if (component.hasProperty('brightness')) { + deviceType = DeviceTypes.DIMMABLE_LIGHT; + } + if ( + (component.hasProperty('red') && component.hasProperty('green') && component.hasProperty('blue') && device.profile !== 'white') || + (component.hasProperty('temp') && device.profile !== 'color') || + component.hasProperty('rgb') + ) { + deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; + } + const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; + child.createDefaultIdentifyClusterServer(); + child.createDefaultGroupsClusterServer(); + child.createDefaultOnOffClusterServer(); + if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); + if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { + // mbDevice.log.debug(`***Adding color control cluster to ${key}`); + if (component.hasProperty('temp') && component.hasProperty('mode')) child.addClusterServer(child.getDefaultColorControlClusterServer()); + else if (component.hasProperty('temp') && !component.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); + else child.addClusterServer(child.getHsColorControlClusterServer()); + } else { + // mbDevice.log.debug(`***Without color control cluster to ${key}`); + } - // Add the electrical measurementa cluster on the same endpoint - this.addElectricalMeasurements(mbDevice, child, device, lightComponent); + // Add the electrical measurementa cluster on the same endpoint + this.addElectricalMeasurements(mbDevice, child, device, component); - // Add command handlers from Matter - child.addCommandHandler('identify', async ({ request }) => { - shellyIdentifyCommandHandler(child, component, request); - }); - child.addCommandHandler('on', async () => { - shellyLightCommandHandler(mbDevice, child.number, device, 'On', true); - }); - child.addCommandHandler('off', async () => { - shellyLightCommandHandler(mbDevice, child.number, device, 'Off', false); - }); - child.addCommandHandler('toggle', async () => { - shellyLightCommandHandler(mbDevice, child.number, device, 'Toggle', false); - }); - child.addCommandHandler('moveToLevel', async ({ request }) => { - shellyLightCommandHandler(mbDevice, child.number, device, 'Level', undefined, request.level); - }); - child.addCommandHandler('moveToLevelWithOnOff', async ({ request }) => { - shellyLightCommandHandler(mbDevice, child.number, device, 'Level', undefined, request.level); - }); - child.addCommandHandler('moveToHue', async ({ request }) => { - child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); - const saturation = child.getAttribute(ColorControlCluster.id, 'currentSaturation', child.log); - const rgb = hslColorToRgbColor((request.hue / 254) * 360, (saturation / 254) * 100, 50); - mbDevice.log.debug(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); - if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); - device.colorCommandTimeout = setTimeout(() => { - shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); - }, 500); - }); - child.addCommandHandler('moveToSaturation', async ({ request }) => { - child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); - const hue = child.getAttribute(ColorControlCluster.id, 'currentHue', child.log); - const rgb = hslColorToRgbColor((hue / 254) * 360, (request.saturation / 254) * 100, 50); - mbDevice.log.debug(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); - if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); - device.colorCommandTimeout = setTimeout(() => { - shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); - }, 500); - }); - child.addCommandHandler('moveToHueAndSaturation', async ({ request }) => { - child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); - const rgb = hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50); - shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); - }); - child.addCommandHandler('moveToColor', async ({ request }) => { - child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY, child.log); - const rgb = xyColorToRgbColor(request.colorX / 65536, request.colorY / 65536); - shellyLightCommandHandler(mbDevice, child.number, device, 'ColorRGB', undefined, undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); - }); - child.addCommandHandler('moveToColorTemperature', async ({ request }) => { - child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, child.log); - shellyLightCommandHandler(mbDevice, child.number, device, 'ColorTemp', undefined, undefined, undefined, request.colorTemperatureMireds); - }); + // Add command handlers from Matter + child.addCommandHandler('identify', async ({ request }) => { + shellyIdentifyCommandHandler(child, component, request); + }); + child.addCommandHandler('on', async () => { + shellyLightCommandHandler(child, component, 'On'); + }); + child.addCommandHandler('off', async () => { + shellyLightCommandHandler(child, component, 'Off'); + }); + child.addCommandHandler('toggle', async () => { + shellyLightCommandHandler(child, component, 'Toggle'); + }); + child.addCommandHandler('moveToLevel', async ({ request }) => { + shellyLightCommandHandler(child, component, 'Level', request.level); + }); + child.addCommandHandler('moveToLevelWithOnOff', async ({ request }) => { + shellyLightCommandHandler(child, component, 'Level', request.level); + }); + child.addCommandHandler('moveToHue', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); + const saturation = child.getAttribute(ColorControlCluster.id, 'currentSaturation', child.log); + const rgb = hslColorToRgbColor((request.hue / 254) * 360, (saturation / 254) * 100, 50); + mbDevice.log.debug(`Sending command moveToHue => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); + device.colorCommandTimeout = setTimeout(() => { + shellyLightCommandHandler(child, component, 'ColorRGB', undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + }, 500); + }); + child.addCommandHandler('moveToSaturation', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); + const hue = child.getAttribute(ColorControlCluster.id, 'currentHue', child.log); + const rgb = hslColorToRgbColor((hue / 254) * 360, (request.saturation / 254) * 100, 50); + mbDevice.log.debug(`Sending command moveToSaturation => ColorRGB(${rgb.r}, ${rgb.g}, ${rgb.b})`); + if (device.colorCommandTimeout) clearTimeout(device.colorCommandTimeout); + device.colorCommandTimeout = setTimeout(() => { + shellyLightCommandHandler(child, component, 'ColorRGB', undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + }, 500); + }); + child.addCommandHandler('moveToHueAndSaturation', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, child.log); + const rgb = hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50); + shellyLightCommandHandler(child, component, 'ColorRGB', undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + }); + child.addCommandHandler('moveToColor', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY, child.log); + const rgb = xyColorToRgbColor(request.colorX / 65536, request.colorY / 65536); + shellyLightCommandHandler(child, component, 'ColorRGB', undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + }); + child.addCommandHandler('moveToColorTemperature', async ({ request }) => { + child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, child.log); + shellyLightCommandHandler(child, component, 'ColorTemp', undefined, undefined, request.colorTemperatureMireds); + }); - // Add event handler from Shelly - lightComponent.on('update', (component: string, property: string, value: ShellyDataType) => { - shellyUpdateHandler(this, mbDevice, device, component, property, value); - }); - } - } else if (component.name === 'Switch' || component.name === 'Relay') { - const switchComponent = device.getComponent(key); - if (switchComponent) { - let deviceType = onOffSwitch; - if (config.exposeSwitch === 'light') deviceType = onOffLight; - if (config.exposeSwitch === 'outlet') deviceType = onOffOutlet; - if (config.switchList && (config.switchList as string[]).includes(device.id)) deviceType = onOffSwitch; - if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; - if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; - const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); - child.log.logName = `${device.name} ${key}`; - child.createDefaultIdentifyClusterServer(); - child.createDefaultGroupsClusterServer(); - child.createDefaultOnOffClusterServer(); + // Add event handler from Shelly + component.on('update', (component: string, property: string, value: ShellyDataType) => { + shellyUpdateHandler(this, mbDevice, device, component, property, value); + }); + } else if (isSwitchComponent(component)) { + let deviceType = onOffSwitch; + if (config.exposeSwitch === 'light') deviceType = onOffLight; + if (config.exposeSwitch === 'outlet') deviceType = onOffOutlet; + if (config.switchList && (config.switchList as string[]).includes(device.id)) deviceType = onOffSwitch; + if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; + if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; + const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; + child.createDefaultIdentifyClusterServer(); + child.createDefaultGroupsClusterServer(); + child.createDefaultOnOffClusterServer(); - // Add the electrical measurementa cluster on the same endpoint - this.addElectricalMeasurements(mbDevice, child, device, switchComponent); + // Add the electrical measurementa cluster on the same endpoint + this.addElectricalMeasurements(mbDevice, child, device, component); - // Add command handlers - child.addCommandHandler('identify', async ({ request }) => { - shellyIdentifyCommandHandler(child, component, request); - }); - child.addCommandHandler('on', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'On'); - }); - child.addCommandHandler('off', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'Off'); - }); - child.addCommandHandler('toggle', async () => { - shellySwitchCommandHandler(mbDevice, child.number, device, 'Toggle'); - }); + // Add command handlers + child.addCommandHandler('identify', async ({ request }) => { + shellyIdentifyCommandHandler(child, component, request); + }); + child.addCommandHandler('on', async () => { + shellySwitchCommandHandler(child, component, 'On'); + }); + child.addCommandHandler('off', async () => { + shellySwitchCommandHandler(child, component, 'Off'); + }); + child.addCommandHandler('toggle', async () => { + shellySwitchCommandHandler(child, component, 'Toggle'); + }); - // Add event handler - switchComponent.on('update', (component: string, property: string, value: ShellyDataType) => { - shellyUpdateHandler(this, mbDevice, device, component, property, value); - }); - } + // Add event handler + component.on('update', (component: string, property: string, value: ShellyDataType) => { + shellyUpdateHandler(this, mbDevice, device, component, property, value); + }); } else if (component.name === 'Cover' || component.name === 'Roller') { const coverComponent = device.getComponent(key); if (coverComponent) { diff --git a/src/platformCommandHadlers.ts b/src/platformCommandHadlers.ts index 6b3045f..e5763ef 100644 --- a/src/platformCommandHadlers.ts +++ b/src/platformCommandHadlers.ts @@ -30,93 +30,59 @@ import { ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwit type PrimitiveValues = boolean | number | bigint | string | object | null | undefined; +/** + * Handles the identify command for a Shelly device. + * + * @param {MatterbridgeDevice} endpoint - The Matterbridge device endpoint. + * @param {ShellyComponent} component - The Shelly component. + * @param {Record} request - The request payload. + */ export function shellyIdentifyCommandHandler(endpoint: MatterbridgeDevice, component: ShellyComponent, request: Record): void { endpoint.log.info( `Identify command received for endpoint ${or}${endpoint.name}${nf}:${or}${endpoint.number}${nf} component ${hk}${component.name}${nf}:${hk}${component.id}${nf} request ${debugStringify(request)}`, ); } -export function shellySwitchCommandHandler( - matterbridgeDevice: MatterbridgeDevice, - endpointNumber: EndpointNumber | undefined, - shellyDevice: ShellyDevice, - command: string, -): boolean { - // Get the matter endpoint - if (!endpointNumber) { - shellyDevice.log.error(`shellyCommandHandler error: endpointNumber undefined for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const endpoint = matterbridgeDevice.getChildEndpoint(endpointNumber); - if (!endpoint) { - shellyDevice.log.error(`shellyCommandHandler error: endpoint not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - // Get the Shelly component - const componentName = endpoint.uniqueStorageKey; - if (!componentName) { - shellyDevice.log.error(`shellyCommandHandler error: componentName not found for endpoint ${endpointNumber} on shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const switchComponent = shellyDevice.getComponent(componentName) as ShellySwitchComponent; - if (!switchComponent) { - shellyDevice.log.error(`shellyCommandHandler error: component ${componentName} not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - - // Send On() Off() Toggle() command +/** + * Handles switch commands (On, Off, Toggle) for a Shelly switch component. + * + * @param {MatterbridgeDevice} endpoint - The Matterbridge device endpoint. + * @param {ShellySwitchComponent} switchComponent - The Shelly switch component. + * @param {string} command - The command to execute (On, Off, Toggle). + */ +export function shellySwitchCommandHandler(endpoint: MatterbridgeDevice, switchComponent: ShellySwitchComponent, command: string): void { if (command === 'On') switchComponent.On(); else if (command === 'Off') switchComponent.Off(); else if (command === 'Toggle') switchComponent.Toggle(); - if (command === 'On' || command === 'Off' || command === 'Toggle') - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}()${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); - return true; + endpoint.log.info( + `${db}Sent command ${hk}${switchComponent.name}${db}:${hk}${switchComponent.id}${db}:${hk}${command}()${db} to shelly device ${idn}${switchComponent.device.id}${rs}${db}`, + ); } export function shellyLightCommandHandler( - matterbridgeDevice: MatterbridgeDevice, - endpointNumber: EndpointNumber | undefined, - shellyDevice: ShellyDevice, + endpoint: MatterbridgeDevice, + lightComponent: ShellyLightComponent, command: string, - state?: boolean, level?: number | null, color?: { r: number; g: number; b: number }, colorTemp?: number, ): boolean { - // Get the matter endpoint - if (!endpointNumber) { - shellyDevice.log.error(`shellyCommandHandler error: endpointNumber undefined for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const endpoint = matterbridgeDevice.getChildEndpoint(endpointNumber); - if (!endpoint) { - shellyDevice.log.error(`shellyCommandHandler error: endpoint not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - // Get the Shelly component - const componentName = endpoint.uniqueStorageKey; - if (!componentName) { - shellyDevice.log.error(`shellyCommandHandler error: componentName not found for endpoint ${endpointNumber} on shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const lightComponent = shellyDevice?.getComponent(componentName) as ShellyLightComponent; - if (!lightComponent) { - shellyDevice.log.error(`shellyCommandHandler error: component ${componentName} not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - // Send On() Off() Toggle() command if (command === 'On') lightComponent.On(); else if (command === 'Off') lightComponent.Off(); else if (command === 'Toggle') lightComponent.Toggle(); if (command === 'On' || command === 'Off' || command === 'Toggle') - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}()${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${lightComponent.name}${db}:${hk}${lightComponent.id}${db}:${hk}${command}()${db} to shelly device ${idn}${lightComponent.device.id}${rs}${db}`, + ); // Send Level() command if (command === 'Level' && isValidNumber(level, 0, 254)) { const shellyLevel = Math.max(Math.min(Math.round((level / 254) * 100), 100), 1); lightComponent.Level(shellyLevel); - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:Level(${YELLOW}${shellyLevel}${nf})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${lightComponent.name}${db}:${hk}${lightComponent.id}${db}:${hk}Level(${YELLOW}${shellyLevel}${hk})${db} to shelly device ${idn}${lightComponent.device.id}${rs}${db}`, + ); } // Send ColorRGB() command @@ -125,8 +91,8 @@ export function shellyLightCommandHandler( color.g = Math.max(Math.min(color.g, 255), 0); color.b = Math.max(Math.min(color.b, 255), 0); lightComponent.ColorRGB(color.r, color.g, color.b); - shellyDevice.log.info( - `${db}Sent command ${hk}${componentName}${nf}:ColorRGB(${YELLOW}${color.r}${nf}, ${YELLOW}${color.g}${nf}, ${YELLOW}${color.b}${nf})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`, + endpoint.log.info( + `${db}Sent command ${hk}${lightComponent.name}${db}:${hk}${lightComponent.id}${db}:${hk}ColorRGB(${YELLOW}${color.r}${hk}, ${YELLOW}${color.g}${hk}, ${YELLOW}${color.b}${hk})${db} to shelly device ${idn}${lightComponent.device.id}${rs}${db}`, ); } @@ -134,13 +100,12 @@ export function shellyLightCommandHandler( if (command === 'ColorTemp' && isValidNumber(colorTemp, 147, 500)) { const minColorTemp = 147; const maxColorTemp = 500; - const minTemp = shellyDevice.model === 'SHBDUO-1' ? 2700 : 3000; + const minTemp = lightComponent.device.model === 'SHBDUO-1' ? 2700 : 3000; const maxTemp = 6500; const temp = Math.max(Math.min(Math.round(((colorTemp - minColorTemp) / (maxColorTemp - minColorTemp)) * (minTemp - maxTemp) + maxTemp), maxTemp), minTemp); - const lightComponent = shellyDevice?.getComponent(componentName) as ShellyLightComponent; lightComponent.ColorTemp(temp); - shellyDevice.log.info( - `${db}Sent command ${hk}${componentName}${nf}:ColorTemp(for model ${shellyDevice.model} ${YELLOW}${colorTemp}->${temp}${nf})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`, + endpoint.log.info( + `${db}Sent command ${hk}${lightComponent.name}${db}:${hk}${lightComponent.id}${db}:${hk}ColorTemp(for model ${lightComponent.device.model} ${YELLOW}${colorTemp}${hk}->${YELLOW}${temp}${hk})${db} to shelly device ${idn}${lightComponent.device.id}${rs}${db}`, ); } return true; From 0b29ec29c093274259c6a86f4d72bfc670241ebd Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 15:52:06 +0100 Subject: [PATCH 16/49] Bump version to 1.1.0-dev.2 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09be49f..9c9b404 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.1", + "version": "1.1.0-dev.2", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 1ced7f1d777f516e54350aff4e50e69d5fe3b0ca Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 16:28:12 +0100 Subject: [PATCH 17/49] Remove from discovered devices the device with mongoose firmware --- package-lock.json | 4 ++-- src/platform.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a1a7e3..faf7490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.1", + "version": "1.1.0-dev.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.1", + "version": "1.1.0-dev.2", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/src/platform.ts b/src/platform.ts index 03384bc..2b6a2bf 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -171,6 +171,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // handle Shelly discovered event (called from mDNS scanner, storage or config devices) this.shelly.on('discovered', async (discoveredDevice: DiscoveredDevice) => { + if (discoveredDevice.port === 9000) { + this.log.warn( + `Shelly device ${hk}${discoveredDevice.id}${wr} host ${zb}${discoveredDevice.host}${wr} has been discovered on port ${discoveredDevice.port}. Unofficial Shelly firmware are not supported.`, + ); + return; + } if (this.discoveredDevices.has(discoveredDevice.id)) { const stored = this.storedDevices.get(discoveredDevice.id); if (stored?.host !== discoveredDevice.host) { From b1b5e7da1475bc68f84e69af5191cc5d03142bca Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 16:35:02 +0100 Subject: [PATCH 18/49] Update changelog to include removal of mongoose firmware device from discovered devices --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bce393..a813894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva ### Added +- [shelly]: Remove from discovered devices the device with mongoose firmware. - [shelly]: Add shellyIdentifyCommandHandler - [shelly]: Refactor shellySwitchCommandHandler - [shelly]: Refactor shellyLightCommandHandler From b0264c8cd1f20cad9a5eb501050f3b34853d234c Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 17:02:27 +0100 Subject: [PATCH 19/49] Refactor shellyCoverCommandHandler and update related logic in platform.ts --- CHANGELOG.md | 1 + src/platform.ts | 67 +++++++++++++-------------- src/platformCommandHadlers.ts | 85 +++++++++++++++-------------------- 3 files changed, 71 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a813894..4c660c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva - [shelly]: Add shellyIdentifyCommandHandler - [shelly]: Refactor shellySwitchCommandHandler - [shelly]: Refactor shellyLightCommandHandler +- [shelly]: Refactor shellyCoverCommandHandler - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. diff --git a/src/platform.ts b/src/platform.ts index 2b6a2bf..403adeb 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -59,6 +59,7 @@ import { thermostatDevice, modeSelect, MatterbridgeEndpoint, + WindowCoveringCluster, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; @@ -83,7 +84,7 @@ import * as fs from 'fs'; import { Shelly } from './shelly.js'; import { DiscoveredDevice } from './mdnsScanner.js'; import { ShellyDevice } from './shellyDevice.js'; -import { isLightComponent, isSwitchComponent, ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; +import { isCoverComponent, isLightComponent, isSwitchComponent, ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; import { ShellyData, ShellyDataType } from './shellyTypes.js'; import { shellyCoverCommandHandler, shellyIdentifyCommandHandler, shellyLightCommandHandler, shellySwitchCommandHandler } from './platformCommandHadlers.js'; import { shellyUpdateHandler } from './platformUpdateHandler.js'; @@ -740,40 +741,40 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { component.on('update', (component: string, property: string, value: ShellyDataType) => { shellyUpdateHandler(this, mbDevice, device, component, property, value); }); - } else if (component.name === 'Cover' || component.name === 'Roller') { - const coverComponent = device.getComponent(key); - if (coverComponent) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], undefined, config.debug as boolean); - child.log.logName = `${device.name} ${key}`; - child.createDefaultIdentifyClusterServer(); - child.createDefaultWindowCoveringClusterServer(); + } else if (isCoverComponent(component)) { + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], undefined, config.debug as boolean); + child.log.logName = `${device.name} ${key}`; + child.createDefaultIdentifyClusterServer(); + child.createDefaultWindowCoveringClusterServer(); - // Add the electrical measurementa cluster on the same endpoint - this.addElectricalMeasurements(mbDevice, child, device, coverComponent); + // Add the electrical measurementa cluster on the same endpoint + this.addElectricalMeasurements(mbDevice, child, device, component); - // Add command handlers - child.addCommandHandler('identify', async ({ request }) => { - shellyIdentifyCommandHandler(child, component, request); - }); - child.addCommandHandler('upOrOpen', async () => { - shellyCoverCommandHandler(mbDevice, child.number, device, 'Open', 0); - }); - child.addCommandHandler('downOrClose', async () => { - shellyCoverCommandHandler(mbDevice, child.number, device, 'Close', 10000); - }); - child.addCommandHandler('stopMotion', async () => { - shellyCoverCommandHandler(mbDevice, child.number, device, 'Stop'); - }); - child.addCommandHandler('goToLiftPercentage', async ({ request }) => { - if (request.liftPercent100thsValue === 0) shellyCoverCommandHandler(mbDevice, child.number, device, 'Open', 0); - else if (request.liftPercent100thsValue === 10000) shellyCoverCommandHandler(mbDevice, child.number, device, 'Close', 10000); - else shellyCoverCommandHandler(mbDevice, child.number, device, 'GoToPosition', request.liftPercent100thsValue); - }); - // Add event handler - coverComponent.on('update', (component: string, property: string, value: ShellyDataType) => { - shellyUpdateHandler(this, mbDevice, device, component, property, value); - }); - } + // Add command handlers + child.addCommandHandler('identify', async ({ request }) => { + shellyIdentifyCommandHandler(child, component, request); + }); + child.addCommandHandler('upOrOpen', async () => { + child.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 0, child.log); + shellyCoverCommandHandler(child, component, 'Open', 0); + }); + child.addCommandHandler('downOrClose', async () => { + child.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 10000, child.log); + shellyCoverCommandHandler(child, component, 'Close', 10000); + }); + child.addCommandHandler('stopMotion', async () => { + shellyCoverCommandHandler(child, component, 'Stop'); + }); + child.addCommandHandler('goToLiftPercentage', async ({ request }) => { + child.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', request.liftPercent100thsValue, child.log); + if (request.liftPercent100thsValue === 0) shellyCoverCommandHandler(child, component, 'Open', 0); + else if (request.liftPercent100thsValue === 10000) shellyCoverCommandHandler(child, component, 'Close', 10000); + else shellyCoverCommandHandler(child, component, 'GoToPosition', request.liftPercent100thsValue); + }); + // Add event handler + component.on('update', (component: string, property: string, value: ShellyDataType) => { + shellyUpdateHandler(this, mbDevice, device, component, property, value); + }); } else if (component.name === 'PowerMeter' && config.exposePowerMeter !== 'disabled') { const pmComponent = device.getComponent(key); if (pmComponent && config.exposePowerMeter === 'matter13') { diff --git a/src/platformCommandHadlers.ts b/src/platformCommandHadlers.ts index e5763ef..5d7581b 100644 --- a/src/platformCommandHadlers.ts +++ b/src/platformCommandHadlers.ts @@ -4,7 +4,7 @@ * @file src\platformCommandHandlers.ts * @author Luca Liguori * @date 2024-12-03 - * @version 1.0.0 + * @version 1.1.0 * * Copyright 2024, 2025, 2026 Luca Liguori. * @@ -21,11 +21,10 @@ * limitations under the License. * */ -import { EndpointNumber, MatterbridgeDevice, WindowCovering, WindowCoveringCluster } from 'matterbridge'; -import { db, debugStringify, dn, er, hk, idn, nf, or, rs, YELLOW } from 'matterbridge/logger'; +import { MatterbridgeDevice } from 'matterbridge'; +import { db, debugStringify, hk, idn, nf, or, rs, YELLOW } from 'matterbridge/logger'; import { isValidNumber, isValidObject } from 'matterbridge/utils'; -import { ShellyDevice } from './shellyDevice.js'; import { ShellyComponent, ShellyCoverComponent, ShellyLightComponent, ShellySwitchComponent } from './shellyComponent.js'; type PrimitiveValues = boolean | number | bigint | string | object | null | undefined; @@ -59,6 +58,17 @@ export function shellySwitchCommandHandler(endpoint: MatterbridgeDevice, switchC ); } +/** + * Handles light commands (On, Off, Toggle, Level, ColorRGB, ColorTemp) for a Shelly light component. + * + * @param {MatterbridgeDevice} endpoint - The Matterbridge device endpoint. + * @param {ShellyLightComponent} lightComponent - The Shelly light component. + * @param {string} command - The command to execute. + * @param {number | null} [level] - The level for the command. + * @param {{ r: number; g: number; b: number }} [color] - The color for the command. + * @param {number} [colorTemp] - The color temperature for the command. + * @returns {boolean} - Returns true if the command was executed successfully, false otherwise. + */ export function shellyLightCommandHandler( endpoint: MatterbridgeDevice, lightComponent: ShellyLightComponent, @@ -66,7 +76,7 @@ export function shellyLightCommandHandler( level?: number | null, color?: { r: number; g: number; b: number }, colorTemp?: number, -): boolean { +): void { // Send On() Off() Toggle() command if (command === 'On') lightComponent.On(); else if (command === 'Off') lightComponent.Off(); @@ -108,62 +118,39 @@ export function shellyLightCommandHandler( `${db}Sent command ${hk}${lightComponent.name}${db}:${hk}${lightComponent.id}${db}:${hk}ColorTemp(for model ${lightComponent.device.model} ${YELLOW}${colorTemp}${hk}->${YELLOW}${temp}${hk})${db} to shelly device ${idn}${lightComponent.device.id}${rs}${db}`, ); } - return true; } -export function shellyCoverCommandHandler( - matterbridgeDevice: MatterbridgeDevice, - endpointNumber: EndpointNumber | undefined, - shellyDevice: ShellyDevice, - command: string, - pos?: number, -): boolean { - // Get the matter endpoint - if (!endpointNumber) { - shellyDevice.log.error(`shellyCoverCommandHandler error: endpointNumber undefined for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const endpoint = matterbridgeDevice.getChildEndpoint(endpointNumber); - if (!endpoint) { - shellyDevice.log.error(`shellyCoverCommandHandler error: endpoint not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - // Get the Shelly cover component - const componentName = endpoint.uniqueStorageKey; - if (!componentName) { - shellyDevice.log.error(`shellyCoverCommandHandler error: componentName not found for endpoint ${endpointNumber} on shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } - const coverComponent = shellyDevice?.getComponent(componentName) as ShellyCoverComponent; - if (!coverComponent) { - shellyDevice.log.error(`shellyCoverCommandHandler error: component ${componentName} not found for shelly device ${dn}${shellyDevice?.id}${er}`); - return false; - } +/** + * Handles cover commands (Stop, Open, Close, GoToPosition) for a Shelly cover component. + * + * @param {MatterbridgeDevice} endpoint - The Matterbridge device endpoint. + * @param {ShellyCoverComponent} coverComponent - The Shelly cover component. + * @param {string} command - The command to execute. + * @param {number} [pos] - The position for the command. + */ +export function shellyCoverCommandHandler(endpoint: MatterbridgeDevice, coverComponent: ShellyCoverComponent, command: string, pos?: number): void { // Matter uses 10000 = fully closed 0 = fully opened // Shelly uses 0 = fully closed 100 = fully opened - const coverCluster = endpoint.getClusterServer( - WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift /* , WindowCovering.Feature.AbsolutePosition*/), - ); - if (!coverCluster) { - shellyDevice.log.error('shellyCoverCommandHandler error: cluster WindowCoveringCluster not found'); - return false; - } if (command === 'Stop') { - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}()${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${coverComponent.name}${db}:${hk}${coverComponent.id}${db}:${hk}${command}()${db} to shelly device ${idn}${coverComponent.device.id}${rs}${db}`, + ); coverComponent.Stop(); } else if (command === 'Open') { - matterbridgeDevice.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 0, shellyDevice.log, endpoint); - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}()${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${coverComponent.name}${db}:${hk}${coverComponent.id}${db}:${hk}${command}()${db} to shelly device ${idn}${coverComponent.device.id}${rs}${db}`, + ); coverComponent.Open(); } else if (command === 'Close') { - matterbridgeDevice.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 10000, shellyDevice.log, endpoint); - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}()${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${coverComponent.name}${db}:${hk}${coverComponent.id}${db}:${hk}${command}()${db} to shelly device ${idn}${coverComponent.device.id}${rs}${db}`, + ); coverComponent.Close(); } else if (command === 'GoToPosition' && isValidNumber(pos, 0, 10000)) { - matterbridgeDevice.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', pos, shellyDevice.log, endpoint); const shellyPos = 100 - Math.max(Math.min(Math.round(pos / 100), 100), 0); - shellyDevice.log.info(`${db}Sent command ${hk}${componentName}${nf}:${command}(${shellyPos})${db} to shelly device ${idn}${shellyDevice?.id}${rs}${db}`); + endpoint.log.info( + `${db}Sent command ${hk}${coverComponent.name}${db}:${hk}${coverComponent.id}${db}:${hk}${command}(${YELLOW}${shellyPos}${hk})${db} to shelly device ${idn}${coverComponent.device.id}${rs}${db}`, + ); coverComponent.GoToPosition(shellyPos); } - return true; } From e19a4080bc69aa46afccbdfd9e86b2893f740596 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 17:07:07 +0100 Subject: [PATCH 20/49] Update logging level for target position setting in shellyUpdateHandler --- src/platformUpdateHandler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platformUpdateHandler.ts b/src/platformUpdateHandler.ts index efb07eb..3fde978 100644 --- a/src/platformUpdateHandler.ts +++ b/src/platformUpdateHandler.ts @@ -41,7 +41,7 @@ import { WindowCovering, WindowCoveringCluster, } from 'matterbridge'; -import { db, debugStringify, dn, hk, idn, or, rs, YELLOW, zb } from 'matterbridge/logger'; +import { db, debugStringify, dn, er, hk, idn, or, rs, YELLOW, zb } from 'matterbridge/logger'; import { isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString, rgbColorToHslColor } from 'matterbridge/utils'; import { ShellyDevice } from './shellyDevice.js'; @@ -276,10 +276,10 @@ export function shellyUpdateHandler( const status = WindowCovering.MovementStatus.Stopped; matterbridgeDevice.setAttribute(WindowCoveringCluster.id, 'operationalStatus', { global: status, lift: status, tilt: status }, shellyDevice.log, endpoint); setTimeout(() => { - shellyDevice.log.info(`Setting target position to current position on endpoint ${or}${endpoint.name}:${endpoint.number}${db}`); + shellyDevice.log.debug(`Setting target position to current position on endpoint ${or}${endpoint.name}:${endpoint.number}${db}`); const current = matterbridgeDevice.getAttribute(WindowCoveringCluster.id, 'currentPositionLiftPercent100ths', shellyDevice.log, endpoint); if (!isValidNumber(current, 0, 10000)) { - matterbridgeDevice.log.error(`Error: current position not found on endpoint ${or}${endpoint.name}:${endpoint.number}${db} ${hk}WindowCovering${db}`); + matterbridgeDevice.log.error(`Error: current position not found on endpoint ${or}${endpoint.name}:${endpoint.number}${er}`); return; } matterbridgeDevice.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', current, shellyDevice.log, endpoint); From ffc5310896d1aebe075874acafec4cd70823fa15 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 17:25:32 +0100 Subject: [PATCH 21/49] Refactor child endpoint handling in ShellyPlatform to use for-of loop and await async operations --- src/platform.ts | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 403adeb..43cf359 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1333,7 +1333,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { console.log(`Device ${mbDevice.deviceName} cluster:`, clusterServer.id, clusterServer.name); }); */ - mbDevice.getChildEndpoints().forEach(async (childEndpoint) => { + for (const childEndpoint of mbDevice.getChildEndpoints()) { /* childEndpoint.getAllClusterServers().forEach((clusterServer) => { clusterMap.set(clusterServer.id, clusterServer.name); @@ -1341,23 +1341,24 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); */ const label = childEndpoint.uniqueStorageKey; + if (!label) return; // Configure the cluster OnOff attribute onOff - if (label?.startsWith('switch') || label?.startsWith('relay') || label?.startsWith('light') || label?.startsWith('rgb')) { + if (label.startsWith('switch') || label.startsWith('relay') || label.startsWith('light') || label.startsWith('rgb')) { const switchComponent = shellyDevice.getComponent(label) as ShellySwitchComponent; this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}state ${YELLOW}${switchComponent.getValue('state')}${nf}`); const state = switchComponent.getValue('state'); if (isValidBoolean(state)) { - mbDevice.setAttribute(OnOffCluster.id, 'onOff', state, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(OnOffCluster.id, 'onOff', state, shellyDevice.log, childEndpoint); } } // Configure the cluster LevelControl attribute currentLevel - if (label?.startsWith('light') || label?.startsWith('rgb')) { + if (label.startsWith('light') || label.startsWith('rgb')) { const lightComponent = shellyDevice.getComponent(label) as ShellyLightComponent; const level = lightComponent.getValue('brightness') as number; if (isValidNumber(level, 0, 100)) { const matterLevel = Math.max(Math.min(Math.round((level / 100) * 254), 254), 0); this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}brightness ${YELLOW}${matterLevel}${nf}`); - mbDevice.setAttribute(LevelControlCluster.id, 'currentLevel', matterLevel, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(LevelControlCluster.id, 'currentLevel', matterLevel, shellyDevice.log, childEndpoint); } // Configure the cluster ColorControl attribute currentHue, currentSaturation and colorMode if (lightComponent.hasProperty('red') && lightComponent.hasProperty('green') && lightComponent.hasProperty('blue') && shellyDevice.profile !== 'white') { @@ -1370,13 +1371,13 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.debug(`ColorRgbToHsl: R:${red} G:${green} B:${blue} => H:${hsl.h} S:${hsl.s} L:${hsl.l}`); const hue = Math.max(Math.min(Math.round((hsl.h / 360) * 254), 254), 0); const saturation = Math.max(Math.min(Math.round((hsl.s / 100) * 254), 254), 0); - if (isValidNumber(hue, 0, 254)) mbDevice.setAttribute(ColorControlCluster.id, 'currentHue', hue, shellyDevice.log, childEndpoint); - if (isValidNumber(saturation, 0, 254)) mbDevice.setAttribute(ColorControlCluster.id, 'currentSaturation', saturation, shellyDevice.log, childEndpoint); - mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, childEndpoint); + if (isValidNumber(hue, 0, 254)) await mbDevice.setAttribute(ColorControlCluster.id, 'currentHue', hue, shellyDevice.log, childEndpoint); + if (isValidNumber(saturation, 0, 254)) await mbDevice.setAttribute(ColorControlCluster.id, 'currentSaturation', saturation, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, childEndpoint); } } if (lightComponent.hasProperty('temp') && shellyDevice.profile !== 'color') { - mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, shellyDevice.log, childEndpoint); } if (lightComponent.hasProperty('rgb') && shellyDevice.profile !== 'white') { const rgb = lightComponent.getValue('rgb') as object; @@ -1386,42 +1387,42 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.debug(`ColorRgbToHsl: R:${rgb[0]} G:${rgb[1]} B:${rgb[2]} => H:${hsl.h} S:${hsl.s} L:${hsl.l}`); const hue = Math.max(Math.min(Math.round((hsl.h / 360) * 254), 254), 0); const saturation = Math.max(Math.min(Math.round((hsl.s / 100) * 254), 254), 0); - if (isValidNumber(hue, 0, 254)) mbDevice.setAttribute(ColorControlCluster.id, 'currentHue', hue, shellyDevice.log, childEndpoint); - if (isValidNumber(saturation, 0, 254)) mbDevice.setAttribute(ColorControlCluster.id, 'currentSaturation', saturation, shellyDevice.log, childEndpoint); - mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, childEndpoint); + if (isValidNumber(hue, 0, 254)) await mbDevice.setAttribute(ColorControlCluster.id, 'currentHue', hue, shellyDevice.log, childEndpoint); + if (isValidNumber(saturation, 0, 254)) await mbDevice.setAttribute(ColorControlCluster.id, 'currentSaturation', saturation, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, shellyDevice.log, childEndpoint); } } } // Configure the cluster WindowCovering attribute currentPositionLiftPercent100ths - if (label?.startsWith('cover') || label?.startsWith('roller')) { + if (label.startsWith('cover') || label.startsWith('roller')) { const coverComponent = shellyDevice.getComponent(label) as ShellyCoverComponent; const position = coverComponent.hasProperty('current_pos') ? (coverComponent.getValue('current_pos') as number) : undefined; if (isValidNumber(position, 0, 100)) { this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}current_pos ${YELLOW}${position}${nf}`); const matterPos = 10000 - Math.min(Math.max(Math.round(position * 100), 0), 10000); - mbDevice.setWindowCoveringCurrentTargetStatus(matterPos, matterPos, WindowCovering.MovementStatus.Stopped, childEndpoint); + await mbDevice.setWindowCoveringCurrentTargetStatus(matterPos, matterPos, WindowCovering.MovementStatus.Stopped, childEndpoint); } else { - mbDevice.setWindowCoveringTargetAsCurrentAndStopped(childEndpoint); + await mbDevice.setWindowCoveringTargetAsCurrentAndStopped(childEndpoint); } } // Configure the cluster Thermostat attribute occupiedHeatingSetpoint occupiedCoolingSetpoint - if (label?.startsWith('thermostat')) { + if (label.startsWith('thermostat')) { const thermostatComponent = shellyDevice.getComponent(label) as ShellyCoverComponent; const target = thermostatComponent.hasProperty('target_C') ? (thermostatComponent.getValue('target_C') as number) : undefined; if (isValidNumber(target, 5, 35)) { if (thermostatComponent.getValue('type') === 'heating') { this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}occupiedHeatingSetpoint ${YELLOW}${target}${nf}`); - mbDevice.setAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', target * 100, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', target * 100, shellyDevice.log, childEndpoint); } else if (thermostatComponent.getValue('type') === 'cooling') { this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}occupiedCoolingSetpoint ${YELLOW}${target}${nf}`); - mbDevice.setAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', target * 100, shellyDevice.log, childEndpoint); + await mbDevice.setAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', target * 100, shellyDevice.log, childEndpoint); } } } - }); + } if (shellyDevice.bthomeDevices.size > 0) { shellyDevice.log.info(`Configuring BLE devices paired to ${hk}${shellyDevice.id}${nf}...`); - shellyDevice.bthomeDevices.forEach(async (bthomeDevice) => { + shellyDevice.bthomeDevices.forEach((bthomeDevice) => { const blu = this.bluBridgedDevices.get(bthomeDevice.addr); if (!blu) return; blu.log.debug( From 9e0f80c07124a47587337575223c3ebbab3b9d30 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 17:59:17 +0100 Subject: [PATCH 22/49] Refactor electrical measurements handling in ShellyPlatform and remove unused validation logic --- src/platform.ts | 44 ++++++++++++-------------------------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 43cf359..807c0d7 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -47,7 +47,6 @@ import { PowerSourceCluster, ElectricalPowerMeasurement, ElectricalEnergyMeasurement, - Endpoint, onOffLight, onOffOutlet, ThermostatCluster, @@ -790,11 +789,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { device.log.debug( `Added ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${child.name}${db} component ${hk}${component.name}:${component.id}${db}`, ); - // Update the electrical attributes - for (const property of component.properties) { - if (!['voltage', 'current', 'power', 'apower', 'act_power', 'total', 'aenergy'].includes(property.key)) continue; - shellyUpdateHandler(this, mbDevice, device, component.id, property.key, property.value); - } // Add event handler pmComponent.on('update', (component: string, property: string, value: ShellyDataType) => { shellyUpdateHandler(this, mbDevice, device, component, property, value); @@ -1419,6 +1413,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } } } + // Update the electrical attributes + if (childEndpoint.getDeviceTypes().includes(electricalSensor)) { + const component = shellyDevice.getComponent(label); + if (!component) return; + this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} electrical component ${hk}${label}${nf}`); + for (const property of component.properties) { + if (!['voltage', 'current', 'power', 'apower', 'act_power', 'total', 'aenergy'].includes(property.key)) continue; + shellyUpdateHandler(this, mbDevice, shellyDevice, component.id, property.key, property.value); + } + } } if (shellyDevice.bthomeDevices.size > 0) { shellyDevice.log.info(`Configuring BLE devices paired to ${hk}${shellyDevice.id}${nf}...`); @@ -1484,19 +1488,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.bluBridgedDevices.forEach((bluDevice) => (bluDevice.log.logLevel = logLevel)); } - private addElectricalMeasurements(device: MatterbridgeDevice, endpoint: Endpoint | undefined, shelly: ShellyDevice, component: ShellyComponent) { - if (!endpoint) { - // this.log.info(`addElectricalMeasurements: endpoint is undefined`); - return; - } - const updateProperties = () => { - for (const property of component.properties) { - if (!['voltage', 'current', 'power', 'apower', 'act_power', 'total', 'aenergy'].includes(property.key)) continue; - shellyUpdateHandler(this, device, shelly, component.id, property.key, property.value); - } - }; - - // Add the Matter 1.3 ElectricalPowerMeasurement and ElectricalEnergyMeasurement cluster on the same endpoint + private addElectricalMeasurements(device: MatterbridgeDevice, endpoint: MatterbridgeDevice, shelly: ShellyDevice, component: ShellyComponent) { + // Add the Matter 1.3 electricalSensor device type and the PowerTopology, ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters on the same endpoint if ( this.config.exposePowerMeter === 'matter13' && (component.hasProperty('voltage') || component.hasProperty('current') || component.hasProperty('apower') || component.hasProperty('aenergy')) @@ -1508,7 +1501,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { endpoint.addClusterServer(device.getDefaultPowerTopologyClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalPowerMeasurementClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalEnergyMeasurementClusterServer()); - updateProperties(); } } @@ -1577,18 +1569,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.shellyDevices.set(device.id, device); } - private validateWhiteBlackList(entityName: string) { - if (this.whiteList.length > 0 && !this.whiteList.find((name) => name === entityName)) { - this.log.warn(`Skipping ${dn}${entityName}${wr} because not in whitelist`); - return false; - } - if (this.blackList.length > 0 && this.blackList.find((name) => name === entityName)) { - this.log.warn(`Skipping ${dn}${entityName}${wr} because in blacklist`); - return false; - } - return true; - } - // TODO: remove when matterbridge 1.6.6 is released and required _validateDeviceWhiteBlackList(device: string, log = true) { if (isValidArray(this.config.whiteList, 1) && !this.config.whiteList.includes(device)) { From 8f7a391db2f0022323aa33a726e0e00ebed94d50 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 18:31:03 +0100 Subject: [PATCH 23/49] Update changelog for version bump and refactor entries; increment package version to 1.1.0-dev.3 --- CHANGELOG.md | 8 +++++--- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c660c7..f084aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,19 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva ### Added +- [package]: Verified to work with matterbridge edge (matter.js new API). - [shelly]: Remove from discovered devices the device with mongoose firmware. - [shelly]: Add shellyIdentifyCommandHandler -- [shelly]: Refactor shellySwitchCommandHandler -- [shelly]: Refactor shellyLightCommandHandler -- [shelly]: Refactor shellyCoverCommandHandler - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. ### Changed +- [shelly]: Refactor shellySwitchCommandHandler +- [shelly]: Refactor shellyLightCommandHandler +- [shelly]: Refactor shellyCoverCommandHandler - [colorControl]: Refactor configuration of the cluster. +- [electricalSensor]: Refactor electrical measurements initial configuration - [package]: Updated dependencies. diff --git a/package-lock.json b/package-lock.json index faf7490..c8d513f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.2", + "version": "1.1.0-dev.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.2", + "version": "1.1.0-dev.3", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/package.json b/package.json index 9c9b404..028521c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.2", + "version": "1.1.0-dev.3", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 560f067128aadabd0476300564a6c26ffc530ff2 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 20:18:18 +0100 Subject: [PATCH 24/49] Add entityBlackList and deviceEntityBlackList sections to README for component exposure control --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b02f379..cb54de1 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,14 @@ If the blackList is defined the devices included in the list will not be exposed If the whiteList is defined only the devices included in the list are exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8). +### entityBlackList + +The components in the list will not be exposed for all devices. Use the component name (i.e. Temperature). + +### deviceEntityBlackList + +List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device. + ### nocacheList The devices in the list will not be loaded from the cache. Use the device id (e.g. shellyplus2pm-5443B23D81F8). This is usefull and necessary if you change the the device configuration from the device web ui of from the Shelly app (e.g. changing from color to white or from switch to cover or adding other BLU devices to a ble gateway). In this case put the device id in the list and restart. From b3b1e8ae2d4653b5da49ef541d6e68cd2a48c140 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 7 Dec 2024 20:20:52 +0100 Subject: [PATCH 25/49] Fix typo in failsafeCount description and add entityBlackList and deviceEntityBlackList sections to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb54de1..17c2e39 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Should be enabled to discover the shelly BLU devices (it will register the BLU d ### failsafeCount -Enable the failsafe count of the devices registered. If the plugin registers less devices then the configured number, the plugin will go in error mode. This is to avoid to loose the controller configuration in case of network issues (default 0 = disabled). +Enable the failsafe count of the devices registered. If the plugin registers less devices then the configured number, the plugin will go in error mode. This is to avoid to lose the controller configuration in case of network issues (default 0 = disabled). ### postfix @@ -280,6 +280,8 @@ These are the config values: "exposePowerMeter": "disabled" | "matter13" "blackList": [], "whiteList": [], + "entityBlackList": [], + "deviceEntityBlackList": {}, "nocacheList": [], "deviceIp": { "": "x.x.x.x", From b871a4f81acbb3b12f30b7d7879b81c03fc92af2 Mon Sep 17 00:00:00 2001 From: Luligu Date: Tue, 10 Dec 2024 19:01:25 +0100 Subject: [PATCH 26/49] Set configUrl for device in ShellyPlatform before registration --- src/platform.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform.ts b/src/platform.ts index 807c0d7..723a846 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1196,6 +1196,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const endpoints = mbDevice.getChildEndpoints(); if (endpoints.length > 1 || (device.hasComponent('blugw') && config.exposeBlugw !== 'disabled')) { try { + // Set configUrl for the device + mbDevice.configUrl = `http://${device.host}`; // Register the device with Matterbridge await this.registerDevice(mbDevice); // Save the MatterbridgeDevice in the bridgedDevices map From c4e132803ba6eb35bc0be60c9d3dd93c30cfe76a Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 00:04:03 +0100 Subject: [PATCH 27/49] Update README to clarify whitelist and blacklist usage --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 17c2e39..d61457b 100644 --- a/README.md +++ b/README.md @@ -181,15 +181,15 @@ The devices in the list will expose the Input event component as a momentary swi ### exposePowerMeter -Choose how to expose the shelly power meters: disabled, matter13 (it uses Matter 1.3 electricalSensor device type that is supported by only by Home Assistant so far). +Choose how to expose the shelly power meters: disabled, matter13 (it uses Matter 1.3 electricalSensor device type that is supported only by Home Assistant so far). ### blackList -If the blackList is defined the devices included in the list will not be exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8) +If the blackList is defined the devices included in the list will not be exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or the BLU addr (i.e. 7c:c6:b6:65:2d:87). ### whiteList -If the whiteList is defined only the devices included in the list are exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8). +If the whiteList is defined only the devices included in the list are exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or the BLU addr (i.e. 7c:c6:b6:65:2d:87). ### entityBlackList From ec06e90b9eaba48eb293236c28e3ab931184780f Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 00:04:37 +0100 Subject: [PATCH 28/49] Refactor device validation to use address instead of name in whitelist/blacklist checks for BLUs --- matterbridge-shelly.schema.json | 4 +- src/platform.ts | 67 +++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/matterbridge-shelly.schema.json b/matterbridge-shelly.schema.json index 54bc56e..188d458 100644 --- a/matterbridge-shelly.schema.json +++ b/matterbridge-shelly.schema.json @@ -110,14 +110,14 @@ "default": "disabled" }, "blackList": { - "description": "The devices in the list will not be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU name (i.e. Shelly BLU HT 7c:c6:b6:65:2d:87)", + "description": "The devices in the list will not be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU addr (i.e. 7c:c6:b6:65:2d:87)", "type": "array", "items": { "type": "string" } }, "whiteList": { - "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU name (i.e. Shelly BLU HT 7c:c6:b6:65:2d:87). If you add a BLU device in the white list, you need to put also his BLU gateway on the list.", + "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU addr (i.e. 7c:c6:b6:65:2d:87). If you add a BLU device in the white list, you need to put also his BLU gateway on the list.", "type": "array", "items": { "type": "string" diff --git a/src/platform.ts b/src/platform.ts index 723a846..10a255e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -243,7 +243,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.info(`Shelly device ${hk}${device.id}${nf} host ${zb}${device.host}${nf} is a ble gateway. Adding paired BLU devices...`); // Register the BLU devices for (const [key, bthomeDevice] of device.bthomeDevices) { - if (!this._validateDeviceWhiteBlackList(bthomeDevice.name)) continue; + if (!this._validateDeviceWhiteBlackList(bthomeDevice.addr)) continue; this.log.info( `- ${idn}${bthomeDevice.name}${rs}${nf} address ${CYAN}${bthomeDevice.addr}${nf} id ${CYAN}${bthomeDevice.id}${nf} ` + `model ${CYAN}${bthomeDevice.model}${nf} (${CYAN}${bthomeDevice.type}${nf})`, @@ -369,13 +369,13 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidNumber(rssi, -100, 0) || !isValidNumber(packet_id, 0) || !isValidNumber(last_updated_ts)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); - return; // Shelly device shelly2pmg3-34CDB0770C4C host 192.168.1.166 sent an unknown BLU device address 0c:ef:f6:f1:d7:7b + return; } blu.log.info( - `${idn}BLU${rs}${db} observer device update message for BLU device ${idn}${blu?.deviceName ?? addr}${rs}${db}: rssi ${YELLOW}${rssi}${db} packet_id ${YELLOW}${packet_id}${db} last_updated ${YELLOW}${device.getLocalTimeFromLastUpdated(last_updated_ts)}${db}`, + `${idn}BLU${rs}${db} observer device update message for BLU device ${idn}${blu.deviceName ?? addr}${rs}${db}: rssi ${YELLOW}${rssi}${db} packet_id ${YELLOW}${packet_id}${db} last_updated ${YELLOW}${device.getLocalTimeFromLastUpdated(last_updated_ts)}${db}`, ); }); // BLU observer sensor updates @@ -383,7 +383,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -444,7 +444,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -466,7 +466,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.name, false)) return; + if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -1196,8 +1196,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const endpoints = mbDevice.getChildEndpoints(); if (endpoints.length > 1 || (device.hasComponent('blugw') && config.exposeBlugw !== 'disabled')) { try { - // Set configUrl for the device - mbDevice.configUrl = `http://${device.host}`; // Register the device with Matterbridge await this.registerDevice(mbDevice); // Save the MatterbridgeDevice in the bridgedDevices map @@ -1323,6 +1321,10 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.error(`Shelly device with serial number ${hk}${serial}${er} not found`); return; } + // Set configUrl for the device + mbDevice.configUrl = `http://${shellyDevice.host}`; + this.log.debug(`Configuring device ${dn}${mbDevice.deviceName}${db} configUrl ${YELLOW}${mbDevice.configUrl}${db}`); + /* mbDevice.getAllClusterServers().forEach((clusterServer) => { clusterMap.set(clusterServer.id, clusterServer.name); @@ -1571,21 +1573,48 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.shellyDevices.set(device.id, device); } - // TODO: remove when matterbridge 1.6.6 is released and required - _validateDeviceWhiteBlackList(device: string, log = true) { - if (isValidArray(this.config.whiteList, 1) && !this.config.whiteList.includes(device)) { - if (log) this.log.info(`Skipping device ${CYAN}${device}${nf} because not in whitelist`); - return false; + // TODO: remove when matterbridge 1.6.6 is published + /** + * Validates if a device is allowed based on the whitelist and blacklist configurations. + * The blacklist has priority over the whitelist. + * + * @param {string | string[]} device - The device name(s) to validate. + * @param {boolean} [log=true] - Whether to log the validation result. + * @returns {boolean} - Returns true if the device is allowed, false otherwise. + */ + _validateDeviceWhiteBlackList(device: string | string[], log = true): boolean { + if (!Array.isArray(device)) device = [device]; + + let blackListBlocked = 0; + if (isValidArray(this.config.blackList, 1)) { + for (const d of device) if (this.config.blackList.includes(d)) blackListBlocked++; } - if (isValidArray(this.config.blackList, 1) && this.config.blackList.includes(device)) { - if (log) this.log.info(`Skipping device ${CYAN}${device}${nf} because in blacklist`); + if (blackListBlocked > 0) { + if (log) this.log.info(`Skipping device ${CYAN}${device.join(', ')}${nf} because in blacklist`); return false; } - return true; + + let whiteListPassed = 0; + if (isValidArray(this.config.whiteList, 1)) { + for (const d of device) if (this.config.whiteList.includes(d)) whiteListPassed++; + } else whiteListPassed++; + if (whiteListPassed > 0) { + if (log) this.log.info(`Skipping device ${CYAN}${device.join(', ')}${nf} because not in whitelist`); + return true; + } + return false; } - // TODO: remove when matterbridge 1.6.6 is released and required - _validateEntityBlackList(device: string, entity: string, log = true) { + // TODO: remove when matterbridge 1.6.6 is published + /** + * Validates if an entity is allowed based on the entity blacklist and device-entity blacklist configurations. + * + * @param {string} device - The device to which the entity belongs. + * @param {string} entity - The entity to validate. + * @param {boolean} [log=true] - Whether to log the validation result. + * @returns {boolean} - Returns true if the entity is allowed, false otherwise. + */ + _validateEntityBlackList(device: string, entity: string, log = true): boolean { if (isValidArray(this.config.entityBlackList, 1) && this.config.entityBlackList.find((e) => e === entity)) { if (log) this.log.info(`Skipping entity ${CYAN}${entity}${nf} because in entityBlackList`); return false; From 465153ba07da90baa8f7e30081e6f2b1527a42a6 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 00:05:30 +0100 Subject: [PATCH 29/49] Dev 1.1.0-dev.4 --- package-lock.json | 552 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 526 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8d513f..aff1493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.3", + "version": "1.1.0-dev.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.3", + "version": "1.1.0-dev.4", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", @@ -1557,6 +1557,139 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/parser": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", @@ -1586,7 +1719,7 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", @@ -1604,6 +1737,111 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz", + "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/type-utils": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", @@ -1632,7 +1870,25 @@ } } }, - "node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", @@ -1646,7 +1902,7 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/typescript-estree": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", @@ -1675,6 +1931,119 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz", + "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz", + "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1702,16 +2071,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz", + "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1721,22 +2090,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz", + "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/types": "8.18.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2481,9 +2846,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", - "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", + "version": "1.5.72", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz", + "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==", "dev": true, "license": "ISC" }, @@ -4460,9 +4825,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, @@ -5461,6 +5826,139 @@ } } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", diff --git a/package.json b/package.json index 028521c..d6cae1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.3", + "version": "1.1.0-dev.4", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 03eac0325a36dfda6b8a61f579c4eed7c8bf0044 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 07:30:03 +0100 Subject: [PATCH 30/49] Fix currentLevel 1-254 --- src/platform.ts | 4 ++-- src/platformCommandHadlers.ts | 2 +- src/platformUpdateHandler.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 10a255e..4abb15c 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1353,8 +1353,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (label.startsWith('light') || label.startsWith('rgb')) { const lightComponent = shellyDevice.getComponent(label) as ShellyLightComponent; const level = lightComponent.getValue('brightness') as number; - if (isValidNumber(level, 0, 100)) { - const matterLevel = Math.max(Math.min(Math.round((level / 100) * 254), 254), 0); + if (isValidNumber(level, 1, 100)) { + const matterLevel = Math.max(Math.min(Math.round((level / 100) * 254), 254), 1); this.log.info(`Configuring device ${dn}${mbDevice.deviceName}${nf} component ${hk}${label}${nf}:${zb}brightness ${YELLOW}${matterLevel}${nf}`); await mbDevice.setAttribute(LevelControlCluster.id, 'currentLevel', matterLevel, shellyDevice.log, childEndpoint); } diff --git a/src/platformCommandHadlers.ts b/src/platformCommandHadlers.ts index 5d7581b..3f80589 100644 --- a/src/platformCommandHadlers.ts +++ b/src/platformCommandHadlers.ts @@ -87,7 +87,7 @@ export function shellyLightCommandHandler( ); // Send Level() command - if (command === 'Level' && isValidNumber(level, 0, 254)) { + if (command === 'Level' && isValidNumber(level, 1, 254)) { const shellyLevel = Math.max(Math.min(Math.round((level / 254) * 100), 100), 1); lightComponent.Level(shellyLevel); endpoint.log.info( diff --git a/src/platformUpdateHandler.ts b/src/platformUpdateHandler.ts index 3fde978..56f63cb 100644 --- a/src/platformUpdateHandler.ts +++ b/src/platformUpdateHandler.ts @@ -79,7 +79,7 @@ export function shellyUpdateHandler( } // Update brightness if (isLightComponent(shellyComponent) && (property === 'gain' || property === 'brightness') && isValidNumber(value, 0, 100)) { - matterbridgeDevice.setAttribute(LevelControlCluster.id, 'currentLevel', Math.max(Math.min(Math.round((value / 100) * 254), 254), 0), shellyDevice.log, endpoint); + matterbridgeDevice.setAttribute(LevelControlCluster.id, 'currentLevel', Math.max(Math.min(Math.round((value / 100) * 254), 254), 1), shellyDevice.log, endpoint); } // Update color gen 1 if (isLightComponent(shellyComponent) && ['red', 'green', 'blue'].includes(property) && isValidNumber(value, 0, 255)) { From fd52d1be8229395530bae64e71a8856ac51e3756 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 07:31:40 +0100 Subject: [PATCH 31/49] Dev 1.1.0-dev.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index aff1493..70ae62d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.4", + "version": "1.1.0-dev.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.4", + "version": "1.1.0-dev.6", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/package.json b/package.json index d6cae1a..a12cd7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.4", + "version": "1.1.0-dev.6", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 58eebbe50e329be19196cf8c35105b8e441cef64 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 11:51:27 +0100 Subject: [PATCH 32/49] Update CHANGELOG and README for version 1.1.0-dev.6 enhancements --- CHANGELOG.md | 4 ++-- README.md | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f084aeb..ef31af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,12 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0] - 2024-12-09 +## [1.1.0-dev.6] - 2024-12-12 ### Added - [package]: Verified to work with matterbridge edge (matter.js new API). -- [shelly]: Remove from discovered devices the device with mongoose firmware. +- [shelly]: Remove from discovered devices the device with mongoose firmware. We ignore them. - [shelly]: Add shellyIdentifyCommandHandler - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. diff --git a/README.md b/README.md index d61457b..4ce7d35 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,9 @@ matterbridge ## How to use it -You may need to set some config values in the frontend (wait that the plugin has been configured before changing the config): +You may need to set some config values in the frontend. +Changing configuration after the controller is already paired may cause the controller to see the device as new devices and reset their configuration. +Wait that the plugin has been configured before changing the config. ### username From 76ee78d59cfb6ee2d8c7dc7c2c54a28b6851e3ee Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 11:56:41 +0100 Subject: [PATCH 33/49] Update colorControl creation --- src/platform.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 4abb15c..67a274a 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -638,9 +638,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { // mbDevice.log.debug(`***Adding color control cluster to ${key}`); - if (component.hasProperty('temp') && component.hasProperty('mode')) child.addClusterServer(child.getDefaultColorControlClusterServer()); + if (component.hasProperty('temp') && component.hasProperty('mode')) child.addClusterServer(child.getHsColorControlClusterServer()); else if (component.hasProperty('temp') && !component.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); - else child.addClusterServer(child.getHsColorControlClusterServer()); + else child.addClusterServer(child.getDefaultColorControlClusterServer()); } else { // mbDevice.log.debug(`***Without color control cluster to ${key}`); } From 02155bf1afef945dd769a770c5cf33fde393f237 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 12:21:46 +0100 Subject: [PATCH 34/49] Dev 1.1.0-dev.7 --- CHANGELOG.md | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef31af6..f31a8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.6] - 2024-12-12 +## [1.1.0-dev.7] - 2024-12-12 ### Added diff --git a/package-lock.json b/package-lock.json index 70ae62d..c3b9429 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.6", + "version": "1.1.0-dev.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.6", + "version": "1.1.0-dev.7", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", @@ -4489,9 +4489,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index a12cd7c..faca557 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.6", + "version": "1.1.0-dev.7", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 67a2f7eb50669812563194a1d4bac6f46e619e1f Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 14:51:24 +0100 Subject: [PATCH 35/49] Add tagList to the descriptor cluster --- src/platform.ts | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 67a274a..ff043f2 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -59,6 +59,8 @@ import { modeSelect, MatterbridgeEndpoint, WindowCoveringCluster, + Semtag, + NumberTag, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; @@ -630,7 +632,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; } - const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + const tagList = this.addTagList(component); + const child = mbDevice.addChildDeviceType(key, [deviceType], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultGroupsClusterServer(); @@ -713,7 +716,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (config.switchList && (config.switchList as string[]).includes(device.id)) deviceType = onOffSwitch; if (config.lightList && (config.lightList as string[]).includes(device.id)) deviceType = onOffLight; if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; - const child = mbDevice.addChildDeviceType(key, [deviceType], undefined, config.debug as boolean); + + const tagList = this.addTagList(component); + const child = mbDevice.addChildDeviceType(key, [deviceType], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultGroupsClusterServer(); @@ -741,7 +746,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { shellyUpdateHandler(this, mbDevice, device, component, property, value); }); } else if (isCoverComponent(component)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], undefined, config.debug as boolean); + const tagList = this.addTagList(component); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultWindowCoveringClusterServer(); @@ -777,12 +783,13 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'PowerMeter' && config.exposePowerMeter !== 'disabled') { const pmComponent = device.getComponent(key); if (pmComponent && config.exposePowerMeter === 'matter13') { + const tagList = this.addTagList(component); // Add the Matter 1.3 electricalSensor device type with the ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters const child = mbDevice.addChildDeviceTypeWithClusterServer( key, [electricalSensor], [ElectricalPowerMeasurement.Cluster.id, ElectricalEnergyMeasurement.Cluster.id], - undefined, + tagList ? { tagList } : undefined, config.debug as boolean, ); child.log.logName = `${device.name} ${key}`; @@ -795,6 +802,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } } else if (component.name === 'Input') { + const tagList = this.addTagList(component); const inputComponent = device.getComponent(key); // Skip the input component if it is disabled in Gen 2/3 devices if (inputComponent && inputComponent.hasProperty('enable') && inputComponent.getValue('enable') === false) continue; @@ -805,7 +813,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; // Set the state attribute child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(state)); @@ -822,7 +830,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -838,7 +846,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultLatchingSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -855,7 +863,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Gen 1 devices const event = inputComponent.getValue('event') as boolean; if (isValidString(event)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -874,7 +882,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { (config.exposeInputEvent !== 'disabled' || (config.inputEventList && (config.inputEventList as string[]).includes(device.id))) ) { // Gen 2/3 devices with Input type=button - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -1492,6 +1500,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.bluBridgedDevices.forEach((bluDevice) => (bluDevice.log.logLevel = logLevel)); } + private addTagList(component: ShellyComponent): Semtag[] | undefined { + // Add the tagList to the descriptor cluster + let tagList: Semtag | undefined; + if (component.index === 0) tagList = { mfgCode: null, namespaceId: NumberTag.Zero.namespaceId, tag: NumberTag.Zero.tag, label: component.id }; + else if (component.index === 1) tagList = { mfgCode: null, namespaceId: NumberTag.One.namespaceId, tag: NumberTag.One.tag, label: component.id }; + else if (component.index === 2) tagList = { mfgCode: null, namespaceId: NumberTag.Two.namespaceId, tag: NumberTag.Two.tag, label: component.id }; + else if (component.index === 3) tagList = { mfgCode: null, namespaceId: NumberTag.Three.namespaceId, tag: NumberTag.Three.tag, label: component.id }; + return tagList ? [tagList] : undefined; + } + private addElectricalMeasurements(device: MatterbridgeDevice, endpoint: MatterbridgeDevice, shelly: ShellyDevice, component: ShellyComponent) { // Add the Matter 1.3 electricalSensor device type and the PowerTopology, ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters on the same endpoint if ( From 8b0ece3d344a025498fe33454f038c2fb77fd970 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 15:34:21 +0100 Subject: [PATCH 36/49] Update README to clarify whiteList requirements for BLU devices --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ce7d35..17d2173 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ If the blackList is defined the devices included in the list will not be exposed ### whiteList If the whiteList is defined only the devices included in the list are exposed to Matter. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or the BLU addr (i.e. 7c:c6:b6:65:2d:87). +If you add a BLU device in the white list, you need to put also its BLU gateway on the list. ### entityBlackList From e8f79c1486f7edddb9f0c6fb32c1bffa1372cbc3 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 15:35:10 +0100 Subject: [PATCH 37/49] Improve child device creation logic for ElectricalMeasurements --- src/platform.ts | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index ff043f2..1d18c1e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -61,6 +61,7 @@ import { WindowCoveringCluster, Semtag, NumberTag, + coverDevice, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; @@ -633,7 +634,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; } const tagList = this.addTagList(component); - const child = mbDevice.addChildDeviceType(key, [deviceType], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType( + key, + this.hasElectricalMeasurements(component) ? [deviceType, electricalSensor] : [deviceType], + tagList ? { tagList } : undefined, + config.debug as boolean, + ); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultGroupsClusterServer(); @@ -718,7 +724,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (config.outletList && (config.outletList as string[]).includes(device.id)) deviceType = onOffOutlet; const tagList = this.addTagList(component); - const child = mbDevice.addChildDeviceType(key, [deviceType], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType( + key, + this.hasElectricalMeasurements(component) ? [deviceType, electricalSensor] : [deviceType], + tagList ? { tagList } : undefined, + config.debug as boolean, + ); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultGroupsClusterServer(); @@ -747,7 +758,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } else if (isCoverComponent(component)) { const tagList = this.addTagList(component); - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.WINDOW_COVERING], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType( + key, + this.hasElectricalMeasurements(component) ? [coverDevice, electricalSensor] : [coverDevice], + tagList ? { tagList } : undefined, + config.debug as boolean, + ); child.log.logName = `${device.name} ${key}`; child.createDefaultIdentifyClusterServer(); child.createDefaultWindowCoveringClusterServer(); @@ -1501,6 +1517,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } private addTagList(component: ShellyComponent): Semtag[] | undefined { + if (this.matterbridge.edge) return undefined; // Add the tagList to the descriptor cluster let tagList: Semtag | undefined; if (component.index === 0) tagList = { mfgCode: null, namespaceId: NumberTag.Zero.namespaceId, tag: NumberTag.Zero.tag, label: component.id }; @@ -1510,6 +1527,17 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { return tagList ? [tagList] : undefined; } + private hasElectricalMeasurements(component: ShellyComponent) { + // Check if the component has electricalSensor + if ( + this.config.exposePowerMeter === 'matter13' && + (component.hasProperty('voltage') || component.hasProperty('current') || component.hasProperty('apower') || component.hasProperty('aenergy')) + ) { + return true; + } + return false; + } + private addElectricalMeasurements(device: MatterbridgeDevice, endpoint: MatterbridgeDevice, shelly: ShellyDevice, component: ShellyComponent) { // Add the Matter 1.3 electricalSensor device type and the PowerTopology, ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters on the same endpoint if ( @@ -1517,9 +1545,11 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { (component.hasProperty('voltage') || component.hasProperty('current') || component.hasProperty('apower') || component.hasProperty('aenergy')) ) { shelly.log.debug(`Adding ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${endpoint.name}${db} component ${hk}${component.id}${db}`); + /* const deviceTypes = endpoint.getDeviceTypes(); if (!deviceTypes.includes(electricalSensor)) deviceTypes.push(electricalSensor); endpoint.setDeviceTypes(deviceTypes); + */ endpoint.addClusterServer(device.getDefaultPowerTopologyClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalPowerMeasurementClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalEnergyMeasurementClusterServer()); From d7a2c8ba942de98c6826d1eb5cf0ed3cd6032f02 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 15:36:17 +0100 Subject: [PATCH 38/49] Dev 1.1.0-dev.8 --- CHANGELOG.md | 3 ++- matterbridge-shelly.schema.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f31a8fc..b992125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.7] - 2024-12-12 +## [1.1.0-dev.8] - 2024-12-12 ### Added @@ -15,6 +15,7 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva - [shelly]: Add shellyIdentifyCommandHandler - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. +- [matter]: Add tagList to the descriptor cluster ### Changed diff --git a/matterbridge-shelly.schema.json b/matterbridge-shelly.schema.json index 188d458..a5a04db 100644 --- a/matterbridge-shelly.schema.json +++ b/matterbridge-shelly.schema.json @@ -117,7 +117,7 @@ } }, "whiteList": { - "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU addr (i.e. 7c:c6:b6:65:2d:87). If you add a BLU device in the white list, you need to put also his BLU gateway on the list.", + "description": "Only the devices in the list will be exposed. Use the device id (e.g. shellyplus2pm-5443B23D81F8) or BLU addr (i.e. 7c:c6:b6:65:2d:87). If you add a BLU device in the white list, you need to put also its BLU gateway on the list.", "type": "array", "items": { "type": "string" diff --git a/package-lock.json b/package-lock.json index c3b9429..2286baf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.7", + "version": "1.1.0-dev.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.7", + "version": "1.1.0-dev.8", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/package.json b/package.json index faca557..bfb8de8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.7", + "version": "1.1.0-dev.8", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From 4e3d564a5cdedcad74bb4a8c8bdacf64a7f5eba3 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 18:26:13 +0100 Subject: [PATCH 39/49] Add mired to rgb conversion --- README.md | 2 +- matterbridge-shelly.schema.json | 25 +++++-------------------- package.json | 2 +- src/platform.ts | 14 +++++++++----- 4 files changed, 16 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 17d2173..6598690 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ If you add a BLU device in the white list, you need to put also its BLU gateway The components in the list will not be exposed for all devices. Use the component name (i.e. Temperature). ### deviceEntityBlackList - + List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device. ### nocacheList diff --git a/matterbridge-shelly.schema.json b/matterbridge-shelly.schema.json index a5a04db..33afd72 100644 --- a/matterbridge-shelly.schema.json +++ b/matterbridge-shelly.schema.json @@ -24,11 +24,7 @@ "exposeSwitch": { "description": "Choose how to expose the shelly switches: as a switch (don't use it for Alexa), light or outlet", "type": "string", - "enum": [ - "switch", - "light", - "outlet" - ], + "enum": ["switch", "light", "outlet"], "default": "outlet" }, "switchList": { @@ -55,12 +51,7 @@ "exposeInput": { "description": "Choose how to expose the shelly inputs: disabled, contact, momentary or latching switch (you may need to pair again the controller when changed)", "type": "string", - "enum": [ - "disabled", - "contact", - "momentary", - "latching" - ], + "enum": ["disabled", "contact", "momentary", "latching"], "default": "disabled" }, "inputContactList": { @@ -87,10 +78,7 @@ "exposeInputEvent": { "description": "Choose weather to expose the shelly input events: momentary or disabled (you may need to pair again the controller when changed)", "type": "string", - "enum": [ - "momentary", - "disabled" - ], + "enum": ["momentary", "disabled"], "default": "disabled" }, "inputEventList": { @@ -103,10 +91,7 @@ "exposePowerMeter": { "description": "Choose how to expose the shelly power meters: disabled, matter13 (will use Matter 1.3 electricalSensor)", "type": "string", - "enum": [ - "disabled", - "matter13" - ], + "enum": ["disabled", "matter13"], "default": "disabled" }, "blackList": { @@ -219,4 +204,4 @@ "default": false } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index bfb8de8..9a8e39a 100644 --- a/package.json +++ b/package.json @@ -128,4 +128,4 @@ "typescript": "5.7.2", "typescript-eslint": "8.17.0" } -} \ No newline at end of file +} diff --git a/src/platform.ts b/src/platform.ts index 1d18c1e..084a17b 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -78,6 +78,8 @@ import { isValidObject, waiter, xyColorToRgbColor, + miredToKelvin, + kelvinToRGB, } from 'matterbridge/utils'; import path from 'path'; @@ -646,12 +648,9 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { child.createDefaultOnOffClusterServer(); if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { - // mbDevice.log.debug(`***Adding color control cluster to ${key}`); if (component.hasProperty('temp') && component.hasProperty('mode')) child.addClusterServer(child.getHsColorControlClusterServer()); else if (component.hasProperty('temp') && !component.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); - else child.addClusterServer(child.getDefaultColorControlClusterServer()); - } else { - // mbDevice.log.debug(`***Without color control cluster to ${key}`); + else child.addClusterServer(child.getHsColorControlClusterServer()); } // Add the electrical measurementa cluster on the same endpoint @@ -708,7 +707,12 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); child.addCommandHandler('moveToColorTemperature', async ({ request }) => { child.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, child.log); - shellyLightCommandHandler(child, component, 'ColorTemp', undefined, undefined, request.colorTemperatureMireds); + if (component.hasProperty('temp')) { + shellyLightCommandHandler(child, component, 'ColorTemp', undefined, undefined, request.colorTemperatureMireds); + } else { + const rgb = kelvinToRGB(miredToKelvin(request.colorTemperatureMireds)); + shellyLightCommandHandler(child, component, 'ColorRGB', undefined, { r: rgb.r, g: rgb.g, b: rgb.b }); + } }); // Add event handler from Shelly From f67766967f3b7d9f9fb87f35ab497205ccdc0d3b Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 20:00:14 +0100 Subject: [PATCH 40/49] Fix shellyrgbw2 sending power in the light component --- src/platform.ts | 5 ----- src/platformUpdateHandler.ts | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 084a17b..6e1e971 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1549,11 +1549,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { (component.hasProperty('voltage') || component.hasProperty('current') || component.hasProperty('apower') || component.hasProperty('aenergy')) ) { shelly.log.debug(`Adding ElectricalPowerMeasurement and ElectricalEnergyMeasurement clusters to endpoint ${hk}${endpoint.name}${db} component ${hk}${component.id}${db}`); - /* - const deviceTypes = endpoint.getDeviceTypes(); - if (!deviceTypes.includes(electricalSensor)) deviceTypes.push(electricalSensor); - endpoint.setDeviceTypes(deviceTypes); - */ endpoint.addClusterServer(device.getDefaultPowerTopologyClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalPowerMeasurementClusterServer()); endpoint.addClusterServer(device.getDefaultElectricalEnergyMeasurementClusterServer()); diff --git a/src/platformUpdateHandler.ts b/src/platformUpdateHandler.ts index 56f63cb..fac4db3 100644 --- a/src/platformUpdateHandler.ts +++ b/src/platformUpdateHandler.ts @@ -321,6 +321,7 @@ export function shellyUpdateHandler( // Gen. 1 devices have: power, total (not all) in PowerMeters and voltage in status (not all) // PRO devices have: apower, voltage, freq, current, aenergy.total (wh) and no PowerMeters if ((property === 'power' || property === 'apower' || property === 'act_power') && isValidNumber(value, 0)) { + if (property === 'power' && shellyComponent.id.startsWith('light') && shellyDevice.id.startsWith('shellyrgbw2')) return; // Skip the rest for shellyrgbw2 devices const power = Math.round(value * 1000) / 1000; matterbridgeDevice.setAttribute(ElectricalPowerMeasurementCluster.id, 'activePower', power * 1000, shellyDevice.log, endpoint); if (property === 'act_power') return; // Skip the rest for PRO devices From cc0f023af84715db6e14866f1f6a33bfcd34f500 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 20:01:58 +0100 Subject: [PATCH 41/49] Dev 1.1.0-dev.9 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b992125..a599523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.8] - 2024-12-12 +## [1.1.0-dev.9] - 2024-12-12 ### Added diff --git a/package-lock.json b/package-lock.json index 2286baf..35d0a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.8", + "version": "1.1.0-dev.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.8", + "version": "1.1.0-dev.9", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/package.json b/package.json index 9a8e39a..8093790 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.8", + "version": "1.1.0-dev.9", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", @@ -128,4 +128,4 @@ "typescript": "5.7.2", "typescript-eslint": "8.17.0" } -} +} \ No newline at end of file From 850035b3ecee3300892bfc20a39655c17cec142a Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 20:25:02 +0100 Subject: [PATCH 42/49] Update all device types --- package-lock.json | 6 ++-- src/platform.ts | 87 +++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 35d0a26..a81e44e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1185,9 +1185,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/platform.ts b/src/platform.ts index 6e1e971..0bfc26c 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -25,7 +25,6 @@ import { Matterbridge, MatterbridgeDevice, MatterbridgeDynamicPlatform, - DeviceTypes, OnOffCluster, PlatformConfig, PowerSource, @@ -62,6 +61,14 @@ import { Semtag, NumberTag, coverDevice, + genericSwitch, + contactSensor, + lightSensor, + occupancySensor, + temperatureSensor, + humiditySensor, + dimmableLight, + colorTemperatureLight, } from 'matterbridge'; // import { EveHistory, EveHistoryCluster, MatterHistory } from 'matterbridge/history'; @@ -256,7 +263,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { let definition: AtLeastOne | undefined; if (bthomeDevice.model === 'Shelly BLU DoorWindow') definition = [bridgedNode, powerSource]; else if (bthomeDevice.model === 'Shelly BLU Motion') definition = [bridgedNode, powerSource]; - else if (bthomeDevice.model === 'Shelly BLU Button1') definition = [DeviceTypes.GENERIC_SWITCH, bridgedNode, powerSource]; + else if (bthomeDevice.model === 'Shelly BLU Button1') definition = [genericSwitch, bridgedNode, powerSource]; else if (bthomeDevice.model === 'Shelly BLU HT') definition = [bridgedNode, powerSource]; else if (bthomeDevice.model === 'Shelly BLU RC Button 4') definition = [bridgedNode, powerSource]; else if (bthomeDevice.model === 'Shelly BLU Wall Switch 4') definition = [bridgedNode, powerSource]; @@ -281,37 +288,37 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (bthomeDevice.model === 'Shelly BLU DoorWindow') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Contact', [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Contact', [contactSensor], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Motion') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Motion', [DeviceTypes.OCCUPANCY_SENSOR], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Motion', [occupancySensor], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Button1') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.createDefaultSwitchClusterServer(); } else if (bthomeDevice.model === 'Shelly BLU HT') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); - mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [DeviceTypes.GENERIC_SWITCH], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [temperatureSensor], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [humiditySensor], [], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU RC Button 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Input'); - mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button0', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button1', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button2', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button3', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Wall Switch 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Input'); - mbDevice.addChildDeviceTypeWithClusterServer('Button0', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button1', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button2', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button3', [DeviceTypes.GENERIC_SWITCH], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button0', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button1', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button2', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); + mbDevice.addChildDeviceTypeWithClusterServer('Button3', [genericSwitch], [Switch.Cluster.id], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Trv') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok, 3000, 'Type AA', 2); mbDevice.createDefaultIdentifyClusterServer(); @@ -624,16 +631,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } else if (isLightComponent(component)) { // Set the device type and clusters based on the light component properties - let deviceType = DeviceTypes.ON_OFF_LIGHT; + let deviceType = onOffLight; if (component.hasProperty('brightness')) { - deviceType = DeviceTypes.DIMMABLE_LIGHT; + deviceType = dimmableLight; } if ( (component.hasProperty('red') && component.hasProperty('green') && component.hasProperty('blue') && device.profile !== 'white') || (component.hasProperty('temp') && device.profile !== 'color') || component.hasProperty('rgb') ) { - deviceType = DeviceTypes.COLOR_TEMPERATURE_LIGHT; + deviceType = colorTemperatureLight; } const tagList = this.addTagList(component); const child = mbDevice.addChildDeviceType( @@ -646,8 +653,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { child.createDefaultIdentifyClusterServer(); child.createDefaultGroupsClusterServer(); child.createDefaultOnOffClusterServer(); - if (deviceType.code === DeviceTypes.DIMMABLE_LIGHT.code || deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) child.createDefaultLevelControlClusterServer(); - if (deviceType.code === DeviceTypes.COLOR_TEMPERATURE_LIGHT.code) { + if (deviceType.code === dimmableLight.code || deviceType.code === colorTemperatureLight.code) child.createDefaultLevelControlClusterServer(); + if (deviceType.code === colorTemperatureLight.code) { if (component.hasProperty('temp') && component.hasProperty('mode')) child.addClusterServer(child.getHsColorControlClusterServer()); else if (component.hasProperty('temp') && !component.hasProperty('mode')) child.addClusterServer(child.getCtColorControlClusterServer()); else child.addClusterServer(child.getHsColorControlClusterServer()); @@ -833,7 +840,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [contactSensor], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; // Set the state attribute child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(state)); @@ -850,7 +857,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [genericSwitch], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -866,7 +873,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { ) { const state = inputComponent.getValue('state') as boolean; if (isValidBoolean(state)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [genericSwitch], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultLatchingSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -883,7 +890,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Gen 1 devices const event = inputComponent.getValue('event') as boolean; if (isValidString(event)) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [genericSwitch], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -902,7 +909,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { (config.exposeInputEvent !== 'disabled' || (config.inputEventList && (config.inputEventList as string[]).includes(device.id))) ) { // Gen 2/3 devices with Input type=button - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], tagList ? { tagList } : undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [genericSwitch], tagList ? { tagList } : undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -924,7 +931,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Sensor' && config.exposeSensor !== 'disabled') { const sensorComponent = device.getComponent(key); if (sensorComponent?.hasProperty('contact_open') && config.exposeContact !== 'disabled') { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.CONTACT_SENSOR], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [contactSensor], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(sensorComponent.getValue('contact_open') === false)); child.addRequiredClusterServers(child); @@ -934,7 +941,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } if (sensorComponent?.hasProperty('motion') && config.exposeMotion !== 'disabled') { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.OCCUPANCY_SENSOR], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [occupancySensor], undefined, config.debug as boolean); child.addClusterServer(mbDevice.getDefaultOccupancySensingClusterServer(sensorComponent.getValue('motion') === true)); child.addRequiredClusterServers(child); // Add event handler @@ -945,7 +952,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Vibration' && config.exposeVibration !== 'disabled') { const vibrationComponent = device.getComponent(key); if (vibrationComponent?.hasProperty('vibration') && isValidBoolean(vibrationComponent.getValue('vibration'))) { - const child = mbDevice.addChildDeviceType(key, [DeviceTypes.GENERIC_SWITCH], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceType(key, [genericSwitch], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultSwitchClusterServer()); child.addRequiredClusterServers(child); @@ -957,7 +964,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Temperature' && config.exposeTemperature !== 'disabled') { const tempComponent = device.getComponent(key); if (tempComponent?.hasProperty('value') && isValidNumber(tempComponent.getValue('value'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [temperatureSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('value') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); @@ -966,7 +973,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { shellyUpdateHandler(this, mbDevice, device, component, property, value); }); } else if (tempComponent?.hasProperty('tC') && isValidNumber(tempComponent.getValue('tC'), -100, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.TEMPERATURE_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [temperatureSensor], [], undefined, config.debug as boolean); const matterTemp = Math.min(Math.max(Math.round((tempComponent.getValue('tC') as number) * 100), -10000), 10000); child.addClusterServer(mbDevice.getDefaultTemperatureMeasurementClusterServer(matterTemp)); child.log.logName = `${device.name} ${key}`; @@ -978,7 +985,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Humidity' && config.exposeHumidity !== 'disabled') { const humidityComponent = device.getComponent(key); if (humidityComponent?.hasProperty('value') && isValidNumber(humidityComponent.getValue('value'), 0, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [humiditySensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('value') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); @@ -988,7 +995,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); } if (humidityComponent?.hasProperty('rh') && isValidNumber(humidityComponent.getValue('rh'), 0, 100)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.HUMIDITY_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [humiditySensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; const matterHumidity = Math.min(Math.max(Math.round((humidityComponent.getValue('rh') as number) * 100), 0), 10000); child.addClusterServer(mbDevice.getDefaultRelativeHumidityMeasurementClusterServer(matterHumidity)); @@ -1000,7 +1007,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Illuminance' && config.exposeIlluminance !== 'disabled') { const illuminanceComponent = device.getComponent(key); if (illuminanceComponent?.hasProperty('lux') && isValidNumber(illuminanceComponent.getValue('lux'), 0, 10000)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [lightSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(illuminanceComponent.getValue('lux') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); @@ -1108,7 +1115,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Flood' && config.exposeFlood !== 'disabled') { const floodComponent = device.getComponent(key); if (floodComponent?.hasProperty('flood') && isValidBoolean(floodComponent.getValue('flood'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [contactSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!(floodComponent.getValue('flood') as boolean))); // Add event handler @@ -1119,7 +1126,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Gas' && config.exposeGas !== 'disabled') { const gasComponent = device.getComponent(key); if (gasComponent?.hasProperty('sensor_state') && isValidString(gasComponent.getValue('alarm_state'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [contactSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(gasComponent.getValue('alarm_state') === 'none')); // Add event handler @@ -1130,7 +1137,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Smoke' && config.exposeSmoke !== 'disabled') { const smokeComponent = device.getComponent(key); if (smokeComponent?.hasProperty('alarm') && isValidBoolean(smokeComponent.getValue('alarm'))) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.CONTACT_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [contactSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; child.addClusterServer(mbDevice.getDefaultBooleanStateClusterServer(!smokeComponent.getValue('alarm') as boolean)); // Add event handler @@ -1141,7 +1148,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else if (component.name === 'Lux' && config.exposeLux !== 'disabled') { const luxComponent = device.getComponent(key); if (luxComponent?.hasProperty('value') && isValidNumber(luxComponent.getValue('value'), 0)) { - const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [DeviceTypes.LIGHT_SENSOR], [], undefined, config.debug as boolean); + const child = mbDevice.addChildDeviceTypeWithClusterServer(key, [lightSensor], [], undefined, config.debug as boolean); child.log.logName = `${device.name} ${key}`; const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(luxComponent.getValue('value') as number), 0xfffe), 0)); child.addClusterServer(mbDevice.getDefaultIlluminanceMeasurementClusterServer(matterLux)); From e55bbb5fecb15470940acaa4cdf77b6fc4130278 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 21:53:50 +0100 Subject: [PATCH 43/49] Require Matterbrdge 1.6.6 --- CHANGELOG.md | 4 +++- package.json | 2 +- src/index.test.ts | 8 +++++-- src/platform.test.ts | 54 ++------------------------------------------ src/platform.ts | 12 +++++----- 5 files changed, 18 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a599523..ec95e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,11 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.9] - 2024-12-12 +## [1.1.0-dev.10] - 2024-12-12 ### Added +- [package]: Requires matterbridge 1.6.6. - [package]: Verified to work with matterbridge edge (matter.js new API). - [shelly]: Remove from discovered devices the device with mongoose firmware. We ignore them. - [shelly]: Add shellyIdentifyCommandHandler @@ -24,6 +25,7 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva - [shelly]: Refactor shellyCoverCommandHandler - [colorControl]: Refactor configuration of the cluster. - [electricalSensor]: Refactor electrical measurements initial configuration +- [matter]: Update all device types. - [package]: Updated dependencies. diff --git a/package.json b/package.json index 8093790..d6c9b2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.9", + "version": "1.1.0-dev.10", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", diff --git a/src/index.test.ts b/src/index.test.ts index 3726821..c531e70 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -3,6 +3,7 @@ import { AnsiLogger } from 'matterbridge/logger'; import { ShellyPlatform } from './platform.js'; import initializePlugin from './index'; import { jest } from '@jest/globals'; +import { wait } from 'matterbridge/utils'; describe('initializePlugin', () => { let mockMatterbridge: Matterbridge; @@ -16,7 +17,7 @@ describe('initializePlugin', () => { matterbridgeDirectory: '', matterbridgePluginDirectory: 'temp', systemInformation: { ipv4Address: undefined }, - matterbridgeVersion: '1.6.5', + matterbridgeVersion: '1.6.6', removeAllBridgedDevices: jest.fn(), } as unknown as Matterbridge; mockLog = { fatal: jest.fn(), error: jest.fn(), warn: jest.fn(), notice: jest.fn(), info: jest.fn(), debug: jest.fn() } as unknown as AnsiLogger; @@ -43,11 +44,14 @@ describe('initializePlugin', () => { it('should return an instance of ShellyPlatform', () => { platform = initializePlugin(mockMatterbridge, mockLog, mockConfig); + expect(platform).toBeDefined(); expect(platform).toBeInstanceOf(ShellyPlatform); }); it('should shutdown the instance of ShellyPlatform', async () => { - await platform.onShutdown(); + expect(platform).toBeDefined(); expect(platform).toBeInstanceOf(ShellyPlatform); + await platform.onShutdown(); + wait(1000); }); }); diff --git a/src/platform.test.ts b/src/platform.test.ts index c937a17..1e6f384 100644 --- a/src/platform.test.ts +++ b/src/platform.test.ts @@ -105,7 +105,7 @@ describe('ShellyPlatform', () => { matterbridgeDirectory: 'temp', matterbridgePluginDirectory: 'temp', systemInformation: { ipv4Address: undefined }, - matterbridgeVersion: '1.6.5', + matterbridgeVersion: '1.6.6', addBridgedDevice: jest.fn(), removeBridgedDevice: jest.fn(), removeAllBridgedDevices: jest.fn(), @@ -306,56 +306,6 @@ describe('ShellyPlatform', () => { expect(isValidUndefined([1, 4, 'string'])).toBe(false); }); - it('should return false and log a warning if entity is not in the whitelist', () => { - (shellyPlatform as any).whiteList = ['entity1', 'entity2']; - (shellyPlatform as any).blackList = []; - - const result = (shellyPlatform as any).validateWhiteBlackList('entity3'); - - expect(result).toBe(false); - expect(mockLog.warn).toHaveBeenCalledWith(`Skipping ${dn}entity3${wr} because not in whitelist`); - }); - - it('should return false and log a warning if entity is in the blacklist', () => { - (shellyPlatform as any).whiteList = []; - (shellyPlatform as any).blackList = ['entity3']; - - const result = (shellyPlatform as any).validateWhiteBlackList('entity3'); - - expect(result).toBe(false); - expect(mockLog.warn).toHaveBeenCalledWith(`Skipping ${dn}entity3${wr} because in blacklist`); - }); - - it('should return true if entity is in the whitelist', () => { - (shellyPlatform as any).whiteList = ['entity3']; - (shellyPlatform as any).blackList = []; - - const result = (shellyPlatform as any).validateWhiteBlackList('entity3'); - - expect(result).toBe(true); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - it('should return true if entity is not in the blacklist and whitelist is empty', () => { - (shellyPlatform as any).whiteList = []; - (shellyPlatform as any).blackList = []; - - const result = (shellyPlatform as any).validateWhiteBlackList('entity3'); - - expect(result).toBe(true); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - it('should return true if both whitelist and blacklist are empty', () => { - (shellyPlatform as any).whiteList = []; - (shellyPlatform as any).blackList = []; - - const result = (shellyPlatform as any).validateWhiteBlackList('entity3'); - - expect(result).toBe(true); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - it('should validate version', () => { mockMatterbridge.matterbridgeVersion = '1.5.4'; expect(shellyPlatform.verifyMatterbridgeVersion('1.5.3')).toBe(true); @@ -374,7 +324,7 @@ describe('ShellyPlatform', () => { it('should throw because of version', () => { mockMatterbridge.matterbridgeVersion = '1.5.4'; expect(() => new ShellyPlatform(mockMatterbridge, mockLog, mockConfig)).toThrow(); - mockMatterbridge.matterbridgeVersion = '1.6.0'; + mockMatterbridge.matterbridgeVersion = '1.6.6'; }); it('should call onStart with reason and start mDNS', async () => { diff --git a/src/platform.ts b/src/platform.ts index 0bfc26c..1ef2d4f 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -123,8 +123,6 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Config private username = ''; private password = ''; - private whiteList: string[] = []; - private blackList: string[] = []; private postfix; private failsafeCount; @@ -140,16 +138,14 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { super(matterbridge, log, config); // Verify that Matterbridge is the correct version - if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('1.6.5')) { + if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('1.6.6')) { throw new Error( - `This plugin requires Matterbridge version >= "1.6.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend."`, + `This plugin requires Matterbridge version >= "1.6.6". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend."`, ); } if (config.username) this.username = config.username as string; if (config.password) this.password = config.password as string; - if (config.whiteList) this.whiteList = config.whiteList as string[]; - if (config.blackList) this.blackList = config.blackList as string[]; this.postfix = (config.postfix as string) ?? ''; if (!isValidString(this.postfix, 0, 3)) this.postfix = ''; this.failsafeCount = (config.failsafeCount as number) ?? 0; @@ -1342,6 +1338,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } override async onConfigure() { + // Create the list of cluster servers // const clusterMap = new Map(); this.log.info(`Configuring platform ${idn}${this.config.name}${rs}${nf}`); this.bridgedDevices.forEach(async (mbDevice) => { @@ -1361,6 +1358,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.debug(`Configuring device ${dn}${mbDevice.deviceName}${db} configUrl ${YELLOW}${mbDevice.configUrl}${db}`); /* + // Create the list of cluster servers mbDevice.getAllClusterServers().forEach((clusterServer) => { clusterMap.set(clusterServer.id, clusterServer.name); console.log(`Device ${mbDevice.deviceName} cluster:`, clusterServer.id, clusterServer.name); @@ -1368,6 +1366,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { */ for (const childEndpoint of mbDevice.getChildEndpoints()) { /* + // Create the list of cluster servers childEndpoint.getAllClusterServers().forEach((clusterServer) => { clusterMap.set(clusterServer.id, clusterServer.name); console.log(`Device ${mbDevice.deviceName} child ${childEndpoint.uniqueStorageKey} cluster:`, clusterServer.id, clusterServer.name); @@ -1484,6 +1483,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { }); /* + // Create the list of cluster servers // eslint-disable-next-line @typescript-eslint/no-explicit-any (this.matterbridge as any).commissioningServer ?.getRootEndpoint() From 6487b78abfe8f553c23c938d8feb0080ce50a710 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 21:55:27 +0100 Subject: [PATCH 44/49] Dev 1.1.0-dev.10 --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec95e04..60f3b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,21 +10,21 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva ### Added -- [package]: Requires matterbridge 1.6.6. - [package]: Verified to work with matterbridge edge (matter.js new API). +- [package]: Requires matterbridge 1.6.6. - [shelly]: Remove from discovered devices the device with mongoose firmware. We ignore them. -- [shelly]: Add shellyIdentifyCommandHandler +- [shelly]: Add shellyIdentifyCommandHandler. - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. - [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. -- [matter]: Add tagList to the descriptor cluster +- [matter]: Add tagList to the descriptor cluster. ### Changed -- [shelly]: Refactor shellySwitchCommandHandler -- [shelly]: Refactor shellyLightCommandHandler -- [shelly]: Refactor shellyCoverCommandHandler +- [shelly]: Refactor shellySwitchCommandHandler. +- [shelly]: Refactor shellyLightCommandHandler. +- [shelly]: Refactor shellyCoverCommandHandler. - [colorControl]: Refactor configuration of the cluster. -- [electricalSensor]: Refactor electrical measurements initial configuration +- [electricalSensor]: Refactor electrical measurements initial configuration. - [matter]: Update all device types. - [package]: Updated dependencies. From b12ca3842f449470cd40e78cf94952aa52891c37 Mon Sep 17 00:00:00 2001 From: Luligu Date: Wed, 11 Dec 2024 21:57:42 +0100 Subject: [PATCH 45/49] Dev 1.1.0-dev.10 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a81e44e..062e2d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.9", + "version": "1.1.0-dev.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.9", + "version": "1.1.0-dev.10", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", diff --git a/package.json b/package.json index d6c9b2f..2791578 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "updateDependencies": "npx npm-check-updates -u && npm install && npm link matterbridge && npm run build", "prepublishOnly": "npm pkg delete devDependencies && npm pkg delete scripts && npm install --omit=dev && npm shrinkwrap", "npmPack": "copy package.json package.log && npm run buildProduction && npm pkg delete devDependencies && npm pkg delete scripts && npx rimraf ./node_modules && npm install --omit=dev && npm shrinkwrap && npm pack && copy package.log package.json && npm run deepCleanBuild && npm run deepCleanBuild", - "npmPublishTagDev": "copy package.json package.log && npm run buildProduction && npm pkg delete devDependencies && npm pkg delete scripts && npx rimraf ./node_modules && npm install --omit=dev && npm shrinkwrap && npm publish --tag dev && copy package.log package.json && npm run deepCleanBuild", + "npmPublishTagDev": "copy package.json package.log && npm run buildProduction && npm pkg delete devDependencies && npm pkg delete scripts && npm pkg delete types && npx rimraf ./node_modules && npm install --omit=dev && npm shrinkwrap && npm publish --tag dev && copy package.log package.json && npm run deepCleanBuild", "npmPublishTagLatest": "copy package.json package.log && npm run buildProduction && npm pkg delete devDependencies && npm pkg delete scripts && npx rimraf ./node_modules && npm install --omit=dev && npm shrinkwrap && npm publish --tag latest && copy package.log package.json && npm run deepCleanBuild", "matterbridge:add": "matterbridge -add .\\", "matterbridge:remove": "matterbridge -remove .\\", From 77aebc31fa97a17b1c3bbce3af02335fb75ce23a Mon Sep 17 00:00:00 2001 From: Luligu Date: Thu, 12 Dec 2024 13:36:58 +0100 Subject: [PATCH 46/49] Add entity blackList to BLU devices --- src/platform.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 1ef2d4f..ef27360 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -285,13 +285,16 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Contact', [contactSensor], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); + if (this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Motion') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Motion', [occupancySensor], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); + if (this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) + mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); + if (this._validateEntityBlackList(bthomeDevice.addr, 'Button')) + mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Button1') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.createDefaultSwitchClusterServer(); @@ -300,7 +303,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [temperatureSensor], [], undefined, config.debug as boolean); mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [humiditySensor], [], undefined, config.debug as boolean); - mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); + if (this._validateEntityBlackList(bthomeDevice.addr, 'Button')) + mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU RC Button 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Input'); @@ -417,7 +421,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const child = blu.getChildEndpointByName('Humidity'); blu.setAttribute(RelativeHumidityMeasurementCluster.id, 'measuredValue', value * 100, blu.log, child); } - if (blu && sensorName === 'Illuminance' && isValidNumber(value, 0, 10000)) { + if (blu && sensorName === 'Illuminance' && isValidNumber(value, 0, 10000) && this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) { const child = blu.getChildEndpointByName('Illuminance'); const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(value), 0xfffe), 0)); blu.setAttribute(IlluminanceMeasurementCluster.id, 'measuredValue', matterLux, blu.log, child); @@ -490,7 +494,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else { child = blu.getChildEndpointByName('Button'); } - if (sensorName === 'Button' && isValidString(event, 9)) { + if (sensorName === 'Button' && isValidString(event, 9) && this._validateEntityBlackList(bthomeDevice.addr, 'Button')) { if (event === 'single_push') { blu.triggerSwitchEvent('Single', blu.log, child); } From 1c2c9784b2c3d9cd69222f2e0bfd96adc1b16687 Mon Sep 17 00:00:00 2001 From: Luligu Date: Thu, 12 Dec 2024 13:38:42 +0100 Subject: [PATCH 47/49] Dev 1.1.0-dev.11 --- CHANGELOG.md | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f3b50..56ab92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his inva - [shelly]: Remove from discovered devices the device with mongoose firmware. We ignore them. - [shelly]: Add shellyIdentifyCommandHandler. - [BLU]: Add validate against white and black list for BLU devices. If you put a shelly BLU in the white list, you need to put also his BLU gateway on the white list. -- [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base. +- [BLU]: Add global and single device component black list. You can now exclude a shelly component globally or on a device base (available also for BLU devices). - [matter]: Add tagList to the descriptor cluster. ### Changed diff --git a/package-lock.json b/package-lock.json index 062e2d6..f42a113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.10", + "version": "1.1.0-dev.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.10", + "version": "1.1.0-dev.11", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", @@ -2531,9 +2531,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001687", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", - "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", "dev": true, "funding": [ { @@ -2846,9 +2846,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.72", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz", - "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==", + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", "dev": true, "license": "ISC" }, diff --git a/package.json b/package.json index 2791578..380054d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.10", + "version": "1.1.0-dev.11", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", From dbad71dc2d0f3e1d02db8b407c1ee6eb57207056 Mon Sep 17 00:00:00 2001 From: Luligu Date: Thu, 12 Dec 2024 14:26:14 +0100 Subject: [PATCH 48/49] Require Matterbridge 1.6.6 --- CHANGELOG.md | 2 +- README.md | 4 ++- src/platform.ts | 85 ++++++++----------------------------------------- 3 files changed, 18 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ab92c..0cca16d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.10] - 2024-12-12 +## [1.1.0-dev.11] - 2024-12-12 ### Added diff --git a/README.md b/README.md index 6598690..850da1c 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,9 @@ The components in the list will not be exposed for all devices. Use the componen ### deviceEntityBlackList -List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device. +List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8 for wifi shelly devices or 7c:c6:b6:65:2d:87 for BLU shelly devices) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device. +Exammples: +- if you want the BLU motion to only have the motion component, add its device addr and in the list Illuminance and Button; ### nocacheList diff --git a/src/platform.ts b/src/platform.ts index ef27360..53b4b31 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -210,7 +210,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.storedDevices.set(discoveredDevice.id, discoveredDevice); await this.saveStoredDevices(); } - if (this._validateDeviceWhiteBlackList(discoveredDevice.id)) { + if (this.validateDeviceWhiteBlackList(discoveredDevice.id)) { await this.addDevice(discoveredDevice.id, discoveredDevice.host); } }); @@ -251,7 +251,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { this.log.info(`Shelly device ${hk}${device.id}${nf} host ${zb}${device.host}${nf} is a ble gateway. Adding paired BLU devices...`); // Register the BLU devices for (const [key, bthomeDevice] of device.bthomeDevices) { - if (!this._validateDeviceWhiteBlackList(bthomeDevice.addr)) continue; + if (!this.validateDeviceWhiteBlackList(bthomeDevice.addr)) continue; this.log.info( `- ${idn}${bthomeDevice.name}${rs}${nf} address ${CYAN}${bthomeDevice.addr}${nf} id ${CYAN}${bthomeDevice.id}${nf} ` + `model ${CYAN}${bthomeDevice.model}${nf} (${CYAN}${bthomeDevice.type}${nf})`, @@ -285,15 +285,15 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Contact', [contactSensor], [], undefined, config.debug as boolean); - if (this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) + if (this.validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Motion') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Motion', [occupancySensor], [], undefined, config.debug as boolean); - if (this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) + if (this.validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) mbDevice.addChildDeviceTypeWithClusterServer('Illuminance', [lightSensor], [], undefined, config.debug as boolean); - if (this._validateEntityBlackList(bthomeDevice.addr, 'Button')) + if (this.validateEntityBlackList(bthomeDevice.addr, 'Button')) mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU Button1') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); @@ -303,7 +303,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { mbDevice.addFixedLabel('composed', 'Sensor'); mbDevice.addChildDeviceTypeWithClusterServer('Temperature', [temperatureSensor], [], undefined, config.debug as boolean); mbDevice.addChildDeviceTypeWithClusterServer('Humidity', [humiditySensor], [], undefined, config.debug as boolean); - if (this._validateEntityBlackList(bthomeDevice.addr, 'Button')) + if (this.validateEntityBlackList(bthomeDevice.addr, 'Button')) mbDevice.addChildDeviceTypeWithClusterServer('Button', [genericSwitch], [], undefined, config.debug as boolean); } else if (bthomeDevice.model === 'Shelly BLU RC Button 4') { mbDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(); @@ -381,7 +381,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidNumber(rssi, -100, 0) || !isValidNumber(packet_id, 0) || !isValidNumber(last_updated_ts)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; + if (bthomeDevice && !this.validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -395,7 +395,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; + if (bthomeDevice && !this.validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -421,7 +421,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { const child = blu.getChildEndpointByName('Humidity'); blu.setAttribute(RelativeHumidityMeasurementCluster.id, 'measuredValue', value * 100, blu.log, child); } - if (blu && sensorName === 'Illuminance' && isValidNumber(value, 0, 10000) && this._validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) { + if (blu && sensorName === 'Illuminance' && isValidNumber(value, 0, 10000) && this.validateEntityBlackList(bthomeDevice.addr, 'Illuminance')) { const child = blu.getChildEndpointByName('Illuminance'); const matterLux = Math.round(Math.max(Math.min(10000 * Math.log10(value), 0xfffe), 0)); blu.setAttribute(IlluminanceMeasurementCluster.id, 'measuredValue', matterLux, blu.log, child); @@ -456,7 +456,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; + if (bthomeDevice && !this.validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -478,7 +478,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { if (!isValidString(addr, 11) || !isValidString(sensorName, 6) || !isValidNumber(sensorIndex, 0, 3) || !isValidString(event, 6)) return; const blu = this.bluBridgedDevices.get(addr); const bthomeDevice = device.bthomeDevices.get(addr); - if (bthomeDevice && !this._validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; + if (bthomeDevice && !this.validateDeviceWhiteBlackList(bthomeDevice.addr, false)) return; if (!blu || !bthomeDevice) { this.log.error(`Shelly device ${hk}${device.id}${er} host ${zb}${device.host}${er} sent an unknown BLU device address ${CYAN}${addr}${er}`); return; @@ -494,7 +494,7 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { } else { child = blu.getChildEndpointByName('Button'); } - if (sensorName === 'Button' && isValidString(event, 9) && this._validateEntityBlackList(bthomeDevice.addr, 'Button')) { + if (sensorName === 'Button' && isValidString(event, 9) && this.validateEntityBlackList(bthomeDevice.addr, 'Button')) { if (event === 'single_push') { blu.triggerSwitchEvent('Single', blu.log, child); } @@ -577,8 +577,8 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { // Scan the device components for (const [key, component] of device) { // Validate the component against the component black list - if (!this._validateEntityBlackList(device.id, component.name)) continue; - if (!this._validateEntityBlackList(device.id, key)) continue; + if (!this.validateEntityBlackList(device.id, component.name)) continue; + if (!this.validateEntityBlackList(device.id, key)) continue; if (component.name === 'Sys') { // Add update handler from Shelly @@ -1630,61 +1630,4 @@ export class ShellyPlatform extends MatterbridgeDynamicPlatform { await this.shelly.addDevice(device); this.shellyDevices.set(device.id, device); } - - // TODO: remove when matterbridge 1.6.6 is published - /** - * Validates if a device is allowed based on the whitelist and blacklist configurations. - * The blacklist has priority over the whitelist. - * - * @param {string | string[]} device - The device name(s) to validate. - * @param {boolean} [log=true] - Whether to log the validation result. - * @returns {boolean} - Returns true if the device is allowed, false otherwise. - */ - _validateDeviceWhiteBlackList(device: string | string[], log = true): boolean { - if (!Array.isArray(device)) device = [device]; - - let blackListBlocked = 0; - if (isValidArray(this.config.blackList, 1)) { - for (const d of device) if (this.config.blackList.includes(d)) blackListBlocked++; - } - if (blackListBlocked > 0) { - if (log) this.log.info(`Skipping device ${CYAN}${device.join(', ')}${nf} because in blacklist`); - return false; - } - - let whiteListPassed = 0; - if (isValidArray(this.config.whiteList, 1)) { - for (const d of device) if (this.config.whiteList.includes(d)) whiteListPassed++; - } else whiteListPassed++; - if (whiteListPassed > 0) { - if (log) this.log.info(`Skipping device ${CYAN}${device.join(', ')}${nf} because not in whitelist`); - return true; - } - return false; - } - - // TODO: remove when matterbridge 1.6.6 is published - /** - * Validates if an entity is allowed based on the entity blacklist and device-entity blacklist configurations. - * - * @param {string} device - The device to which the entity belongs. - * @param {string} entity - The entity to validate. - * @param {boolean} [log=true] - Whether to log the validation result. - * @returns {boolean} - Returns true if the entity is allowed, false otherwise. - */ - _validateEntityBlackList(device: string, entity: string, log = true): boolean { - if (isValidArray(this.config.entityBlackList, 1) && this.config.entityBlackList.find((e) => e === entity)) { - if (log) this.log.info(`Skipping entity ${CYAN}${entity}${nf} because in entityBlackList`); - return false; - } - if ( - isValidObject(this.config.deviceEntityBlackList, 1) && - device in this.config.deviceEntityBlackList && - (this.config.deviceEntityBlackList as Record)[device].includes(entity) - ) { - if (log) this.log.info(`Skipping entity ${CYAN}${entity}${wr} for device ${CYAN}${device}${nf} because in deviceEntityBlackList`); - return false; - } - return true; - } } From 4aea5677f41b8e8a8e291c941e1057c09618f2f2 Mon Sep 17 00:00:00 2001 From: Luligu Date: Sat, 14 Dec 2024 22:32:17 +0100 Subject: [PATCH 49/49] Release 1.1.0 --- CHANGELOG.md | 2 +- README.md | 3 +- package-lock.json | 648 +++++----------------------------------------- package.json | 10 +- 4 files changed, 72 insertions(+), 591 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cca16d..5170731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ If you like this project and find it useful, please consider giving it a star on You can also sponsor Tamer here https://buymeacoffee.com/6sjde6vkzl for his invaluable contribution to this project. -## [1.1.0-dev.11] - 2024-12-12 +## [1.1.0] - 2024-12-14 ### Added diff --git a/README.md b/README.md index 850da1c..fd0d8bf 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ The components in the list will not be exposed for all devices. Use the componen List of components not to be exposed for a single device. Enter in the first field the name of the device id (e.g. shellyplus2pm-5443B23D81F8 for wifi shelly devices or 7c:c6:b6:65:2d:87 for BLU shelly devices) and in the second field add all the component names (i.e. Temperature) or component ids (i.e. temperature:0) you want to exclude for that device. Exammples: + - if you want the BLU motion to only have the motion component, add its device addr and in the list Illuminance and Button; ### nocacheList @@ -210,7 +211,7 @@ The devices in the list will not be loaded from the cache. Use the device id (e. ### deviceIp -You can put there one of more of your devices if they have problem with mdns (don't use it unless is needed). +You can put there one of more of your devices if they have problem with mdns (don't use it unless is needed cause the IP address is fixed). E.g. "shelly1minig3-543204547478": "192.168.1.221" ### enableMdnsDiscover diff --git a/package-lock.json b/package-lock.json index f42a113..f0ad234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.11", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge-shelly", - "version": "1.1.0-dev.11", + "version": "1.1.0", "license": "Apache-2.0", "dependencies": { "coap": "1.4.0", @@ -18,13 +18,13 @@ "ws": "8.18.0" }, "devDependencies": { - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@types/eslint__js": "8.42.3", "@types/jest": "29.5.14", "@types/multicast-dns": "7.2.4", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/ws": "8.5.13", - "eslint": "9.16.0", + "eslint": "9.17.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "28.9.0", "eslint-plugin-prettier": "5.2.1", @@ -32,7 +32,7 @@ "prettier": "3.4.2", "ts-jest": "29.2.5", "typescript": "5.7.2", - "typescript-eslint": "8.17.0" + "typescript-eslint": "8.18.0" }, "engines": { "node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0 || >=22.0.0 <23.0.0" @@ -677,9 +677,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -1471,9 +1471,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -1524,17 +1524,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz", + "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/type-utils": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1549,158 +1549,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz", + "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MITClause", "dependencies": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "debug": "^4.3.4" }, "engines": { @@ -1711,117 +1574,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { @@ -1843,79 +1597,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz", + "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", - "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/utils": "8.18.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { @@ -1925,82 +1615,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { @@ -2424,9 +2041,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -2444,9 +2061,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -2906,9 +2523,9 @@ } }, "node_modules/eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2917,7 +2534,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2926,7 +2543,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3707,9 +3324,9 @@ "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "dev": true, "license": "MIT", "dependencies": { @@ -5280,13 +4897,13 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5800,104 +5417,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", - "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", - "@typescript-eslint/utils": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz", + "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/eslint-plugin": "8.18.0", + "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/utils": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5907,56 +5435,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.17.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 380054d..50c0fb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge-shelly", - "version": "1.1.0-dev.11", + "version": "1.1.0", "description": "Matterbridge shelly plugin", "author": "https://github.com/Luligu", "license": "Apache-2.0", @@ -112,13 +112,13 @@ "ws": "8.18.0" }, "devDependencies": { - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@types/eslint__js": "8.42.3", "@types/jest": "29.5.14", "@types/multicast-dns": "7.2.4", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/ws": "8.5.13", - "eslint": "9.16.0", + "eslint": "9.17.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "28.9.0", "eslint-plugin-prettier": "5.2.1", @@ -126,6 +126,6 @@ "prettier": "3.4.2", "ts-jest": "29.2.5", "typescript": "5.7.2", - "typescript-eslint": "8.17.0" + "typescript-eslint": "8.18.0" } } \ No newline at end of file