diff --git a/.github/workflows/docker-buildx-main.yml b/.github/workflows/docker-buildx-main.yml index 0e36dc2c..0107ef25 100644 --- a/.github/workflows/docker-buildx-main.yml +++ b/.github/workflows/docker-buildx-main.yml @@ -39,7 +39,7 @@ jobs: --platform linux/amd64,linux/arm64,linux/arm/v7 \ -f docker/Dockerfile.main \ -t luligu/matterbridge:latest \ - -t luligu/matterbridge:1.3.8 \ + -t luligu/matterbridge:1.3.9 \ --push . docker manifest inspect luligu/matterbridge:latest timeout-minutes: 60 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9249cc..cd19c7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. If you like this project and find it useful, please consider giving it a star on GitHub at https://github.com/Luligu/matterbridge and sponsoring it. +## [1.3.9] - 2024-07-02 + +### Fixed +- [matterbridge]: Fixed nodeLabel in childbridge mode +- [matterbridge]: Fixed MeasurementClusters + + + Buy me a coffee + + ## [1.3.8] - 2024-07-01 ### Fixed diff --git a/package-lock.json b/package-lock.json index cd192b3d..e8efd70f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "matterbridge", - "version": "1.3.7", + "version": "1.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "matterbridge", - "version": "1.3.7", + "version": "1.3.8", "license": "Apache-2.0", "dependencies": { "@project-chip/matter-node.js": "^0.9.3", diff --git a/package.json b/package.json index 1f9392d0..a2e54480 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matterbridge", - "version": "1.3.8", + "version": "1.3.9", "description": "Matterbridge plugin manager for Matter", "author": "https://github.com/Luligu", "license": "Apache-2.0", diff --git a/src/cluster/ElectricalPowerMeasurementCluster.ts b/src/cluster/ElectricalPowerMeasurementCluster.ts index cbfbd258..efbcf2eb 100644 --- a/src/cluster/ElectricalPowerMeasurementCluster.ts +++ b/src/cluster/ElectricalPowerMeasurementCluster.ts @@ -479,7 +479,7 @@ export namespace ElectricalPowerMeasurement { * * @see {@link MatterSpecification.v13.Cluster} § 2.13.6.19 */ - neutralCurrent: OptionalAttribute(0x12, TlvNullable(TlvInt64.bound({ min: -262, max: 262 })), { default: null }), + neutralCurrent: OptionalAttribute(0x12, TlvNullable(TlvInt64.bound({ min: -(2 ** 62), max: 2 ** 62 })), { default: null }), }, }); @@ -649,7 +649,7 @@ export namespace ElectricalPowerMeasurement { * * @see {@link MatterSpecification.v13.Cluster} § 2.13.6.5 */ - voltage: OptionalAttribute(0x4, TlvNullable(TlvInt64.bound({ min: -262, max: 262 })), { default: null }), + voltage: OptionalAttribute(0x4, TlvNullable(TlvInt64.bound({ min: -(2 ** 62), max: 2 ** 62 })), { default: null }), /** * This shall indicate the most recent ActiveCurrent reading in milliamps (mA). @@ -670,7 +670,7 @@ export namespace ElectricalPowerMeasurement { * * @see {@link MatterSpecification.v13.Cluster} § 2.13.6.6 */ - activeCurrent: OptionalAttribute(0x5, TlvNullable(TlvInt64.bound({ min: -262, max: 262 })), { default: null }), + activeCurrent: OptionalAttribute(0x5, TlvNullable(TlvInt64.bound({ min: -(2 ** 62), max: 2 ** 62 })), { default: null }), /** * This shall indicate the most recent ActivePower reading in milliwatts (mW). If the power cannot be @@ -692,7 +692,7 @@ export namespace ElectricalPowerMeasurement { * * @see {@link MatterSpecification.v13.Cluster} § 2.13.6.9 */ - activePower: Attribute(0x8, TlvNullable(TlvInt64.bound({ min: -262, max: 262 })), { default: null }), + activePower: Attribute(0x8, TlvNullable(TlvInt64.bound({ min: -(2 ** 62), max: 2 ** 62 })), { default: null }), }, events: { diff --git a/src/matterbridge.ts b/src/matterbridge.ts index 531f7ecc..98533d44 100644 --- a/src/matterbridge.ts +++ b/src/matterbridge.ts @@ -192,6 +192,8 @@ export class Matterbridge extends EventEmitter { private mdnsInterface: string | undefined; // matter server mdnsInterface: 'eth0' or 'wlan0' or 'WiFi' private port = 5540; // first commissioning server port + private passcode?: number; // first commissioning server passcode + private discriminator?: number; // first commissioning server discriminator private log!: AnsiLogger; private hasCleanupStarted = false; // private plugins = new Map(); @@ -395,6 +397,10 @@ export class Matterbridge extends EventEmitter { // Set the first port to use for the commissioning server this.port = getIntParameter('port') ?? 5540; + // Set the first passcode to use for the commissioning server + this.passcode = getIntParameter('passcode'); + // Set the first discriminator to use for the commissioning server + this.discriminator = getIntParameter('discriminator'); // Set the restart mode if (hasParameter('service')) this.restartMode = 'service'; @@ -1147,7 +1153,8 @@ export class Matterbridge extends EventEmitter { if (!plugin.locked) { plugin.locked = true; this.log.debug(`Creating commissioning server context for ${plg}${plugin.name}${db}`); - plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform'); + // plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dynamic Platform'); + plugin.storageContext = await this.createCommissioningServerContext(plugin.name, 'Matterbridge', DeviceTypes.AGGREGATOR.code, 0xfff1, 'Matterbridge', 0x8000, plugin.description); this.log.debug(`Creating commissioning server for ${plg}${plugin.name}${db}`); plugin.commissioningServer = await this.createCommisioningServer(plugin.storageContext, plugin.name); this.log.debug(`Creating aggregator for plugin ${plg}${plugin.name}${db}`); @@ -1986,6 +1993,7 @@ export class Matterbridge extends EventEmitter { this.log.error('importCommissioningServerContext error: no storage manager initialized'); process.exit(1); } + this.log.debug(`Importing commissioning server storage context for ${plg}${pluginName}${db}`); const storageContext = this.storageManager.createContext(pluginName); await storageContext.set('deviceName', basic.getNodeLabelAttribute()); @@ -2233,16 +2241,17 @@ export class Matterbridge extends EventEmitter { const hardwareVersion = await context.get('hardwareVersion', 1); const hardwareVersionString = await context.get('hardwareVersionString', '1.0.0'); - this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName ${deviceName} deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`); + this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with deviceName '${deviceName}' deviceType ${deviceType}(0x${deviceType.toString(16).padStart(4, '0')})`); this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with uniqueId ${uniqueId} serialNumber ${serialNumber}`); this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with softwareVersion ${softwareVersion} softwareVersionString ${softwareVersionString}`); this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with hardwareVersion ${hardwareVersion} hardwareVersionString ${hardwareVersionString}`); + this.log.debug(`Creating matter commissioning server for plugin ${plg}${pluginName}${db} with nodeLabel '${productName}' port ${this.port} passcode ${this.passcode} discriminator ${this.discriminator}`); const commissioningServer = new CommissioningServer({ port: this.port++, // listeningAddressIpv4 // listeningAddressIpv6 - passcode: undefined, - discriminator: undefined, + passcode: this.passcode, + discriminator: this.discriminator, deviceName, deviceType, basicInformation: { @@ -2341,6 +2350,9 @@ export class Matterbridge extends EventEmitter { } }, }); + if (this.passcode !== undefined) this.passcode++; + if (this.discriminator !== undefined) this.discriminator++; + commissioningServer.addCommandHandler('testEventTrigger', async ({ request: { enableKey, eventTrigger } }) => this.log.info(`testEventTrigger called on GeneralDiagnostic cluster: ${enableKey} ${eventTrigger}`)); return commissioningServer; } diff --git a/src/matterbridgeDeviceV8.ts b/src/matterbridgeDeviceV8.ts deleted file mode 100644 index 56606b56..00000000 --- a/src/matterbridgeDeviceV8.ts +++ /dev/null @@ -1,2253 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -// New API imports -import { Endpoint, EndpointServer } from '@project-chip/matter.js/endpoint'; -import { OnOffLightDevice } from '@project-chip/matter.js/devices/OnOffLightDevice'; -import { AggregatorEndpoint } from '@project-chip/matter.js/endpoints/AggregatorEndpoint'; -import { BridgedNodeEndpoint } from '@project-chip/matter.js/endpoints/BridgedNodeEndpoint'; -import { MutableEndpoint, EndpointType } from '@project-chip/matter.js/endpoint/type'; -import { Behavior } from '@project-chip/matter.js/behavior'; -import { SupportedBehaviors } from '@project-chip/matter.js/endpoint/properties'; -import { IdentifyServer, IdentifyBehavior } from '@project-chip/matter.js/behavior/definitions/identify'; -import { GroupsServer, GroupsBehavior } from '@project-chip/matter.js/behavior/definitions/groups'; -import { ScenesServer, ScenesBehavior } from '@project-chip/matter.js/behavior/definitions/scenes'; -import { OnOffServer, OnOffBehavior } from '@project-chip/matter.js/behavior/definitions/on-off'; -import { TemperatureMeasurementServer } from '@project-chip/matter.js/behavior/definitions/temperature-measurement'; -import { RelativeHumidityMeasurementServer } from '@project-chip/matter.js/behavior/definitions/relative-humidity-measurement'; -import { BridgedDeviceBasicInformationServer, BridgedDeviceBasicInformationBehavior } from '@project-chip/matter.js/behavior/definitions/bridged-device-basic-information'; - -// Old API imports -import { DeviceTypeDefinition, DeviceTypes, EndpointOptions } from '@project-chip/matter-node.js/device'; -import { - AttributeServer, - Attributes, - BasicInformationCluster, - BooleanState, - BooleanStateCluster, - BridgedDeviceBasicInformation, - BridgedDeviceBasicInformationCluster, - Cluster, - ClusterClientObj, - ClusterServer, - ClusterServerObj, - ColorControl, - ColorControlCluster, - Commands, - DoorLock, - DoorLockCluster, - ElectricalMeasurement, - ElectricalMeasurementCluster, - Events, - FanControl, - FanControlCluster, - FlowMeasurement, - FlowMeasurementCluster, - Groups, - Identify, - IdentifyCluster, - IlluminanceMeasurement, - IlluminanceMeasurementCluster, - LevelControl, - LevelControlCluster, - OccupancySensing, - OccupancySensingCluster, - OnOff, - OnOffCluster, - PowerSource, - PowerSourceCluster, - PowerSourceConfigurationCluster, - PressureMeasurement, - PressureMeasurementCluster, - RelativeHumidityMeasurement, - RelativeHumidityMeasurementCluster, - Scenes, - Switch, - SwitchCluster, - TemperatureMeasurement, - TemperatureMeasurementCluster, - Thermostat, - ThermostatCluster, - ThreadNetworkDiagnostics, - ThreadNetworkDiagnosticsCluster, - TimeSync, - TimeSyncCluster, - WindowCovering, - WindowCoveringCluster, - createDefaultGroupsClusterServer, - createDefaultScenesClusterServer, - getClusterNameById, -} from '@project-chip/matter-node.js/cluster'; -import { AtLeastOne } from '@project-chip/matter-node.js/util'; -import { ClusterId, EndpointNumber, VendorId } from '@project-chip/matter-node.js/datatype'; - -// Matterbridge imports -import { AnsiLogger, CYAN, TimestampFormat, db, hk, zb } from 'node-ansi-logger'; -import { BooleanStateConfiguration, BooleanStateConfigurationCluster } from './cluster/BooleanStateConfigurationCluster.js'; -import { PowerTopology, PowerTopologyCluster } from './cluster/PowerTopologyCluster.js'; -import { ElectricalPowerMeasurement, ElectricalPowerMeasurementCluster } from './cluster/ElectricalPowerMeasurementCluster.js'; -import { ElectricalEnergyMeasurement, ElectricalEnergyMeasurementCluster } from './cluster/ElectricalEnergyMeasurementCluster.js'; -import { SmokeCoAlarm, SmokeCoAlarmCluster } from './cluster/SmokeCoAlarmCluster.js'; -import { AirQuality, AirQualityCluster } from './cluster/AirQualityCluster.js'; -import { CarbonMonoxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurementCluster } from './cluster/CarbonMonoxideConcentrationMeasurementCluster.js'; -import { CarbonDioxideConcentrationMeasurement, CarbonDioxideConcentrationMeasurementCluster } from './cluster/CarbonDioxideConcentrationMeasurementCluster.js'; -import { NitrogenDioxideConcentrationMeasurement, NitrogenDioxideConcentrationMeasurementCluster } from './cluster/NitrogenDioxideConcentrationMeasurementCluster.js'; -import { OzoneConcentrationMeasurement, OzoneConcentrationMeasurementCluster } from './cluster/OzoneConcentrationMeasurementCluster.js'; -import { FormaldehydeConcentrationMeasurement, FormaldehydeConcentrationMeasurementCluster } from './cluster/FormaldehydeConcentrationMeasurementCluster.js'; -import { Pm1ConcentrationMeasurement, Pm1ConcentrationMeasurementCluster } from './cluster/Pm1ConcentrationMeasurementCluster.js'; -import { Pm25ConcentrationMeasurement, Pm25ConcentrationMeasurementCluster } from './cluster/Pm25ConcentrationMeasurementCluster.js'; -import { Pm10ConcentrationMeasurement, Pm10ConcentrationMeasurementCluster } from './cluster/Pm10ConcentrationMeasurementCluster.js'; -import { RadonConcentrationMeasurement, RadonConcentrationMeasurementCluster } from './cluster/RadonConcentrationMeasurementCluster.js'; -import { TvocMeasurement, TvocMeasurementCluster } from './cluster/TvocCluster.js'; -import { DeviceEnergyManagement, DeviceEnergyManagementCluster } from './cluster/DeviceEnergyManagementCluster.js'; -import { DeviceEnergyManagementMode, DeviceEnergyManagementModeCluster } from './cluster/DeviceEnergyManagementModeCluster.js'; -import { EveHistory, EveHistoryCluster } from 'matter-history'; -import { createHash } from 'crypto'; -import { MeasurementType } from './cluster/MeasurementType.js'; -import { ConcentrationMeasurement } from './cluster/ConcentrationMeasurementCluster.js'; -import { BitSchema, TypeFromPartialBitSchema } from '@project-chip/matter-node.js/schema'; - -export class MatterbridgeDeviceV8 extends Endpoint { - public static bridgeMode = ''; - log: AnsiLogger; - serialNumber: string | undefined = undefined; - deviceName: string | undefined = undefined; - uniqueId: string | undefined = undefined; - - // Maps matter endpoints to endpointV8 - private readonly deviceTypes = new Map(); - private readonly clusterServers = new Map>(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readonly clusterClients = new Map>(); - - /** - * Represents a Matterbridge device. - * @constructor - * @param {DeviceTypeDefinition} definition - The definition of the device. - * @param {EndpointOptions} [options={}] - The options for the device. - */ - constructor(definition: DeviceTypeDefinition, options: EndpointOptions = {}) { - // Convert the DeviceTypeDefinition to a EndpointType.Options - const deviceTypeDefinitionV8: EndpointType.Options = { - name: definition.name.replace('-', '_'), - deviceType: definition.code, - deviceRevision: definition.revision, - deviceClass: definition.deviceClass, - requirements: { - server: { - mandatory: SupportedBehaviors(...MatterbridgeDeviceV8.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)), - optional: SupportedBehaviors(...MatterbridgeDeviceV8.getBehaviourTypesFromClusterServerIds(definition.optionalServerClusters)), - }, - client: { - mandatory: SupportedBehaviors(...MatterbridgeDeviceV8.getBehaviourTypesFromClusterClientIds(definition.requiredClientClusters)), - optional: SupportedBehaviors(...MatterbridgeDeviceV8.getBehaviourTypesFromClusterClientIds(definition.optionalClientClusters)), - }, - }, - behaviors: SupportedBehaviors(...MatterbridgeDeviceV8.getBehaviourTypesFromClusterServerIds(definition.requiredServerClusters)), - }; - const endpointV8 = MutableEndpoint(deviceTypeDefinitionV8); - const optionsV8: Endpoint.Options = { - id: options.uniqueStorageKey, - }; - super(endpointV8, optionsV8); - this.log = new AnsiLogger({ logName: 'MatterbridgeDevice', logTimestampFormat: TimestampFormat.TIME_MILLIS, logDebug: true }); - this.deviceTypes.set(definition.code, definition); - } - - static getBehaviourTypesFromClusterServerIds(clusterServerList: ClusterId[]) { - // Map ClusterId to Behavior.Type - const behaviorTypes: Behavior.Type[] = []; - clusterServerList.forEach((clusterId) => { - if (clusterId === Identify.Cluster.id) behaviorTypes.push(IdentifyServer); - if (clusterId === Groups.Cluster.id) behaviorTypes.push(GroupsServer); - if (clusterId === Scenes.Cluster.id) behaviorTypes.push(ScenesServer); - if (clusterId === OnOff.Cluster.id) behaviorTypes.push(OnOffServer); - if (clusterId === TemperatureMeasurement.Cluster.id) behaviorTypes.push(TemperatureMeasurementServer); - if (clusterId === RelativeHumidityMeasurement.Cluster.id) behaviorTypes.push(RelativeHumidityMeasurementServer); - if (clusterId === BridgedDeviceBasicInformation.Cluster.id) behaviorTypes.push(BridgedDeviceBasicInformationServer); - }); - return behaviorTypes; - } - - static getBehaviourTypesFromClusterClientIds(clusterServerList: ClusterId[]) { - // Map ClusterId to Behavior.Type - const behaviorTypes: Behavior.Type[] = []; - clusterServerList.forEach((clusterId) => { - // - }); - return behaviorTypes; - } - - /** - * Loads an instance of the MatterbridgeDevice class. - * - * @param {DeviceTypeDefinition} definition - The DeviceTypeDefinition of the device. - * @returns MatterbridgeDevice instance. - */ - static async loadInstance(definition: DeviceTypeDefinition, options: EndpointOptions = {}) { - return new MatterbridgeDeviceV8(definition, options); - } - - /** - * Adds a device type to the list of device types. - * If the device type is not already present in the list, it will be added. - * - * @param {DeviceTypeDefinition} deviceType - The device type to add. - */ - addDeviceType(deviceType: DeviceTypeDefinition) { - if (!this.deviceTypes.has(deviceType.code)) { - this.log.debug(`addDeviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`); - this.deviceTypes.set(deviceType.code, deviceType); - } - } - - /** - * Adds one or more device types with the required cluster servers and the specified cluster servers. - * - * @param {AtLeastOne} deviceTypes - The device types to add. - * @param {ClusterId[]} includeServerList - The list of cluster IDs to include. - */ - addDeviceTypeWithClusterServer(deviceTypes: AtLeastOne, includeServerList: ClusterId[]) { - this.log.debug('addDeviceTypeWithClusterServer:'); - deviceTypes.forEach((deviceType) => { - this.addDeviceType(deviceType); - this.log.debug(`- with deviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`); - deviceType.requiredServerClusters.forEach((clusterId) => { - if (!includeServerList.includes(clusterId)) includeServerList.push(clusterId); - }); - }); - includeServerList.forEach((clusterId) => { - this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); - }); - this.addClusterServerFromList(this, includeServerList); - } - - /** - * Adds a child endpoint with one or more device types with the required cluster servers and the specified cluster servers. - * If the child endpoint is not already present in the childEndpoints, it will be added. - * If the child endpoint is already present in the childEndpoints, the device types and cluster servers will be added to the existing child endpoint. - * - * @param {string} endpointName - The name of the new enpoint to add. - * @param {AtLeastOne} deviceTypes - The device types to add. - * @param {ClusterId[]} includeServerList - The list of cluster IDs to include. - * @returns {Endpoint} - The child endpoint that was found or added. - */ - addChildDeviceTypeWithClusterServer(endpointName: string, deviceTypes: AtLeastOne, includeServerList: ClusterId[]) { - /* - this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`); - let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName); - if (!child) { - child = new Endpoint(deviceTypes, { uniqueStorageKey: endpointName }); - child.addFixedLabel('endpointName', endpointName); - } - deviceTypes.forEach((deviceType) => { - this.log.debug(`- with deviceType: ${zb}${deviceType.code}${db}-${zb}${deviceType.name}${db}`); - deviceType.requiredServerClusters.forEach((clusterId) => { - if (!includeServerList.includes(clusterId)) includeServerList.push(clusterId); - }); - }); - includeServerList.forEach((clusterId) => { - this.log.debug(`- with cluster: ${hk}${clusterId}${db}-${hk}${getClusterNameById(clusterId)}${db}`); - }); - this.addClusterServerFromList(child, includeServerList); - this.addChildEndpoint(child); - return child; - */ - } - - getClusterServer, A extends Attributes, C extends Commands, E extends Events>(cluster: Cluster): ClusterServerObj | undefined { - const clusterServer = this.clusterServers.get(cluster.id); - if (clusterServer !== undefined) { - return clusterServer as unknown as ClusterServerObj; - } - } - - addClusterServer(cluster: ClusterServerObj) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const options: Record = {}; - for (const attribute of Object.values(cluster.attributes)) { - if ((attribute as AttributeServer).id < 0xfff0) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - options[(attribute as AttributeServer).name] = (attribute as any).value; - } - } - this.log.debug(`addClusterServer: ${cluster.name} with options:`, options); - const behaviorTypes = MatterbridgeDeviceV8.getBehaviourTypesFromClusterServerIds([cluster.id]); - this.behaviors.require(behaviorTypes[0], options); - this.clusterServers.set(cluster.id, cluster); - } - - /** - * Adds cluster servers to the specified endpoint based on the provided server list. - * - * @param {Endpoint} endpoint - The endpoint to add cluster servers to. - * @param {ClusterId[]} includeServerList - The list of cluster IDs to include. - * @returns void - */ - addClusterServerFromList(endpoint: MatterbridgeDeviceV8, includeServerList: ClusterId[]): void { - if (includeServerList.includes(Identify.Cluster.id)) endpoint.addClusterServer(this.getDefaultIdentifyClusterServer()); - if (includeServerList.includes(Groups.Cluster.id)) endpoint.addClusterServer(this.getDefaultGroupsClusterServer()); - if (includeServerList.includes(Scenes.Cluster.id)) endpoint.addClusterServer(this.getDefaultScenesClusterServer()); - if (includeServerList.includes(OnOff.Cluster.id)) endpoint.addClusterServer(this.getDefaultOnOffClusterServer()); - if (includeServerList.includes(LevelControl.Cluster.id)) endpoint.addClusterServer(this.getDefaultLevelControlClusterServer()); - if (includeServerList.includes(ColorControl.Cluster.id)) endpoint.addClusterServer(this.getDefaultColorControlClusterServer()); - if (includeServerList.includes(Switch.Cluster.id)) endpoint.addClusterServer(this.getDefaultSwitchClusterServer()); - if (includeServerList.includes(DoorLock.Cluster.id)) endpoint.addClusterServer(this.getDefaultDoorLockClusterServer()); - if (includeServerList.includes(Thermostat.Cluster.id)) endpoint.addClusterServer(this.getDefaultThermostatClusterServer()); - if (includeServerList.includes(TimeSync.Cluster.id)) endpoint.addClusterServer(this.getDefaultTimeSyncClusterServer()); - if (includeServerList.includes(WindowCovering.Cluster.id)) endpoint.addClusterServer(this.getDefaultWindowCoveringClusterServer()); - if (includeServerList.includes(TemperatureMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultTemperatureMeasurementClusterServer()); - if (includeServerList.includes(RelativeHumidityMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultRelativeHumidityMeasurementClusterServer()); - if (includeServerList.includes(PressureMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultPressureMeasurementClusterServer()); - if (includeServerList.includes(FlowMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultFlowMeasurementClusterServer()); - if (includeServerList.includes(BooleanState.Cluster.id)) endpoint.addClusterServer(this.getDefaultBooleanStateClusterServer()); - if (includeServerList.includes(BooleanStateConfiguration.Cluster.id)) endpoint.addClusterServer(this.getDefaultBooleanStateConfigurationClusterServer()); - if (includeServerList.includes(OccupancySensing.Cluster.id)) endpoint.addClusterServer(this.getDefaultOccupancySensingClusterServer()); - if (includeServerList.includes(IlluminanceMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultIlluminanceMeasurementClusterServer()); - if (includeServerList.includes(PowerSource.Cluster.id)) endpoint.addClusterServer(this.getDefaultPowerSourceWiredClusterServer()); - if (includeServerList.includes(EveHistory.Cluster.id)) endpoint.addClusterServer(this.getDefaultStaticEveHistoryClusterServer()); - if (includeServerList.includes(ElectricalMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultElectricalMeasurementClusterServer()); - if (includeServerList.includes(PowerTopology.Cluster.id)) endpoint.addClusterServer(this.getDefaultPowerTopologyClusterServer()); - if (includeServerList.includes(ElectricalPowerMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultElectricalPowerMeasurementClusterServer()); - if (includeServerList.includes(ElectricalEnergyMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultElectricalEnergyMeasurementClusterServer()); - if (includeServerList.includes(SmokeCoAlarm.Cluster.id)) endpoint.addClusterServer(this.getDefaultSmokeCOAlarmClusterServer()); - if (includeServerList.includes(AirQuality.Cluster.id)) endpoint.addClusterServer(this.getDefaultAirQualityClusterServer()); - if (includeServerList.includes(CarbonMonoxideConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultCarbonMonoxideConcentrationMeasurementClusterServer()); - if (includeServerList.includes(CarbonDioxideConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultCarbonDioxideConcentrationMeasurementClusterServer()); - if (includeServerList.includes(NitrogenDioxideConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultNitrogenDioxideConcentrationMeasurementClusterServer()); - if (includeServerList.includes(OzoneConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultOzoneConcentrationMeasurementClusterServer()); - if (includeServerList.includes(FormaldehydeConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultFormaldehydeConcentrationMeasurementClusterServer()); - if (includeServerList.includes(Pm1ConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultPm1ConcentrationMeasurementClusterServer()); - if (includeServerList.includes(Pm25ConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultPm25ConcentrationMeasurementClusterServer()); - if (includeServerList.includes(Pm10ConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultPm10ConcentrationMeasurementClusterServer()); - if (includeServerList.includes(RadonConcentrationMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultRadonConcentrationMeasurementClusterServer()); - if (includeServerList.includes(TvocMeasurement.Cluster.id)) endpoint.addClusterServer(this.getDefaultTvocMeasurementClusterServer()); - if (includeServerList.includes(FanControl.Cluster.id)) endpoint.addClusterServer(this.getDefaultFanControlClusterServer()); - if (includeServerList.includes(DeviceEnergyManagement.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementClusterServer()); - if (includeServerList.includes(DeviceEnergyManagementMode.Cluster.id)) endpoint.addClusterServer(this.getDefaultDeviceEnergyManagementModeClusterServer()); - } - - /** - * Returns a default static EveHistoryClusterServer object with the specified voltage, current, power, and consumption values. - * This shows up in HA as a static sensor! - * @param voltage - The voltage value (default: 0). - * @param current - The current value (default: 0). - * @param power - The power value (default: 0). - * @param consumption - The consumption value (default: 0). - * @returns The default static EveHistoryClusterServer object. - */ - getDefaultStaticEveHistoryClusterServer(voltage = 0, current = 0, power = 0, consumption = 0) { - return ClusterServer( - EveHistoryCluster.with(EveHistory.Feature.EveEnergy), - { - // Dynamic attributes - ConfigDataGet: Uint8Array.fromHex(''), - ConfigDataSet: Uint8Array.fromHex(''), - HistoryStatus: Uint8Array.fromHex(''), - HistoryEntries: Uint8Array.fromHex(''), - HistoryRequest: Uint8Array.fromHex(''), - HistorySetTime: Uint8Array.fromHex(''), - LastEvent: 0, - ResetTotal: 0, - // Normal attributes - Voltage: voltage, - Current: current, - Consumption: power, - TotalConsumption: consumption, - EnergyUnknown: 1, - ChildLock: false, - RLoc: 46080, - }, - {}, - {}, - ); - } - - /** - * Get a default IdentifyCluster server. - */ - getDefaultIdentifyClusterServer() { - return ClusterServer( - IdentifyCluster, - { - identifyTime: 0, - identifyType: Identify.IdentifyType.None, - }, - { - identify: async (data) => { - this.log.debug('Matter command: Identify'); - // await this.commandHandler.executeHandler('identify', data); - }, - }, - ); - } - - /** - * Creates a default IdentifyCluster server. - */ - createDefaultIdentifyClusterServer() { - this.addClusterServer(this.getDefaultIdentifyClusterServer()); - } - - /** - * Get a default IdentifyCluster server. - */ - getDefaultGroupsClusterServer() { - return createDefaultGroupsClusterServer(); - } - - /** - * Creates a default groups cluster server and adds it to the device. - */ - createDefaultGroupsClusterServer() { - this.addClusterServer(this.getDefaultGroupsClusterServer()); - } - - /** - * Get a default scenes cluster server and adds it to the current instance. - */ - getDefaultScenesClusterServer() { - return createDefaultScenesClusterServer(); - } - - /** - * Creates a default scenes cluster server and adds it to the current instance. - */ - createDefaultScenesClusterServer() { - this.addClusterServer(this.getDefaultScenesClusterServer()); - } - - /** - * Creates a unique identifier based on the provided parameters. - * @param param1 - The first parameter. - * @param param2 - The second parameter. - * @param param3 - The third parameter. - * @param param4 - The fourth parameter. - * @returns A unique identifier generated using the MD5 hash algorithm. - */ - private createUniqueId(param1: string, param2: string, param3: string, param4: string) { - const hash = createHash('md5'); - hash.update(param1 + param2 + param3 + param4); - return hash.digest('hex'); - } - - /** - * Get a default Basic Information Cluster Server. - * - * @param deviceName - The name of the device. - * @param serialNumber - The serial number of the device. - * @param vendorId - The vendor ID of the device. - * @param vendorName - The vendor name of the device. - * @param productId - The product ID of the device. - * @param productName - The product name of the device. - * @param softwareVersion - The software version of the device. Default is 1. - * @param softwareVersionString - The software version string of the device. Default is 'v.1.0.0'. - * @param hardwareVersion - The hardware version of the device. Default is 1. - * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'. - */ - getDefaultBasicInformationClusterServer( - deviceName: string, - serialNumber: string, - vendorId: number, - vendorName: string, - productId: number, - productName: string, - softwareVersion = 1, - softwareVersionString = '1.0.0', - hardwareVersion = 1, - hardwareVersionString = '1.0.0', - ) { - return ClusterServer( - BasicInformationCluster, - { - dataModelRevision: 1, - location: 'XX', - vendorId: VendorId(vendorId), - vendorName: vendorName.slice(0, 32), - productId: productId, - productName: productName.slice(0, 32), - productLabel: deviceName.slice(0, 64), - nodeLabel: deviceName.slice(0, 32), - serialNumber: serialNumber.slice(0, 32), - uniqueId: this.createUniqueId(deviceName, serialNumber, vendorName, productName), - softwareVersion, - softwareVersionString: softwareVersionString.slice(0, 64), - hardwareVersion, - hardwareVersionString: hardwareVersionString.slice(0, 64), - reachable: true, - capabilityMinima: { caseSessionsPerFabric: 3, subscriptionsPerFabric: 3 }, - }, - {}, - { - startUp: true, - shutDown: true, - leave: true, - reachableChanged: true, - }, - ); - } - /** - * Creates a default Basic Information Cluster Server. - * - * @param deviceName - The name of the device. - * @param serialNumber - The serial number of the device. - * @param vendorId - The vendor ID of the device. - * @param vendorName - The vendor name of the device. - * @param productId - The product ID of the device. - * @param productName - The product name of the device. - * @param softwareVersion - The software version of the device. Default is 1. - * @param softwareVersionString - The software version string of the device. Default is 'v.1.0.0'. - * @param hardwareVersion - The hardware version of the device. Default is 1. - * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'. - */ - createDefaultBasicInformationClusterServer( - deviceName: string, - serialNumber: string, - vendorId: number, - vendorName: string, - productId: number, - productName: string, - softwareVersion = 1, - softwareVersionString = '1.0.0', - hardwareVersion = 1, - hardwareVersionString = '1.0.0', - ) { - this.deviceName = deviceName; - this.serialNumber = serialNumber; - this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName); - if (MatterbridgeDeviceV8.bridgeMode === 'bridge') { - this.createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString); - return; - } - this.addClusterServer(this.getDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productId, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString)); - } - - /** - * Get a default BridgedDeviceBasicInformationClusterServer. - * - * @param deviceName - The name of the device. - * @param serialNumber - The serial number of the device. - * @param vendorId - The vendor ID of the device. - * @param vendorName - The name of the vendor. - * @param productName - The name of the product. - * @param softwareVersion - The software version of the device. Default is 1. - * @param softwareVersionString - The software version string of the device. Default is 'v.1.0.0'. - * @param hardwareVersion - The hardware version of the device. Default is 1. - * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'. - */ - getDefaultBridgedDeviceBasicInformationClusterServer( - deviceName: string, - serialNumber: string, - vendorId: number, - vendorName: string, - productName: string, - softwareVersion = 1, - softwareVersionString = '1.0.0', - hardwareVersion = 1, - hardwareVersionString = '1.0.0', - ) { - return ClusterServer( - BridgedDeviceBasicInformationCluster, - { - vendorId: vendorId !== undefined ? VendorId(vendorId) : undefined, // 4874 - vendorName: vendorName.slice(0, 32), - // productId: 0x8000, - productName: productName.slice(0, 32), - productLabel: deviceName.slice(0, 64), - nodeLabel: deviceName.slice(0, 32), - serialNumber: serialNumber.slice(0, 32), - uniqueId: this.createUniqueId(deviceName, serialNumber, vendorName, productName), - softwareVersion, - softwareVersionString: softwareVersionString.slice(0, 64), - hardwareVersion, - hardwareVersionString: hardwareVersionString.slice(0, 64), - reachable: true, - }, - {}, - { - reachableChanged: true, - }, - ); - } - - /** - * Creates a default BridgedDeviceBasicInformationClusterServer. - * - * @param deviceName - The name of the device. - * @param serialNumber - The serial number of the device. - * @param vendorId - The vendor ID of the device. - * @param vendorName - The name of the vendor. - * @param productName - The name of the product. - * @param softwareVersion - The software version of the device. Default is 1. - * @param softwareVersionString - The software version string of the device. Default is 'v.1.0.0'. - * @param hardwareVersion - The hardware version of the device. Default is 1. - * @param hardwareVersionString - The hardware version string of the device. Default is 'v.1.0.0'. - */ - createDefaultBridgedDeviceBasicInformationClusterServer( - deviceName: string, - serialNumber: string, - vendorId: number, - vendorName: string, - productName: string, - softwareVersion = 1, - softwareVersionString = '1.0.0', - hardwareVersion = 1, - hardwareVersionString = '1.0.0', - ) { - this.deviceName = deviceName; - this.serialNumber = serialNumber; - this.uniqueId = this.createUniqueId(deviceName, serialNumber, vendorName, productName); - this.addClusterServer(this.getDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber, vendorId, vendorName, productName, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString)); - } - - /** - * Get a default Electrical Energy Measurement Cluster Server. - * - * @param energy - The total consumption value. - */ - getDefaultPowerTopologyClusterServer() { - return ClusterServer(PowerTopologyCluster.with(PowerTopology.Feature.TreeTopology), {}, {}, {}); - } - - /** - * Get a default Electrical Energy Measurement Cluster Server. - * - * @param energy - The total consumption value. - */ - getDefaultElectricalEnergyMeasurementClusterServer(energy = 0) { - return ClusterServer( - ElectricalEnergyMeasurementCluster.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), - { - accuracy: { - measurementType: MeasurementType.ElectricalEnergy, - measured: true, - minMeasuredValue: 0, - maxMeasuredValue: 0, - accuracyRanges: [{ rangeMin: 0, rangeMax: 2 ** 62, fixedMin: 10, fixedMax: 10, fixedTypical: 0 }], - }, - cumulativeEnergyImported: { energy }, - cumulativeEnergyExported: null, - }, - {}, - { - cumulativeEnergyMeasured: true, - }, - ); - } - - /** - * Get a default Electrical Power Measurement Cluster Server. - * - * @param energy - The total consumption value. - */ - getDefaultElectricalPowerMeasurementClusterServer(voltage = 0, current = 0, power = 0) { - return ClusterServer( - ElectricalPowerMeasurementCluster.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), - { - powerMode: ElectricalPowerMeasurement.PowerMode.Ac, - numberOfMeasurementTypes: 3, - accuracy: [ - { - measurementType: MeasurementType.Voltage, - measured: true, - minMeasuredValue: 0, - maxMeasuredValue: 100, - accuracyRanges: [{ rangeMin: 0, rangeMax: 2 ** 62, fixedMin: 10, fixedMax: 10, fixedTypical: 0 }], - }, - { - measurementType: MeasurementType.ActiveCurrent, - measured: true, - minMeasuredValue: 0, - maxMeasuredValue: 100, - accuracyRanges: [{ rangeMin: 0, rangeMax: 2 ** 62, fixedMin: 10, fixedMax: 10, fixedTypical: 0 }], - }, - { - measurementType: MeasurementType.ActivePower, - measured: true, - minMeasuredValue: 0, - maxMeasuredValue: 100, - accuracyRanges: [{ rangeMin: 0, rangeMax: 2 ** 62, fixedMin: 10, fixedMax: 10, fixedTypical: 0 }], - }, - ], - voltage: voltage, - activeCurrent: current, - activePower: power, - }, - {}, - {}, - ); - } - - /** - * @deprecated This method is deprecated and will be removed in a future version. - * Get a default Electrical Measurement Cluster Server. - * - * @param voltage - The RMS voltage value. - * @param current - The RMS current value. - * @param power - The active power value. - * @param consumption - The total active power consumption value. - */ - getDefaultElectricalMeasurementClusterServer(voltage = 0, current = 0, power = 0, consumption = 0) { - return ClusterServer( - ElectricalMeasurementCluster, - { - rmsVoltage: voltage, - rmsCurrent: current, - activePower: power, - totalActivePower: consumption, - }, - {}, - {}, - ); - } - - /** - * @deprecated This method is deprecated and will be removed in a future version. - * Creates a default Electrical Measurement Cluster Server. - * - * @param voltage - The RMS voltage value. - * @param current - The RMS current value. - * @param power - The active power value. - * @param consumption - The total active power consumption value. - */ - createDefaultElectricalMeasurementClusterServer(voltage = 0, current = 0, power = 0, consumption = 0) { - this.addClusterServer(this.getDefaultElectricalMeasurementClusterServer(voltage, current, power, consumption)); - } - - /** - * Creates a default Dummy Thread Network Diagnostics Cluster server. - * - * @remarks - * This method adds a cluster server used only to give the networkName to Eve app. - * - * @returns void - */ - createDefaultDummyThreadNetworkDiagnosticsClusterServer() { - this.addClusterServer( - ClusterServer( - ThreadNetworkDiagnosticsCluster.with(ThreadNetworkDiagnostics.Feature.PacketCounts, ThreadNetworkDiagnostics.Feature.ErrorCounts), - { - channel: 1, - routingRole: ThreadNetworkDiagnostics.RoutingRole.Router, - networkName: 'MyMatterThread', - panId: 0, - extendedPanId: 0, - meshLocalPrefix: null, - neighborTable: [], - routeTable: [], - partitionId: null, - weighting: null, - dataVersion: null, - stableDataVersion: null, - leaderRouterId: null, - securityPolicy: null, - channelPage0Mask: null, - operationalDatasetComponents: null, - overrunCount: 0, - activeNetworkFaults: [], - }, - { - resetCounts: async (data) => { - this.log.debug('Matter command: resetCounts'); - // await this.commandHandler.executeHandler('resetCounts', data); - }, - }, - {}, - ), - ); - } - - /** - * Get a default OnOff cluster server. - * - * @param onOff - The initial state of the OnOff cluster (default: false). - */ - getDefaultOnOffClusterServer(onOff = false) { - return ClusterServer( - OnOffCluster, - { - onOff, - }, - { - on: async (data) => { - this.log.debug('Matter command: on onOff:', data.attributes.onOff.getLocal()); - // await this.commandHandler.executeHandler('on', data); - }, - off: async (data) => { - this.log.debug('Matter command: off onOff:', data.attributes.onOff.getLocal()); - // await this.commandHandler.executeHandler('off', data); - }, - toggle: async (data) => { - this.log.debug('Matter command: toggle onOff:', data.attributes.onOff.getLocal()); - // await this.commandHandler.executeHandler('toggle', data); - }, - }, - {}, - ); - } - - /** - * Creates a default OnOff cluster server. - * - * @param onOff - The initial state of the OnOff cluster (default: false). - */ - createDefaultOnOffClusterServer(onOff = false) { - this.addClusterServer(this.getDefaultOnOffClusterServer(onOff)); - } - - /** - * Get a default level control cluster server. - * - * @param currentLevel - The current level (default: 0). - */ - getDefaultLevelControlClusterServer(currentLevel = 0) { - return ClusterServer( - LevelControlCluster.with(LevelControl.Feature.OnOff), - { - currentLevel, - onLevel: 0, - options: { - executeIfOff: false, - coupleColorTempToLevel: false, - }, - }, - { - moveToLevel: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToLevel request:', request, 'attributes.currentLevel:', attributes.currentLevel.getLocal()); - // await this.commandHandler.executeHandler('moveToLevel', { request, attributes, endpoint }); - }, - move: async () => { - this.log.error('Matter command: move not implemented'); - }, - step: async () => { - this.log.error('Matter command: step not implemented'); - }, - stop: async () => { - this.log.error('Matter command: stop not implemented'); - }, - moveToLevelWithOnOff: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToLevelWithOnOff request:', request, 'attributes.currentLevel:', attributes.currentLevel.getLocal()); - // await this.commandHandler.executeHandler('moveToLevelWithOnOff', { request, attributes, endpoint }); - }, - moveWithOnOff: async () => { - this.log.error('Matter command: moveWithOnOff not implemented'); - }, - stepWithOnOff: async () => { - this.log.error('Matter command: stepWithOnOff not implemented'); - }, - stopWithOnOff: async () => { - this.log.error('Matter command: stopWithOnOff not implemented'); - }, - }, - ); - } - - /** - * Creates a default level control cluster server. - * - * @param currentLevel - The current level (default: 0). - */ - createDefaultLevelControlClusterServer(currentLevel = 0) { - this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel)); - } - - /** - * Get a default color control cluster server. - * - * @param currentHue - The current hue value. - * @param currentSaturation - The current saturation value. - * @param colorTemperatureMireds - The color temperature in mireds. - * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds. - * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds. - */ - getDefaultColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) { - return ClusterServer( - ColorControlCluster.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), - { - colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation, - options: { - executeIfOff: false, - }, - numberOfPrimaries: null, - enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation, - colorCapabilities: { xy: false, hueSaturation: true, colorLoop: false, enhancedHue: false, colorTemperature: true }, - currentHue, - currentSaturation, - colorTemperatureMireds, - colorTempPhysicalMinMireds, - colorTempPhysicalMaxMireds, - }, - { - moveToHue: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToHue request:', request, 'attributes.currentHue:', attributes.currentHue.getLocal()); - // attributes.currentHue.setLocal(request.hue); - // this.commandHandler.executeHandler('moveToHue', { request, attributes, endpoint }); - }, - moveHue: async () => { - this.log.error('Matter command: moveHue not implemented'); - }, - stepHue: async () => { - this.log.error('Matter command: stepHue not implemented'); - }, - moveToSaturation: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToSaturation request:', request, 'attributes.currentSaturation:', attributes.currentSaturation.getLocal()); - // attributes.currentSaturation.setLocal(request.saturation); - // this.commandHandler.executeHandler('moveToSaturation', { request, attributes, endpoint }); - }, - moveSaturation: async () => { - this.log.error('Matter command: moveSaturation not implemented'); - }, - stepSaturation: async () => { - this.log.error('Matter command: stepSaturation not implemented'); - }, - moveToHueAndSaturation: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToHueAndSaturation request:', request, 'attributes.currentHue:', attributes.currentHue.getLocal(), 'attributes.currentSaturation:', attributes.currentSaturation.getLocal()); - // attributes.currentHue.setLocal(request.hue); - // attributes.currentSaturation.setLocal(request.saturation); - // this.commandHandler.executeHandler('moveToHueAndSaturation', { request, attributes, endpoint }); - }, - stopMoveStep: async () => { - this.log.error('Matter command: stopMoveStep not implemented'); - }, - moveToColorTemperature: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToColorTemperature request:', request, 'attributes.colorTemperatureMireds:', attributes.colorTemperatureMireds.getLocal()); - // attributes.colorTemperatureMireds.setLocal(request.colorTemperatureMireds); - // this.commandHandler.executeHandler('moveToColorTemperature', { request, attributes, endpoint }); - }, - moveColorTemperature: async () => { - this.log.error('Matter command: moveColorTemperature not implemented'); - }, - stepColorTemperature: async () => { - this.log.error('Matter command: stepColorTemperature not implemented'); - }, - }, - {}, - ); - } - /** - * Creates a default color control cluster server. - * - * @param currentHue - The current hue value. - * @param currentSaturation - The current saturation value. - * @param colorTemperatureMireds - The color temperature in mireds. - * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds. - * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds. - */ - createDefaultColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) { - this.addClusterServer(this.getDefaultColorControlClusterServer(currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds)); - } - - /** - * Get a default color control cluster server. - * - * @param currentHue - The current hue value. - * @param currentSaturation - The current saturation value. - * @param colorTemperatureMireds - The color temperature in mireds. - * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds. - * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds. - */ - getDefaultXYColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) { - return ClusterServer( - ColorControlCluster.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), - { - colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation, - options: { - executeIfOff: false, - }, - numberOfPrimaries: null, - enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation, - colorCapabilities: { xy: true, hueSaturation: true, colorLoop: false, enhancedHue: false, colorTemperature: true }, - currentHue: 0, - currentSaturation: 0, - currentX, - currentY, - colorTemperatureMireds, - colorTempPhysicalMinMireds, - colorTempPhysicalMaxMireds, - }, - { - moveToColor: async (data) => { - this.log.debug('Matter command: moveToColor request:', data.request, 'attributes.currentHue:', data.attributes.currentX.getLocal(), data.attributes.currentY.getLocal()); - // this.commandHandler.executeHandler('moveToColor', data); - }, - moveColor: async () => { - this.log.error('Matter command: moveColor not implemented'); - }, - stepColor: async () => { - this.log.error('Matter command: stepColor not implemented'); - }, - moveToHue: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToHue request:', request, 'attributes.currentHue:', attributes.currentHue.getLocal()); - // this.commandHandler.executeHandler('moveToHue', { request, attributes, endpoint }); - }, - moveHue: async () => { - this.log.error('Matter command: moveHue not implemented'); - }, - stepHue: async () => { - this.log.error('Matter command: stepHue not implemented'); - }, - moveToSaturation: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToSaturation request:', request, 'attributes.currentSaturation:', attributes.currentSaturation.getLocal()); - // this.commandHandler.executeHandler('moveToSaturation', { request, attributes, endpoint }); - }, - moveSaturation: async () => { - this.log.error('Matter command: moveSaturation not implemented'); - }, - stepSaturation: async () => { - this.log.error('Matter command: stepSaturation not implemented'); - }, - moveToHueAndSaturation: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToHueAndSaturation request:', request, 'attributes.currentHue:', attributes.currentHue.getLocal(), 'attributes.currentSaturation:', attributes.currentSaturation.getLocal()); - // this.commandHandler.executeHandler('moveToHueAndSaturation', { request, attributes, endpoint }); - }, - stopMoveStep: async () => { - this.log.error('Matter command: stopMoveStep not implemented'); - }, - moveToColorTemperature: async ({ request, attributes, endpoint }) => { - this.log.debug('Matter command: moveToColorTemperature request:', request, 'attributes.colorTemperatureMireds:', attributes.colorTemperatureMireds.getLocal()); - // this.commandHandler.executeHandler('moveToColorTemperature', { request, attributes, endpoint }); - }, - moveColorTemperature: async () => { - this.log.error('Matter command: moveColorTemperature not implemented'); - }, - stepColorTemperature: async () => { - this.log.error('Matter command: stepColorTemperature not implemented'); - }, - }, - {}, - ); - } - /** - * Creates a default color control cluster server. - * - * @param currentHue - The current hue value. - * @param currentSaturation - The current saturation value. - * @param colorTemperatureMireds - The color temperature in mireds. - * @param colorTempPhysicalMinMireds - The physical minimum color temperature in mireds. - * @param colorTempPhysicalMaxMireds - The physical maximum color temperature in mireds. - */ - createDefaultXYColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) { - this.addClusterServer(this.getDefaultXYColorControlClusterServer(currentX, currentY, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds)); - } - - /** - * Get a default window covering cluster server. - * - * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. - */ - getDefaultWindowCoveringClusterServer(positionPercent100ths?: number) { - return ClusterServer( - WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.AbsolutePosition), - { - type: WindowCovering.WindowCoveringType.Rollershade, - configStatus: { - operational: true, - onlineReserved: true, - liftMovementReversed: false, - liftPositionAware: true, - tiltPositionAware: false, - liftEncoderControlled: false, - tiltEncoderControlled: false, - }, - operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped }, - endProductType: WindowCovering.EndProductType.RollerShade, - mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false }, - targetPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed - currentPositionLiftPercent100ths: positionPercent100ths ?? 0, // 0 Fully open 10000 fully closed - installedClosedLimitLift: 10000, - installedOpenLimitLift: 0, - }, - { - upOrOpen: async (data) => { - this.log.debug('Matter command: upOrOpen'); - // await this.commandHandler.executeHandler('upOrOpen', data); - }, - downOrClose: async (data) => { - this.log.debug('Matter command: downOrClose'); - // await this.commandHandler.executeHandler('downOrClose', data); - }, - stopMotion: async (data) => { - this.log.debug('Matter command: stopMotion'); - // await this.commandHandler.executeHandler('stopMotion', data); - }, - goToLiftPercentage: async (data) => { - this.log.debug( - `Matter command: goToLiftPercentage: ${data.request.liftPercent100thsValue} current: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()} ` + - `target: ${data.attributes.targetPositionLiftPercent100ths?.getLocal()} status: ${data.attributes.operationalStatus.getLocal().lift}`, - ); - // await this.commandHandler.executeHandler('goToLiftPercentage', data); - }, - }, - {}, - ); - } - /** - * Creates a default window covering cluster server. - * - * @param positionPercent100ths - The position percentage in 100ths (0-10000). Defaults to 0. - */ - createDefaultWindowCoveringClusterServer(positionPercent100ths?: number) { - this.addClusterServer(this.getDefaultWindowCoveringClusterServer(positionPercent100ths)); - } - - /** - * Sets the window covering target position as the current position and stops the movement. - */ - setWindowCoveringTargetAsCurrentAndStopped(endpoint?: MatterbridgeDeviceV8) { - if (!endpoint) endpoint = this as MatterbridgeDeviceV8; - const windowCoveringCluster = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.AbsolutePosition)); - if (windowCoveringCluster) { - const position = windowCoveringCluster.getCurrentPositionLiftPercent100thsAttribute(); - if (position !== null) { - windowCoveringCluster.setTargetPositionLiftPercent100thsAttribute(position); - windowCoveringCluster.setOperationalStatusAttribute({ - global: WindowCovering.MovementStatus.Stopped, - lift: WindowCovering.MovementStatus.Stopped, - tilt: 0, - }); - } - this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`); - } - } - - /** - * Sets the current and target status of a window covering. - * @param current - The current position of the window covering. - * @param target - The target position of the window covering. - * @param status - The movement status of the window covering. - */ - setWindowCoveringCurrentTargetStatus(current: number, target: number, status: WindowCovering.MovementStatus, endpoint?: MatterbridgeDeviceV8) { - if (!endpoint) endpoint = this as MatterbridgeDeviceV8; - const windowCoveringCluster = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.AbsolutePosition)); - if (windowCoveringCluster) { - windowCoveringCluster.setCurrentPositionLiftPercent100thsAttribute(current); - windowCoveringCluster.setTargetPositionLiftPercent100thsAttribute(target); - windowCoveringCluster.setOperationalStatusAttribute({ - global: status, - lift: status, - tilt: 0, - }); - } - this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${current}, targetPositionLiftPercent100ths: ${target} and operationalStatus: ${status}.`); - } - - /** - * Sets the status of the window covering. - * @param {WindowCovering.MovementStatus} status - The movement status to set. - */ - setWindowCoveringStatus(status: WindowCovering.MovementStatus, endpoint?: MatterbridgeDeviceV8) { - if (!endpoint) endpoint = this as MatterbridgeDeviceV8; - const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.AbsolutePosition)); - if (!windowCovering) return; - windowCovering.setOperationalStatusAttribute({ global: status, lift: status, tilt: 0 }); - this.log.debug(`Set WindowCovering operationalStatus: ${status}`); - } - - /** - * Retrieves the status of the window covering. - * @returns The global operational status of the window covering. - */ - getWindowCoveringStatus(endpoint?: MatterbridgeDeviceV8) { - if (!endpoint) endpoint = this as MatterbridgeDeviceV8; - const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.AbsolutePosition)); - if (!windowCovering) return undefined; - const status = windowCovering.getOperationalStatusAttribute(); - this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`); - return status.global; - } - - /** - * Sets the target and current position of the window covering. - * - * @param position - The position to set, specified as a number. - */ - setWindowCoveringTargetAndCurrentPosition(position: number, endpoint?: MatterbridgeDeviceV8) { - if (!endpoint) endpoint = this as MatterbridgeDeviceV8; - const windowCovering = endpoint.getClusterServer(WindowCoveringCluster.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift)); - if (!windowCovering) return; - windowCovering.setCurrentPositionLiftPercent100thsAttribute(position); - windowCovering.setTargetPositionLiftPercent100thsAttribute(position); - this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${position} and targetPositionLiftPercent100ths: ${position}.`); - } - - /** - * Get a default door lock cluster server. - * - * @remarks - * This method adds a cluster server for a door lock cluster with default settings. - * - */ - getDefaultDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.Deadbolt) { - return ClusterServer( - DoorLockCluster, - { - operatingMode: DoorLock.OperatingMode.Normal, - lockState, - lockType, - actuatorEnabled: false, - supportedOperatingModes: { normal: true, vacation: false, privacy: false, noRemoteLockUnlock: false, passage: false }, - }, - { - lockDoor: async (data) => { - this.log.debug('Matter command: lockDoor', data.request); - // await this.commandHandler.executeHandler('lockDoor', data); - }, - unlockDoor: async (data) => { - this.log.debug('Matter command: unlockDoor', data.request); - // await this.commandHandler.executeHandler('unlockDoor', data); - }, - }, - { - doorLockAlarm: true, - lockOperation: true, - lockOperationError: true, - }, - ); - } - /** - * Creates a default door lock cluster server. - * - * @remarks - * This method adds a cluster server for a door lock cluster with default settings. - * - */ - createDefaultDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.Deadbolt) { - this.addClusterServer(this.getDefaultDoorLockClusterServer(lockState, lockType)); - } - - /** - * Get a default momentary switch cluster server. - * - * @remarks - * This method adds a cluster server with default momentary switch features and configurations suitable for (AppleHome) Single Double Long automations. - */ - getDefaultSwitchClusterServer() { - return ClusterServer( - SwitchCluster.with(Switch.Feature.MomentarySwitch, Switch.Feature.MomentarySwitchRelease, Switch.Feature.MomentarySwitchLongPress, Switch.Feature.MomentarySwitchMultiPress), - { - numberOfPositions: 2, - currentPosition: 0, - multiPressMax: 2, - }, - {}, - { - initialPress: true, - longPress: true, - shortRelease: true, - longRelease: true, - multiPressOngoing: true, - multiPressComplete: true, - }, - ); - } - - /** - * Creates a default momentary switch cluster server. - * - * @remarks - * This method adds a cluster server with default momentary switch features and configurations. - */ - createDefaultSwitchClusterServer() { - this.addClusterServer(this.getDefaultSwitchClusterServer()); - // this.addFixedLabel('orientation', 'Switch'); - // this.addFixedLabel('label', 'Switch'); - } - - /** - * Get a default latching switch cluster server. - * - * @remarks - * This method adds a cluster server with default latching switch features and configuration. - */ - getDefaultLatchingSwitchClusterServer() { - return ClusterServer( - SwitchCluster.with(Switch.Feature.LatchingSwitch), - { - numberOfPositions: 2, - currentPosition: 0, - }, - {}, - { - switchLatched: true, - }, - ); - } - - /** - * Creates a default latching switch cluster server. - * - * @remarks - * This method adds a cluster server with default latching switch features and configuration. - */ - createDefaultLatchingSwitchClusterServer() { - this.addClusterServer(this.getDefaultLatchingSwitchClusterServer()); - // this.addFixedLabel('orientation', 'Switch'); - // this.addFixedLabel('label', 'Switch'); - } - - /* - getDefaultModeSelectClusterServer(description: string, supportedModes: ModeSelect.ModeOptionStruct[], currentMode = 0, startUpMode = 0) { - return ClusterServer( - ModeSelectCluster, - { - description: description, - standardNamespace: null, - supportedModes: supportedModes, - currentMode: currentMode, - startUpMode: startUpMode, - }, - { - changeToMode: async (data) => { - this.log.debug('Matter command: changeToMode', data.request); - await this.commandHandler.executeHandler('changeToMode', data); - }, - }, - ); - } - createDefaultModeSelectClusterServer(endpoint?: Endpoint) { - if (!endpoint) endpoint = this as Endpoint; - endpoint.addClusterServer( - this.getDefaultModeSelectClusterServer('Mode select', [ - { label: 'Mode 0', mode: 0, semanticTags: [{ mfgCode: VendorId(0xfff1), value: 0 }] }, - { label: 'Mode 1', mode: 1, semanticTags: [{ mfgCode: VendorId(0xfff1), value: 1 }] }, - ]), - ); - } - */ - - /** - * Get a default occupancy sensing cluster server. - * - * @param occupied - A boolean indicating whether the occupancy is occupied or not. Default is false. - */ - getDefaultOccupancySensingClusterServer(occupied = false) { - return ClusterServer( - OccupancySensingCluster, - { - occupancy: { occupied }, - occupancySensorType: OccupancySensing.OccupancySensorType.Pir, - occupancySensorTypeBitmap: { pir: true, ultrasonic: false, physicalContact: false }, - pirOccupiedToUnoccupiedDelay: 30, - }, - {}, - ); - } - /** - * Creates a default occupancy sensing cluster server. - * - * @param occupied - A boolean indicating whether the occupancy is occupied or not. Default is false. - */ - createDefaultOccupancySensingClusterServer(occupied = false) { - this.addClusterServer(this.getDefaultOccupancySensingClusterServer(occupied)); - } - - /** - * Get a default Illuminance Measurement Cluster Server. - * - * @param measuredValue - The measured value of illuminance. - */ - getDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) { - return ClusterServer( - IlluminanceMeasurementCluster, - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - tolerance: 0, - }, - {}, - {}, - ); - } - /** - * Creates a default Illuminance Measurement Cluster Server. - * - * @param measuredValue - The measured value of illuminance. - */ - createDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) { - this.addClusterServer(this.getDefaultIlluminanceMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default flow measurement cluster server. - * - * @param measuredValue - The measured value of the temperature. - */ - getDefaultFlowMeasurementClusterServer(measuredValue = 0) { - return ClusterServer( - FlowMeasurementCluster, - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - tolerance: 0, - }, - {}, - {}, - ); - } - - /** - * Creates a default flow measurement cluster server. - * - * @param measuredValue - The measured value of the temperature. - */ - createDefaultFlowMeasurementClusterServer(measuredValue = 0) { - this.addClusterServer(this.getDefaultFlowMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default temperature measurement cluster server. - * - * @param measuredValue - The measured value of the temperature. - */ - getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) { - return ClusterServer( - TemperatureMeasurementCluster, - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - tolerance: 0, - }, - {}, - {}, - ); - } - - /** - * Creates a default temperature measurement cluster server. - * - * @param measuredValue - The measured value of the temperature. - */ - createDefaultTemperatureMeasurementClusterServer(measuredValue = 0) { - this.addClusterServer(this.getDefaultTemperatureMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default RelativeHumidityMeasurementCluster server. - * - * @param measuredValue - The measured value of the relative humidity. - */ - getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) { - return ClusterServer( - RelativeHumidityMeasurementCluster, - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - tolerance: 0, - }, - {}, - {}, - ); - } - /** - * Creates a default RelativeHumidityMeasurementCluster server. - * - * @param measuredValue - The measured value of the relative humidity. - */ - createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) { - this.addClusterServer(this.getDefaultRelativeHumidityMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default Pressure Measurement Cluster Server. - * - * @param measuredValue - The measured value for the pressure. - */ - getDefaultPressureMeasurementClusterServer(measuredValue = 1000) { - return ClusterServer( - PressureMeasurementCluster, - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - tolerance: 0, - }, - {}, - {}, - ); - } - /** - * Creates a default Pressure Measurement Cluster Server. - * - * @param measuredValue - The measured value for the pressure. - */ - createDefaultPressureMeasurementClusterServer(measuredValue = 1000) { - this.addClusterServer(this.getDefaultPressureMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default boolean state cluster server. - * - * @param contact - Optional boolean value indicating the contact state. Defaults to `true` if not provided. - */ - getDefaultBooleanStateClusterServer(contact?: boolean) { - return ClusterServer( - BooleanStateCluster, - { - stateValue: contact ?? true, // true=contact false=no_contact - }, - {}, - { - stateChange: true, - }, - ); - } - - /** - * Creates a default boolean state configuration cluster server. - * - * @param contact - Optional boolean value indicating the contact state. Defaults to `true` if not provided. - */ - createDefaultBooleanStateClusterServer(contact?: boolean) { - this.addClusterServer(this.getDefaultBooleanStateClusterServer(contact)); - } - - /** - * Get a default boolean state configuration cluster server. - * - * @param contact - Optional boolean value indicating the sensor fault state. Defaults to `false` if not provided. - */ - getDefaultBooleanStateConfigurationClusterServer(sensorFault = false) { - return ClusterServer( - BooleanStateConfigurationCluster.with(BooleanStateConfiguration.Feature.Visual, BooleanStateConfiguration.Feature.Audible, BooleanStateConfiguration.Feature.SensitivityLevel), - { - currentSensitivityLevel: 0, - supportedSensitivityLevels: 2, - defaultSensitivityLevel: 0, - alarmsActive: { visual: false, audible: false }, - alarmsEnabled: { visual: false, audible: false }, - alarmsSupported: { visual: true, audible: true }, - // alarmsSuppressed: { visual: false, audible: false }, - sensorFault: { generalFault: sensorFault }, - }, - { - enableDisableAlarm: async ({ request, attributes }) => { - this.log.debug('Matter command: enableDisableAlarm', request); - // await this.commandHandler.executeHandler('enableDisableAlarm', { request, attributes }); - }, - }, - { - alarmsStateChanged: true, - sensorFault: true, - }, - ); - } - /** - * Creates a default boolean state configuration cluster server. - * - * @param contact - Optional boolean value indicating the sensor fault state. Defaults to `false` if not provided. - */ - createDefaultBooleanStateConfigurationClusterServer(sensorFault = false) { - this.addClusterServer(this.getDefaultBooleanStateConfigurationClusterServer(sensorFault)); - } - - /** - * Get a default power source replaceable battery cluster server. - * - * @param batPercentRemaining - The remaining battery percentage (default: 100). - * @param batChargeLevel - The battery charge level (default: PowerSource.BatChargeLevel.Ok). - * @param batVoltage - The battery voltage (default: 1500). - * @param batReplacementDescription - The battery replacement description (default: 'Battery type'). - * @param batQuantity - The battery quantity (default: 1). - */ - getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel: PowerSource.BatChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplacementDescription = 'Battery type', batQuantity = 1) { - return ClusterServer( - PowerSourceCluster.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable), - { - status: PowerSource.PowerSourceStatus.Active, - order: 0, - description: 'Primary battery', - batVoltage, - batPercentRemaining: Math.min(Math.max(batPercentRemaining * 2, 0), 200), - batChargeLevel, - batReplacementNeeded: false, - batReplaceability: PowerSource.BatReplaceability.UserReplaceable, - activeBatFaults: undefined, - batReplacementDescription, - batQuantity, - }, - {}, - {}, - ); - } - - /** - * Creates a default power source replaceable battery cluster server. - * - * @param batPercentRemaining - The remaining battery percentage (default: 100). - * @param batChargeLevel - The battery charge level (default: PowerSource.BatChargeLevel.Ok). - * @param batVoltage - The battery voltage (default: 1500). - * @param batReplacementDescription - The battery replacement description (default: 'Battery type'). - * @param batQuantity - The battery quantity (default: 1). - */ - createDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel: PowerSource.BatChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplacementDescription = 'Battery type', batQuantity = 1) { - this.addClusterServer(this.getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplacementDescription, batQuantity)); - } - - /** - * Get a default power source rechargeable battery cluster server. - * - * @param batPercentRemaining - The remaining battery percentage (default: 100). - * @param batChargeLevel - The battery charge level (default: PowerSource.BatChargeLevel.Ok). - * @param batVoltage - The battery voltage (default: 1500). - */ - getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel: PowerSource.BatChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500) { - return ClusterServer( - PowerSourceCluster.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable), - { - status: PowerSource.PowerSourceStatus.Active, - order: 0, - description: 'Primary battery', - batVoltage, - batPercentRemaining: Math.min(Math.max(batPercentRemaining * 2, 0), 200), - batTimeRemaining: 1, - batChargeLevel, - batReplacementNeeded: false, - batReplaceability: PowerSource.BatReplaceability.Unspecified, - activeBatFaults: undefined, - batChargeState: PowerSource.BatChargeState.IsNotCharging, - batFunctionalWhileCharging: true, - }, - {}, - {}, - ); - } - - /** - * Creates a default power source rechargeable battery cluster server. - * - * @param batPercentRemaining - The remaining battery percentage (default: 100). - * @param batChargeLevel - The battery charge level (default: PowerSource.BatChargeLevel.Ok). - * @param batVoltage - The battery voltage (default: 1500). - */ - createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel: PowerSource.BatChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500) { - this.addClusterServer(this.getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage)); - } - - /** - * Get a default power source wired cluster server. - * - * @param wiredCurrentType - The type of wired current (default: PowerSource.WiredCurrentType.Ac) - */ - getDefaultPowerSourceWiredClusterServer(wiredCurrentType: PowerSource.WiredCurrentType = PowerSource.WiredCurrentType.Ac) { - return ClusterServer( - PowerSourceCluster.with(PowerSource.Feature.Wired), - { - wiredCurrentType, - description: wiredCurrentType === PowerSource.WiredCurrentType.Ac ? 'AC Power' : 'DC Power', - status: PowerSource.PowerSourceStatus.Active, - order: 0, - }, - {}, - {}, - ); - } - - /** - * Creates a default power source wired cluster server. - * - * @param wiredCurrentType - The type of wired current (default: PowerSource.WiredCurrentType.Ac) - */ - createDefaultPowerSourceWiredClusterServer(wiredCurrentType: PowerSource.WiredCurrentType = PowerSource.WiredCurrentType.Ac) { - this.addClusterServer(this.getDefaultPowerSourceWiredClusterServer(wiredCurrentType)); - } - - /** - * @deprecated This function is deprecated by Matter 1.3 spec and will be removed in a future version. - */ - createDefaultPowerSourceConfigurationClusterServer(endpointNumber?: number) { - this.addClusterServer( - ClusterServer( - PowerSourceConfigurationCluster, - { - sources: endpointNumber ? [EndpointNumber(endpointNumber)] : [], - }, - {}, - {}, - ), - ); - } - - /** - * Get a default air quality cluster server. - * - * @param airQuality The air quality type. Defaults to `AirQuality.AirQualityType.Unknown`. - */ - getDefaultAirQualityClusterServer(airQuality = AirQuality.AirQualityType.Unknown) { - return ClusterServer( - AirQualityCluster.with(AirQuality.Feature.FairAirQuality, AirQuality.Feature.ModerateAirQuality, AirQuality.Feature.VeryPoorAirQuality), - { - airQuality, - }, - {}, - {}, - ); - } - /** - * Creates a default air quality cluster server. - * - * @param airQuality The air quality type. Defaults to `AirQuality.AirQualityType.Unknown`. - */ - createDefaultAirQualityClusterServer(airQuality = AirQuality.AirQualityType.Unknown) { - this.addClusterServer(this.getDefaultAirQualityClusterServer(airQuality)); - } - - /** - * Get a default TVOC measurement cluster server. - * - * @param measuredValue - The measured value for TVOC. - */ - getDefaultTvocMeasurementClusterServer(measuredValue = 0) { - return ClusterServer( - TvocMeasurementCluster.with(TvocMeasurement.Feature.NumericMeasurement), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - }, - {}, - {}, - ); - } - - /** - * Creates a default TVOC measurement cluster server. - * - * @param measuredValue - The measured value for TVOC. - */ - createDefaultTvocMeasurementClusterServer(measuredValue = 0) { - this.addClusterServer(this.getDefaultTvocMeasurementClusterServer(measuredValue)); - } - - /** - * Get a default thermostat cluster server with the specified parameters. - * - * @param localTemperature - The local temperature value in degrees Celsius. Defaults to 23. - * @param occupiedHeatingSetpoint - The occupied heating setpoint value in degrees Celsius. Defaults to 21. - * @param occupiedCoolingSetpoint - The occupied cooling setpoint value in degrees Celsius. Defaults to 25. - * @param minSetpointDeadBand - The minimum setpoint dead band value. - */ - getDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1) { - return ClusterServer( - ThermostatCluster.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode), - { - localTemperature: localTemperature * 100, - occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100, - occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100, - minHeatSetpointLimit: 0, - maxHeatSetpointLimit: 5000, - absMinHeatSetpointLimit: 0, - absMaxHeatSetpointLimit: 5000, - minCoolSetpointLimit: 0, - maxCoolSetpointLimit: 5000, - absMinCoolSetpointLimit: 0, - absMaxCoolSetpointLimit: 5000, - minSetpointDeadBand, - systemMode: Thermostat.SystemMode.Off, - controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating, - thermostatRunningMode: Thermostat.ThermostatRunningMode.Off, - }, - { - setpointRaiseLower: async ({ request, attributes }) => { - this.log.debug('Matter command: setpointRaiseLower', request); - // await this.commandHandler.executeHandler('setpointRaiseLower', { request, attributes }); - }, - }, - {}, - ); - } - - /** - * Creates and adds a default thermostat cluster server to the device. - * - * @param localTemperature - The local temperature value. - * @param occupiedHeatingSetpoint - The occupied heating setpoint value. - * @param occupiedCoolingSetpoint - The occupied cooling setpoint value. - * @param minSetpointDeadBand - The minimum setpoint dead band value. - */ - createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1) { - this.addClusterServer(this.getDefaultThermostatClusterServer(localTemperature, occupiedHeatingSetpoint, occupiedCoolingSetpoint, minSetpointDeadBand)); - } - - /** - * Get a default dummy time sync cluster server. Only needed to create a thermostat. - */ - getDefaultTimeSyncClusterServer() { - return ClusterServer( - TimeSyncCluster.with(TimeSync.Feature.TimeZone), - { - utcTime: null, - granularity: TimeSync.Granularity.NoTimeGranularity, - timeZone: [{ offset: 0, validAt: 0 }], - trustedTimeNodeId: null, - dstOffset: [], - localTime: null, - timeZoneDatabase: true, - }, - { - setUtcTime: async ({ request, attributes }) => { - this.log.debug('Matter command: setUtcTime', request); - // await this.commandHandler.executeHandler('setUtcTime', { request, attributes }); - }, - }, - { - dstTableEmpty: true, - dstStatus: true, - timeZoneStatus: true, - }, - ); - } - /** - * Creates a default dummy time sync cluster server. Only needed to create a thermostat. - */ - createDefaultTimeSyncClusterServer() { - this.addClusterServer(this.getDefaultTimeSyncClusterServer()); - } - - /** - * Returns the default SmokeCOAlarm Cluster Server. - * - * @param smokeState - The state of the smoke alarm. Defaults to SmokeCoAlarm.AlarmState.Normal. - * @param coState - The state of the CO alarm. Defaults to SmokeCoAlarm.AlarmState.Normal. - * @returns The default SmokeCOAlarmClusterServer. - */ - getDefaultSmokeCOAlarmClusterServer(smokeState = SmokeCoAlarm.AlarmState.Normal, coState = SmokeCoAlarm.AlarmState.Normal) { - return ClusterServer( - SmokeCoAlarmCluster.with(SmokeCoAlarm.Feature.SmokeAlarm, SmokeCoAlarm.Feature.CoAlarm), - { - smokeState, - coState, - expressedState: SmokeCoAlarm.ExpressedState.Normal, - batteryAlert: SmokeCoAlarm.AlarmState.Normal, - deviceMuted: SmokeCoAlarm.MuteState.NotMuted, - testInProgress: false, - hardwareFaultAlert: false, - endOfServiceAlert: SmokeCoAlarm.EndOfService.Normal, - interconnectSmokeAlarm: SmokeCoAlarm.AlarmState.Normal, - interconnectCoAlarm: SmokeCoAlarm.AlarmState.Normal, - }, - { - selfTestRequest: async ({ request, attributes }) => { - this.log.debug('Matter command: selfTestRequest'); - // await this.commandHandler.executeHandler('selfTestRequest', { request, attributes }); - }, - }, - { - smokeAlarm: true, - interconnectSmokeAlarm: true, - coAlarm: true, - interconnectCoAlarm: true, - lowBattery: true, - hardwareFault: true, - endOfService: true, - selfTestComplete: true, - alarmMuted: true, - muteEnded: true, - allClear: true, - }, - ); - } - /** - * Create the default SmokeCOAlarm Cluster Server. - * - * @param smokeState - The state of the smoke alarm. Defaults to SmokeCoAlarm.AlarmState.Normal. - * @param coState - The state of the CO alarm. Defaults to SmokeCoAlarm.AlarmState.Normal. - * @returns The default SmokeCOAlarmClusterServer. - */ - createDefaultSmokeCOAlarmClusterServer(smokeState = SmokeCoAlarm.AlarmState.Normal, coState = SmokeCoAlarm.AlarmState.Normal) { - this.addClusterServer(this.getDefaultSmokeCOAlarmClusterServer(smokeState, coState)); - } - - /** - * Returns the default Carbon Monoxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultCarbonMonoxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - CarbonMonoxideConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Carbon Monoxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaultCarbonMonoxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultCarbonMonoxideConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Carbon Dioxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultCarbonDioxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - CarbonDioxideConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Carbon Dioxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaultCarbonDioxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultCarbonDioxideConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Formaldehyde Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultFormaldehydeConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - FormaldehydeConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Formaldehyde Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaultFormaldehydeConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultFormaldehydeConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Pm1 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultPm1ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - Pm1ConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Pm1 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulPm1ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultPm1ConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Pm25 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultPm25ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - Pm25ConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Pm25 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulPm25ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultPm25ConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Pm10 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultPm10ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - Pm10ConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Pm10 Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulPm10ConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultPm10ConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Ozone Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultOzoneConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ugm3, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - OzoneConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Ozone Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulOzoneConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ugm3, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultOzoneConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Radon Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultRadonConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - RadonConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Radon Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulRadonConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ppm, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultRadonConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default Nitrogen Dioxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - * @returns {ClusterServer} - The default Carbon Monoxide Concentration Measurement Cluster Server. - */ - getDefaultNitrogenDioxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ugm3, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - return ClusterServer( - NitrogenDioxideConcentrationMeasurementCluster.with('NumericMeasurement'), - { - measuredValue, - minMeasuredValue: null, - maxMeasuredValue: null, - uncertainty: 0, - measurementUnit, - measurementMedium, - }, - {}, - {}, - ); - } - /** - * Create the default Nitrogen Dioxide Concentration Measurement Cluster Server. - * - * @param {number} measuredValue - The measured value of the concentration. - * @param {ConcentrationMeasurement.MeasurementUnit} measurementUnit - The unit of measurement. - * @param {ConcentrationMeasurement.MeasurementMedium} measurementMedium - The medium of measurement. - */ - createDefaulNitrogenDioxideConcentrationMeasurementClusterServer(measuredValue = 0, measurementUnit = ConcentrationMeasurement.MeasurementUnit.Ugm3, measurementMedium = ConcentrationMeasurement.MeasurementMedium.Air) { - this.addClusterServer(this.getDefaultNitrogenDioxideConcentrationMeasurementClusterServer(measuredValue, measurementUnit, measurementMedium)); - } - - /** - * Returns the default fan control cluster server rev 2. - * - * @param fanMode The fan mode to set. Defaults to `FanControl.FanMode.Off`. - * @returns The default fan control cluster server. - */ - getDefaultFanControlClusterServer(fanMode = FanControl.FanMode.Off) { - return ClusterServer( - FanControlCluster.with(FanControl.Feature.MultiSpeed, FanControl.Feature.Auto /* , FanControl.Feature.Step*/), - { - fanMode, - fanModeSequence: FanControl.FanModeSequence.OffLowMedHighAuto, - percentSetting: 0, - percentCurrent: 0, - speedMax: 100, - speedSetting: 0, - speedCurrent: 0, - }, - { - /* - step: async ({ request, attributes }) => { - this.log.debug('Matter command: step', request); - await this.commandHandler.executeHandler('step', { request, attributes }); - }, - */ - }, - {}, - ); - } - /** - * Create the default fan control cluster server rev 2. - * - * @param fanMode The fan mode to set. Defaults to `FanControl.FanMode.Off`. - * @returns The default fan control cluster server. - */ - createDefaultFanControlClusterServer(fanMode = FanControl.FanMode.Off) { - this.addClusterServer(this.getDefaultFanControlClusterServer(fanMode)); - } - - // NOTE Support of Device Energy Management Cluster is provisional. - getDefaultDeviceEnergyManagementClusterServer() { - return ClusterServer( - DeviceEnergyManagementCluster.with(DeviceEnergyManagement.Feature.Pausable, DeviceEnergyManagement.Feature.PowerForecastReporting, DeviceEnergyManagement.Feature.StateForecastReporting), - { - esaType: DeviceEnergyManagement.EsaType.Other, - esaCanGenerate: false, - esaState: DeviceEnergyManagement.EsaState.Online, - absMinPower: 0, - absMaxPower: 0, - optOutState: DeviceEnergyManagement.OptOutState.NoOptOut, - forecast: null, - }, - { - pauseRequest: async ({ request, attributes }) => { - this.log.debug('Matter command: pauseRequest', request); - // await this.commandHandler.executeHandler('pauseRequest', { request, attributes }); - }, - resumeRequest: async () => { - this.log.debug('Matter command: resumeRequest'); - // await this.commandHandler.executeHandler('resumeRequest'); - }, - }, - { - paused: true, - resumed: true, - }, - ); - } - - // NOTE Support of Device Energy Management Mode Cluster is provisional. - getDefaultDeviceEnergyManagementModeClusterServer() { - return ClusterServer( - DeviceEnergyManagementModeCluster, - { - esaType: DeviceEnergyManagement.EsaType.Other, - esaCanGenerate: false, - esaState: DeviceEnergyManagement.EsaState.Online, - absMinPower: 0, - absMaxPower: 0, - optOutState: DeviceEnergyManagement.OptOutState.NoOptOut, - }, - {}, - {}, - ); - } -} diff --git a/src/matterbridgeV8.ts b/src/matterbridgeV8.ts deleted file mode 100644 index 5d946e0f..00000000 --- a/src/matterbridgeV8.ts +++ /dev/null @@ -1,785 +0,0 @@ -/* eslint-disable no-console */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * This file contains the class NewMatterbridge. Test of new matter.js api - * - * @file matterbridgeNewApi.ts - * @author Luca Liguori - * @date 2024-06-01 - * @version 1.0.0 - * - * Copyright 2024 Luca Liguori. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. * - */ - -/** - * Import needed modules from @project-chip/matter-node.js - */ -// Include this first to auto-register Crypto, Network and Time Node.js implementations -import '@project-chip/matter-node.js'; -import { CryptoNode } from '@project-chip/matter-node.js/crypto'; -import { DeviceTypeId, FabricIndex, VendorId } from '@project-chip/matter-node.js/datatype'; -import { Format, Level, Logger, createFileLogger } from '@project-chip/matter-node.js/log'; -import { Storage, StorageContext, StorageManager } from '@project-chip/matter-node.js/storage'; -import { Environment, StorageService } from '@project-chip/matter.js/environment'; -import { ServerNode } from '@project-chip/matter.js/node'; -import { DeviceTypeDefinition, DeviceTypes, logEndpoint } from '@project-chip/matter-node.js/device'; -import { QrCode } from '@project-chip/matter-node.js/schema'; -import { FabricAction } from '@project-chip/matter-node.js/fabric'; -import { Endpoint, EndpointServer } from '@project-chip/matter.js/endpoint'; -import { EndpointType } from '@project-chip/matter.js/endpoint/type'; - -import { AggregatorEndpoint } from '@project-chip/matter.js/endpoints/AggregatorEndpoint'; -import { BridgedNodeEndpoint } from '@project-chip/matter.js/endpoints/BridgedNodeEndpoint'; - -// Behaviour servers -import { IdentifyServer } from '@project-chip/matter.js/behavior/definitions/identify'; -import { OnOffServer } from '@project-chip/matter.js/behavior/definitions/on-off'; -import { GroupsServer } from '@project-chip/matter.js/behavior/definitions/groups'; -import { ScenesServer } from '@project-chip/matter.js/behavior/definitions/scenes'; -import { BridgedDeviceBasicInformationServer } from '@project-chip/matter.js/behavior/definitions/bridged-device-basic-information'; -import { TemperatureMeasurementServer } from '@project-chip/matter.js/behavior/definitions/temperature-measurement'; -import { RelativeHumidityMeasurementServer } from '@project-chip/matter.js/behavior/definitions/relative-humidity-measurement'; -import { SwitchServer } from '@project-chip/matter.js/behavior/definitions/switch'; - -import { ActionContext } from '@project-chip/matter.js/behavior/context'; - -// Device definitions -import { ColorDimmerSwitchDevice } from '@project-chip/matter.js/devices/ColorDimmerSwitchDevice'; -import { OnOffLightDevice } from '@project-chip/matter.js/devices/OnOffLightDevice'; -import { GenericSwitchDevice } from '@project-chip/matter.js/devices/GenericSwitchDevice'; - -import { MutableEndpoint } from '@project-chip/matter.js/endpoint/type'; -import { SupportedBehaviors } from '@project-chip/matter.js/endpoint/properties'; - -import { AnsiLogger, BRIGHT, RESET, TimestampFormat, UNDERLINE, UNDERLINEOFF, YELLOW, db, debugStringify, stringify, er, nf, rs, wr, RED, GREEN, zb, CYAN } from 'node-ansi-logger'; -import { NodeStorageManager, NodeStorage } from 'node-persist-manager'; - -import EventEmitter from 'events'; -import path from 'path'; -import { promises as fs } from 'fs'; -import { MatterbridgeDeviceV8 } from './matterbridgeDeviceV8.js'; -import { Actions, BasicInformationCluster, Identify, RelativeHumidityMeasurement, SwitchCluster, TemperatureMeasurement } from '@project-chip/matter-node.js/cluster'; -import { bridgedNode, dimmableSwitch } from './matterbridgeDevice.js'; -import { RegisteredPlugin } from './matterbridge.js'; -import { MatterbridgePlatform, PlatformConfig } from './matterbridgePlatform.js'; -import { pathToFileURL } from 'url'; -import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js'; - -const plg = '\u001B[38;5;33m'; -const dev = '\u001B[38;5;79m'; -const typ = '\u001B[38;5;207m'; - -const log = Logger.get('Matterbridge'); - -/** - * Represents the Matterbridge application. - */ -export class MatterbridgeV8 extends EventEmitter { - private environment = Environment.default; - - public matterbridgeVersion = ''; - public osVersion = ''; - public matterbridgeDirectory = ''; - public matterbridgePluginDirectory = ''; - public globalModulesDirectory = ''; - public matterbridgeLogFile = ''; - private registeredPlugins: RegisteredPlugin[] = []; - - // Node storage - private nodeStorage: NodeStorageManager | undefined; - private nodeContext: NodeStorage | undefined; - - // Matter storage - public matterStorageService?: StorageService; - public matterStorageManager?: StorageManager; - public matterStorageContext?: StorageContext; - - public matterServerNode?: ServerNode; - public matterAggregator?: Endpoint; - - public matterLogger?: Logger; - - private constructor() { - super(); - } - - static async create() { - const matterbridge = new MatterbridgeV8(); - await matterbridge.initialize(); - return matterbridge; - } - - async initialize() { - // Set up the temporary Matterbridge environment - this.matterbridgeVersion = '2.0.0'; - this.osVersion = '10.0.22631'; - this.matterbridgeDirectory = 'C:\\Users\\lligu\\.matterbridge'; - this.matterbridgePluginDirectory = 'C:\\Users\\lligu\\Matterbridge'; - this.globalModulesDirectory = 'C:\\Users\\lligu\\AppData\\Roaming\\npm\\node_modules'; - this.matterbridgeLogFile = 'matterbridge.log'; - - this.matterLogger = Logger.get('Matterbridge'); - await this.deleteMatterLogfile(this.matterbridgeLogFile); - await this.setupMatterFileLogger(this.matterbridgeLogFile); - this.matterLogger?.notice(`Starting Matterbridge v${this.matterbridgeVersion} on Node.js ${process.version} (${process.platform} ${process.arch})`); - - this.setupMatterVars(Level.DEBUG, Format.ANSI); - - await this.setupMatterStorage(); - - await this.setupNodeStorage(); - - // Get the plugins from node storage - if (!this.nodeStorage) throw new Error('No node storage initialized'); - if (!this.nodeContext) throw new Error('No node storage context initialized'); - this.registeredPlugins = await this.nodeContext.get('plugins', []); - for (const plugin of this.registeredPlugins) { - plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name); - await plugin.nodeContext.set('name', plugin.name); - await plugin.nodeContext.set('type', plugin.type); - await plugin.nodeContext.set('path', plugin.path); - await plugin.nodeContext.set('version', plugin.version); - await plugin.nodeContext.set('description', plugin.description); - await plugin.nodeContext.set('author', plugin.author); - this.matterLogger?.notice(`Created node storage context for plugin ${plugin.name}`); - } - } - - private setupMatterVars(level: Level, format: Format.Type) { - this.environment.vars.set('log.level', level); - this.environment.vars.set('log.format', format); - this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, 'matterstorage')); - this.environment.vars.set('runtime.signals', false); - this.environment.vars.set('runtime.exitcode', false); - } - - private async setupMatterStorage() { - this.matterStorageService = this.environment.get(StorageService); - this.matterLogger?.notice(`Storage service created: ${this.matterStorageService.location}`); - - this.matterStorageManager = await this.matterStorageService.open('Matterbridge'); - this.matterLogger?.notice('Storage manager "Matterbridge" created'); - - this.matterStorageContext = this.matterStorageManager.createContext('persist'); - this.matterLogger?.notice('Storage context "Matterbridge.persist" created'); - } - - private async setupNodeStorage() { - this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false }); - this.matterLogger?.notice(`Created node storage manager: ${path.join(this.matterbridgeDirectory, 'storage')}`); - this.nodeContext = await this.nodeStorage.createStorage('matterbridge'); - this.matterLogger?.notice('Created node storage context "matterbridge"'); - } - - private async deleteMatterLogfile(filename: string) { - try { - await fs.unlink(path.join(this.matterbridgeDirectory, filename)); - } catch (err) { - this.matterLogger?.error(`Error deleting old log file: ${err}`); - } - } - - private async setupMatterFileLogger(filename: string) { - Logger.addLogger('filelogger', await createFileLogger(path.join(this.matterbridgeDirectory, filename)), { - defaultLogLevel: Level.DEBUG, - }); - this.matterLogger?.notice('File logger created: ' + path.join(this.matterbridgeDirectory, filename)); - } - - /** - * Creates a server node storage context. - * - * @param pluginName - The name of the plugin. - * @param deviceName - The name of the device. - * @param deviceType - The deviceType of the device. - * @param vendorId - The vendor ID. - * @param vendorName - The vendor name. - * @param productId - The product ID. - * @param productName - The product name. - * @param serialNumber - The serial number of the device (optional). - * @param uniqueId - The unique ID of the device (optional). - * @param softwareVersion - The software version of the device (optional). - * @param softwareVersionString - The software version string of the device (optional). - * @param hardwareVersion - The hardware version of the device (optional). - * @param hardwareVersionString - The hardware version string of the device (optional). - * @returns The storage context for the commissioning server. - */ - async createServerNodeContext(pluginName: string, deviceName: string, deviceType: DeviceTypeId, vendorId: number, vendorName: string, productId: number, productName: string, serialNumber?: string): Promise> { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - if (!this.matterStorageService) throw new Error('No storage service initialized'); - - log.notice(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`); - const storageManager = await this.matterStorageService.open(pluginName); - const storageContext = storageManager.createContext('persist'); - const random = 'SN' + CryptoNode.getRandomData(8).toHex(); - await storageContext.set('storeId', pluginName); - await storageContext.set('deviceName', deviceName); - await storageContext.set('deviceType', deviceType); - await storageContext.set('vendorId', vendorId); - await storageContext.set('vendorName', vendorName.slice(0, 32)); - await storageContext.set('productId', productId); - await storageContext.set('productName', productName.slice(0, 32)); - await storageContext.set('nodeLabel', productName.slice(0, 32)); - await storageContext.set('productLabel', productName.slice(0, 32)); - await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : random)); - await storageContext.set('uniqueId', await storageContext.get('uniqueId', random)); - await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1); - await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0'); - await storageContext.set('hardwareVersion', this.osVersion && this.osVersion.includes('.') ? parseInt(this.osVersion.split('.')[0], 10) : 1); - await storageContext.set('hardwareVersionString', this.osVersion ?? '1.0.0'); - - log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`); - log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`); - log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`); - log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`); - log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`); - return storageContext; - } - - async createServerNode(storageContext: StorageContext, port = 5540, passcode = 20242025, discriminator = 3850) { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - - log.notice(`Creating server node for ${await storageContext.get('storeId')}`); - - /** - * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration - */ - const serverNode = await ServerNode.create({ - // Required: Give the Node a unique ID which is used to store the state of this node - id: await storageContext.get('storeId'), - - // Provide Network relevant configuration like the port - // Optional when operating only one device on a host, Default port is 5540 - network: { - port, - }, - - // Provide Commissioning relevant settings - // Optional for development/testing purposes - commissioning: { - passcode, - discriminator, - }, - - // Provide Node announcement settings - // Optional: If Ommitted some development defaults are used - productDescription: { - name: await storageContext.get('deviceName'), - deviceType: DeviceTypeId(await storageContext.get('deviceType')), - }, - - // Provide defaults for the BasicInformation cluster on the Root endpoint - // Optional: If Omitted some development defaults are used - basicInformation: { - vendorId: VendorId(await storageContext.get('vendorId')), - vendorName: await storageContext.get('vendorName'), - - productId: await storageContext.get('productId'), - productName: await storageContext.get('productName'), - productLabel: await storageContext.get('productName'), - nodeLabel: await storageContext.get('productName'), - - serialNumber: await storageContext.get('serialNumber'), - uniqueId: await storageContext.get('uniqueId'), - - softwareVersion: await storageContext.get('softwareVersion'), - softwareVersionString: await storageContext.get('softwareVersionString'), - hardwareVersion: await storageContext.get('hardwareVersion'), - hardwareVersionString: await storageContext.get('hardwareVersionString'), - }, - }); - - /** - * This event is triggered when the device is initially commissioned successfully. - * This means: It is added to the first fabric. - */ - serverNode.lifecycle.commissioned.on(() => log.notice('Server was initially commissioned successfully!')); - - /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */ - serverNode.lifecycle.decommissioned.on(() => log.notice('Server was fully decommissioned successfully!')); - - /** This event is triggered when the device went online. This means that it is discoverable in the network. */ - serverNode.lifecycle.online.on(() => log.notice('Server is online')); - - /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */ - serverNode.lifecycle.offline.on(() => log.notice('Server is offline')); - - /** - * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular - * information is needed. - */ - serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => { - let action = ''; - switch (fabricAction) { - case FabricAction.Added: - action = 'added'; - break; - case FabricAction.Removed: - action = 'removed'; - break; - case FabricAction.Updated: - action = 'updated'; - break; - } - log.notice(`Commissioned Fabrics changed event (${action}) for ${fabricIndex} triggered`); - log.notice(this.matterServerNode?.state.commissioning.fabrics[fabricIndex]); - }); - - /** - * This event is triggered when an operative new session was opened by a Controller. - * It is not triggered for the initial commissioning process, just afterwards for real connections. - */ - serverNode.events.sessions.opened.on((session) => log.notice('Session opened', session)); - - /** - * This event is triggered when an operative session is closed by a Controller or because the Device goes offline. - */ - serverNode.events.sessions.closed.on((session) => log.notice('Session closed', session)); - - /** This event is triggered when a subscription gets added or removed on an operative session. */ - serverNode.events.sessions.subscriptionsChanged.on((session) => { - log.notice('Session subscriptions changed', session); - log.notice('Status of all sessions', this.matterServerNode?.state.sessions.sessions); - }); - - return serverNode; - } - - showServerNodeQR() { - if (!this.matterServerNode || !this.matterLogger) return; - const log = this.matterLogger; - if (!this.matterServerNode.lifecycle.isCommissioned) { - const { qrPairingCode, manualPairingCode } = this.matterServerNode.state.commissioning.pairingCodes; - console.log(QrCode.get(qrPairingCode)); - log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`); - log.notice(`Manual pairing code: ${manualPairingCode}`); - } else { - log.notice('Device is already commissioned. Waiting for controllers to connect ...'); - log.notice('Fabrics:', this.matterServerNode.state.commissioning.fabrics); - for (const key in this.matterServerNode.state.commissioning.fabrics) { - const fabric = this.matterServerNode.state.commissioning.fabrics[FabricIndex(Number(key))]; - log.notice(`- index ${fabric.fabricIndex} id ${fabric.fabricId} nodeId ${fabric.nodeId} rootVendor ${fabric.rootVendorId} rootNodeId ${fabric.rootNodeId}`); - } - } - } - - async startServerNode(storageContext: StorageContext) { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - - if (!this.matterServerNode) return; - log.notice(`Starting ${await storageContext.get('storeId')} server node`); - await this.matterServerNode.bringOnline(); - } - - async stopServerNode(storageContext: StorageContext) { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - - if (!this.matterServerNode) return; - log.notice(`Stopping ${await storageContext.get('storeId')} server node`); - await this.matterServerNode.close(); - this.matterServerNode = undefined; - } - - async createAggregator(storageContext: StorageContext) { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - - log.notice(`Creating ${await storageContext.get('storeId')} aggregator `); - - const aggregator = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')} aggregator` }); - return aggregator; - } - - async startBridge() { - if (!this.matterLogger) throw new Error('No logger initialized'); - const log = this.matterLogger; - - const storageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', AggregatorEndpoint.deviceType, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator'); - - this.matterServerNode = await this.createServerNode(storageContext); - - this.matterAggregator = await this.createAggregator(storageContext); - - log.notice(`Adding ${await storageContext.get('storeId')} aggregator to ${await storageContext.get('storeId')} server node`); - await this.matterServerNode.add(this.matterAggregator); - - /* - for (const plugin of this.registeredPlugins) { - if (!plugin.enabled) { - log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`); - continue; - } - plugin.error = false; - plugin.loaded = false; - plugin.started = false; - plugin.configured = false; - plugin.connected = undefined; - plugin.qrPairingCode = undefined; - plugin.manualPairingCode = undefined; - this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously - } - */ - - log.notice(`Adding lightEndpoint1 to ${await storageContext.get('storeId')} aggregator`); - const lightEndpoint1 = new Endpoint(OnOffLightDevice.with(BridgedDeviceBasicInformationServer), { - id: 'OnOffLight', - bridgedDeviceBasicInformation: { - vendorId: VendorId(await storageContext.get('vendorId')), - vendorName: await storageContext.get('vendorName'), - - productName: 'Light', - productLabel: 'Light', - nodeLabel: 'Light', - - serialNumber: '0x123456789', - uniqueId: '0x123456789', - reachable: true, - }, - }); - await this.matterAggregator.add(lightEndpoint1); - - log.notice(`Adding switchEnpoint2 to ${await storageContext.get('storeId')} aggregator`); - const switchEnpoint2 = new Endpoint(GenericSwitchDevice.with(BridgedDeviceBasicInformationServer, SwitchServer.with('MomentarySwitch', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress', 'MomentarySwitchRelease')), { - id: 'GenericSwitch', - bridgedDeviceBasicInformation: { - vendorId: VendorId(await storageContext.get('vendorId')), - vendorName: await storageContext.get('vendorName'), - - productName: 'GenericSwitch', - productLabel: 'GenericSwitch', - nodeLabel: 'GenericSwitch', - - serialNumber: '0x123456739', - uniqueId: '0x123456739', - reachable: true, - }, - switch: { - numberOfPositions: 2, - currentPosition: 0, - multiPressMax: 2, - }, - }); - await this.matterAggregator.add(switchEnpoint2); - switchEnpoint2.events.identify.startIdentifying.on(() => log.notice('GenericSwitch.identify logic, ideally blink a light every 0.5s ...')); - switchEnpoint2.events.switch.currentPosition$Changed.on(() => log.notice('GenericSwitch.currentPosition changed ...')); - // switchEnpoint2.act((agent) => agent.switch.events.initialPress.emit({ newPosition: 1 }, agent.context)); - // switchEnpoint2.events.switch.emit('initialPress', { newPosition: 1 }, switchEnpoint2.events.switch.context); - - log.notice(`Adding matterbridge device to ${await storageContext.get('storeId')} aggregator`); - const matterbridgeDevice3 = new MatterbridgeDeviceV8(DeviceTypes.TEMPERATURE_SENSOR, { uniqueStorageKey: 'TemperatureSensor' }); - matterbridgeDevice3.addDeviceTypeWithClusterServer([DeviceTypes.HUMIDITY_SENSOR], [TemperatureMeasurement.Cluster.id, RelativeHumidityMeasurement.Cluster.id]); - /* - matterbridgeDevice3.behaviors.require(IdentifyServer, { - identifyTime: 5, - }); - matterbridgeDevice3.behaviors.require(TemperatureMeasurementServer, { - measuredValue: 25.0, - minMeasuredValue: null, - maxMeasuredValue: null, - }); - */ - matterbridgeDevice3.behaviors.require(BridgedDeviceBasicInformationServer, { - vendorId: VendorId(await storageContext.get('vendorId')), - vendorName: await storageContext.get('vendorName'), - - productName: 'TemperatureSensor', - productLabel: 'TemperatureSensor', - nodeLabel: 'TemperatureSensor', - - serialNumber: '0x145456739', - uniqueId: '0x124556739', - reachable: true, - }); - await this.matterAggregator.add(matterbridgeDevice3); - - await this.startServerNode(storageContext); - - // logEndpoint(EndpointServer.forEndpoint(matterbridgeDevice3)); - - await lightEndpoint1.set({ - onOff: { - onOff: true, - }, - }); - await matterbridgeDevice3.set({ - temperatureMeasurement: { - measuredValue: 20 * 100, - }, - relativeHumidityMeasurement: { - measuredValue: 50 * 100, - }, - }); - await switchEnpoint2.set({ - switch: { - currentPosition: 1, - }, - }); - switchEnpoint2.act((agent) => agent.switch.events.initialPress.emit({ newPosition: 1 }, agent.context)); - - // logEndpoint(EndpointServer.forEndpoint(matterbridgeDevice3)); - - /* - logEndpoint(EndpointServer.forEndpoint(this.matterServerNode)); - logEndpoint(EndpointServer.forEndpoint(matterbridgeDevice3)); - console.log('matterbridgeDevice3\n', matterbridgeDevice3); - console.log('matterbridgeDevice3.events\n', matterbridgeDevice3.events); - console.log('matterbridgeDevice3.events.identify\n', matterbridgeDevice3.eventsOf(IdentifyServer)); - console.log('matterbridgeDevice3.state\n', matterbridgeDevice3.state); - console.log('matterbridgeDevice3.state.temperatureMeasurement\n', matterbridgeDevice3.stateOf(TemperatureMeasurementServer)); - // matterbridgeDevice3.eventsOf(IdentifyServer); - // matterbridgeDevice3.events.identify.startIdentifying.on(() => log.notice('Run identify logic, ideally blink a light every 0.5s ...')); - */ - - this.showServerNodeQR(); - } - - async startChildbridge() { - // - } - - async startController() { - // - } - - /** - * Adds a bridged device to the Matterbridge. - * @param pluginName - The name of the plugin. - * @param device - The bridged device to add. - * @returns {Promise} - A promise that resolves when the storage process is started. - */ - async addBridgedDevice(pluginName: string, device: MatterbridgeDeviceV8): Promise { - log.info(`Adding bridged device ${dev}${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`); - } - - /** - * Loads a plugin and returns the corresponding MatterbridgePlatform instance. - * @param plugin - The plugin to load. - * @param start - Optional flag indicating whether to start the plugin after loading. Default is false. - * @param message - Optional message to pass to the plugin when starting. - * @returns A Promise that resolves to the loaded MatterbridgePlatform instance. - * @throws An error if the plugin is not enabled, already loaded, or fails to load. - */ - private async loadPlugin(plugin: RegisteredPlugin, start = false, message = ''): Promise { - if (!plugin.enabled) { - return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not enabled`)); - } - if (plugin.platform) { - return Promise.resolve(plugin.platform); - } - log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`); - try { - // Load the package.json of the plugin - const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8')); - // Resolve the main module path relative to package.json - const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main); - // Dynamically import the plugin - const pluginUrl = pathToFileURL(pluginEntry); - log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`); - const pluginInstance = await import(pluginUrl.href); - log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`); - - // Call the default export function of the plugin, passing this MatterBridge instance, the log and the config - if (pluginInstance.default) { - const config: PlatformConfig = await this.loadPluginConfig(plugin); - const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: TimestampFormat.TIME_MILLIS, logDebug: true }); - const platform = pluginInstance.default(this, log, config) as MatterbridgePlatform; - platform.name = packageJson.name; - platform.config = config; - platform.version = packageJson.version; - plugin.name = packageJson.name; - plugin.description = packageJson.description; - plugin.version = packageJson.version; - plugin.author = packageJson.author; - plugin.type = platform.type; - plugin.platform = platform; - plugin.loaded = true; - plugin.registeredDevices = 0; - plugin.addedDevices = 0; - // Save the updated plugin data in the node storage - // await this.nodeContext?.set('plugins', await this.getBaseRegisteredPlugins()); - - // await this.getPluginLatestVersion(plugin); - - log.info(`Loaded plugin ${plg}${plugin.name}${nf} type ${typ}${platform.type} ${db}(entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`); - if (start) this.startPlugin(plugin, message); // No await do it asyncronously - return Promise.resolve(platform); - } else { - plugin.error = true; - return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`)); - } - } catch (err) { - plugin.error = true; - return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`)); - } - } - - /** - * Starts a plugin. - * - * @param {RegisteredPlugin} plugin - The plugin to start. - * @param {string} [message] - Optional message to pass to the plugin's onStart method. - * @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting. - * @returns {Promise} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails. - */ - private async startPlugin(plugin: RegisteredPlugin, message?: string, configure = false): Promise { - if (!plugin.loaded || !plugin.platform) { - return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`)); - } - if (plugin.started) { - log.debug(`Plugin ${plg}${plugin.name}${db} already started`); - return Promise.resolve(); - } - log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`); - try { - plugin.platform - .onStart(message) - .then(() => { - plugin.started = true; - log.info(`Started plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`); - if (configure) this.configurePlugin(plugin); // No await do it asyncronously - return Promise.resolve(); - }) - .catch((err) => { - plugin.error = true; - return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`)); - }); - } catch (err) { - plugin.error = true; - return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`)); - } - } - - /** - * Configures a plugin. - * - * @param {RegisteredPlugin} plugin - The plugin to configure. - * @returns {Promise} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails. - */ - private async configurePlugin(plugin: RegisteredPlugin): Promise { - if (!plugin.loaded || !plugin.started || !plugin.platform) { - return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded (${plugin.loaded}) or not started (${plugin.started}) or not platform (${plugin.platform?.name})`)); - } - if (plugin.configured) { - log.info(`Plugin ${plg}${plugin.name}${nf} already configured`); - return Promise.resolve(); - } - log.info(`Configuring plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`); - try { - plugin.platform - .onConfigure() - .then(() => { - plugin.configured = true; - log.info(`Configured plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`); - // this.savePluginConfig(plugin); - return Promise.resolve(); - }) - .catch((err) => { - plugin.error = true; - return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`)); - }); - } catch (err) { - plugin.error = true; - return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`)); - } - } - - /** - * Loads the configuration for a plugin. - * If the configuration file exists, it reads the file and returns the parsed JSON data. - * If the configuration file does not exist, it creates a new file with default configuration and returns it. - * If any error occurs during file access or creation, it logs an error and rejects the promise with the error. - * - * @param plugin - The plugin for which to load the configuration. - * @returns A promise that resolves to the loaded or created configuration. - */ - private async loadPluginConfig(plugin: RegisteredPlugin): Promise { - const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`); - try { - await fs.access(configFile); - const data = await fs.readFile(configFile, 'utf8'); - const config = JSON.parse(data) as PlatformConfig; - // this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config); - log.debug(`Config file found: ${configFile}.`); - /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/ - config.name = plugin.name; - config.type = plugin.type; - return config; - } catch (err) { - if (err instanceof Error) { - const nodeErr = err as NodeJS.ErrnoException; - if (nodeErr.code === 'ENOENT') { - let config: PlatformConfig; - if (plugin.name === 'matterbridge-zigbee2mqtt') config = zigbee2mqtt_config; - else if (plugin.name === 'matterbridge-somfy-tahoma') config = somfytahoma_config; - else if (plugin.name === 'matterbridge-shelly') config = shelly_config; - else config = { name: plugin.name, type: plugin.type, unregisterOnShutdown: false }; - try { - await this.writeFile(configFile, JSON.stringify(config, null, 2)); - log.debug(`Created config file: ${configFile}.`); - // this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, config); - return config; - } catch (err) { - log.error(`Error creating config file ${configFile}: ${err}`); - return config; - } - } else { - log.error(`Error accessing config file ${configFile}: ${err}`); - return {}; - } - } - log.error(`Error loading config file ${configFile}: ${err}`); - return {}; - } - } - - /** - * Writes data to a file. - * - * @param {string} filePath - The path of the file to write to. - * @param {string} data - The data to write to the file. - * @returns {Promise} - A promise that resolves when the data is successfully written to the file. - */ - private async writeFile(filePath: string, data: string): Promise { - // Write the data to a file - await fs - .writeFile(`${filePath}`, data, 'utf8') - .then(() => { - log.debug(`Successfully wrote to ${filePath}`); - }) - .catch((error) => { - log.error(`Error writing to ${filePath}:`, error); - }); - } -} - -// node dist/matterbridgeV8.js MatterbridgeV8 -if (process.argv.includes('MatterbridgeV8')) { - const matterbridge = await MatterbridgeV8.create(); - - if (process.argv.includes('-bridge')) await matterbridge.startBridge(); - if (process.argv.includes('-childbridge')) await matterbridge.startChildbridge(); - if (process.argv.includes('-controller')) await matterbridge.startController(); - - process.on('SIGINT', async function () { - console.log('Caught interrupt signal'); - if (!matterbridge || !matterbridge.matterServerNode || !matterbridge.matterStorageContext) return; - await matterbridge.stopServerNode(matterbridge.matterStorageContext); - process.exit(); - }); -}