UNPKG

matterbridge-hass

Version:
234 lines (233 loc) 12.6 kB
import { createHash, randomBytes } from 'node:crypto'; import { colorTemperatureLight, colorTemperatureSwitch, dimmableLight, dimmableOutlet, dimmableSwitch, MatterbridgeEndpoint, onOffLight, onOffOutlet, onOffSwitch, } from 'matterbridge'; import { db, debugStringify, idn, ign, rs } from 'matterbridge/logger'; import { VendorId, ClusterRegistry } from 'matterbridge/matter/types'; import { BridgedDeviceBasicInformation } from 'matterbridge/matter/clusters'; import { BridgedDeviceBasicInformationServer } from 'matterbridge/matter/behaviors'; import { CYAN } from 'node-ansi-logger'; export function getClusterServerObj(clusterId, type, options) { return { id: clusterId, type, options }; } export class MutableDevice { mutableDevice = new Map(); matterbridge; deviceName; serialNumber; vendorId; vendorName; productName; softwareVersion; softwareVersionString; hardwareVersion; hardwareVersionString; composedType = undefined; constructor(matterbridge, deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productName = 'Matterbridge Device', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString) { this.matterbridge = matterbridge; this.deviceName = deviceName; this.serialNumber = serialNumber ?? '0x' + randomBytes(8).toString('hex'); this.vendorId = VendorId(vendorId); this.vendorName = vendorName; this.productName = productName; this.softwareVersion = softwareVersion ?? parseInt(matterbridge.matterbridgeVersion.replace(/\D/g, '')); this.softwareVersionString = softwareVersionString ?? matterbridge.matterbridgeVersion; this.hardwareVersion = hardwareVersion ?? parseInt(this.matterbridge.systemInformation.nodeVersion.replace(/\D/g, '')); this.hardwareVersionString = hardwareVersionString ?? this.matterbridge.systemInformation.nodeVersion; this.initializeEndpoint(''); } has(endpoint) { return this.mutableDevice.has(endpoint); } get(endpoint = '') { if (this.mutableDevice.get(endpoint) === undefined) throw new Error(`Device ${endpoint} is not defined`); return this.mutableDevice.get(endpoint); } getEndpoint(endpoint = '') { if (this.mutableDevice.get(endpoint)?.endpoint === undefined) throw new Error(`Device ${endpoint} endpoint is not defined`); return this.mutableDevice.get(endpoint)?.endpoint; } initializeEndpoint(endpoint) { if (!this.mutableDevice.has(endpoint)) { this.mutableDevice.set(endpoint, { friendlyName: endpoint, tagList: [], deviceTypes: [], clusterServersIds: [], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [], }); } return this.mutableDevice.get(endpoint); } setFriendlyName(endpoint, friendlyName) { const device = this.initializeEndpoint(endpoint); device.friendlyName = friendlyName; return this; } addTagLists(endpoint, ...tagList) { const device = this.initializeEndpoint(endpoint); device.tagList.push(...tagList); } addDeviceTypes(endpoint, ...deviceTypes) { const device = this.initializeEndpoint(endpoint); device.deviceTypes.push(...deviceTypes); } addClusterServerIds(endpoint, ...clusterServerIds) { const device = this.initializeEndpoint(endpoint); device.clusterServersIds.push(...clusterServerIds); } addClusterServerObjs(endpoint, ...clusterServerObj) { const device = this.initializeEndpoint(endpoint); device.clusterServersObjs.push(...clusterServerObj); } createUniqueId(param1, param2, param3, param4) { const hash = createHash('md5'); hash.update(param1 + param2 + param3 + param4); return hash.digest('hex'); } addBridgedDeviceBasicInformationClusterServer() { const device = this.getEndpoint(''); device.log.logName = this.deviceName; device.deviceName = this.deviceName; device.serialNumber = this.serialNumber; device.uniqueId = this.createUniqueId(this.deviceName, this.serialNumber, this.vendorName, this.productName); device.productId = undefined; device.productName = this.productName; device.vendorId = this.vendorId; device.vendorName = this.vendorName; device.softwareVersion = this.softwareVersion; device.softwareVersionString = this.softwareVersionString; device.hardwareVersion = this.hardwareVersion; device.hardwareVersionString = this.hardwareVersionString; this.addClusterServerObjs('', getClusterServerObj(BridgedDeviceBasicInformation.Cluster.id, BridgedDeviceBasicInformationServer, { vendorId: this.vendorId, vendorName: this.vendorName.slice(0, 32), productName: this.productName.slice(0, 32), productLabel: this.deviceName.slice(0, 64), nodeLabel: this.deviceName.slice(0, 32), serialNumber: this.serialNumber.slice(0, 32), uniqueId: this.createUniqueId(this.deviceName, this.serialNumber, this.vendorName, this.productName), softwareVersion: this.softwareVersion, softwareVersionString: this.softwareVersionString.slice(0, 64), hardwareVersion: this.hardwareVersion, hardwareVersionString: this.hardwareVersionString.slice(0, 64), reachable: true, })); } async create() { await this.createMainEndpoint(); await this.createChildEndpoints(); for (const [endpoint] of this.mutableDevice) { await this.createClusters(endpoint); } const mainDevice = this.mutableDevice.get(''); return mainDevice.endpoint; } removeDuplicateAndSupersetDeviceTypes() { for (const device of this.mutableDevice.values()) { const deviceTypesMap = new Map(); device.deviceTypes.forEach((deviceType) => { deviceTypesMap.set(deviceType.code, deviceType); }); if (deviceTypesMap.has(onOffSwitch.code) && deviceTypesMap.has(dimmableSwitch.code)) deviceTypesMap.delete(onOffSwitch.code); if (deviceTypesMap.has(dimmableSwitch.code) && deviceTypesMap.has(colorTemperatureSwitch.code)) deviceTypesMap.delete(dimmableSwitch.code); if (deviceTypesMap.has(onOffOutlet.code) && deviceTypesMap.has(dimmableOutlet.code)) deviceTypesMap.delete(onOffOutlet.code); if (deviceTypesMap.has(onOffLight.code) && deviceTypesMap.has(dimmableLight.code)) deviceTypesMap.delete(onOffLight.code); if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code)) deviceTypesMap.delete(dimmableLight.code); device.deviceTypes = Array.from(deviceTypesMap.values()); } } async createMainEndpoint() { this.removeDuplicateAndSupersetDeviceTypes(); const mainDevice = this.mutableDevice.get(''); mainDevice.friendlyName = this.deviceName; mainDevice.endpoint = new MatterbridgeEndpoint(mainDevice.deviceTypes, { uniqueStorageKey: this.deviceName }, true); mainDevice.endpoint.log.logName = this.deviceName; return mainDevice.endpoint; } async createChildEndpoint(endpoint) { this.removeDuplicateAndSupersetDeviceTypes(); const mainDevice = this.mutableDevice.get(''); if (!mainDevice.endpoint) throw new Error('Main endpoint is not defined. Call createMainEndpoint() first.'); const device = this.mutableDevice.get(endpoint); if (!device) throw new Error(`Device ${endpoint} is not defined.`); device.endpoint = mainDevice.endpoint.addChildDeviceType(endpoint, device.deviceTypes, device.tagList.length ? { tagList: device.tagList } : {}, true); device.endpoint.log.logName = device.friendlyName; return device.endpoint; } async createChildEndpoints() { this.removeDuplicateAndSupersetDeviceTypes(); const mainDevice = this.mutableDevice.get(''); if (!mainDevice.endpoint) throw new Error('Main endpoint is not defined. Call createMainEndpoint() first.'); for (const [endpoint, device] of Array.from(this.mutableDevice.entries()).filter(([endpoint]) => endpoint !== '')) { device.endpoint = mainDevice.endpoint.addChildDeviceType(endpoint, device.deviceTypes, device.tagList.length ? { tagList: device.tagList } : {}, true); } return this; } removeDuplicateClusterServers() { for (const device of this.mutableDevice.values()) { const deviceClusterServersIdMap = new Map(); device.clusterServersIds.forEach((clusterServerId) => { deviceClusterServersIdMap.set(clusterServerId, clusterServerId); }); const deviceClusterServersObjMap = new Map(); device.clusterServersObjs.forEach((clusterServerObj) => { deviceClusterServersIdMap.delete(clusterServerObj.id); deviceClusterServersObjMap.set(clusterServerObj.id, clusterServerObj); }); device.clusterServersIds = Array.from(deviceClusterServersIdMap.values()); device.clusterServersObjs = Array.from(deviceClusterServersObjMap.values()); } } async createClusters(endpoint) { this.removeDuplicateClusterServers(); if (endpoint === '') { const mainDevice = this.mutableDevice.get(endpoint); if (!mainDevice.endpoint) throw new Error('Main endpoint is not defined'); this.addBridgedDeviceBasicInformationClusterServer(); for (const clusterServerObj of mainDevice.clusterServersObjs) { mainDevice.endpoint.behaviors.require(clusterServerObj.type, clusterServerObj.options); } mainDevice.endpoint.addClusterServers(mainDevice.clusterServersIds); mainDevice.endpoint.addRequiredClusterServers(); if (this.composedType) await mainDevice.endpoint.addFixedLabel('composed', this.composedType); return this; } for (const [, device] of Array.from(this.mutableDevice.entries()).filter(([e]) => e === endpoint)) { if (!device.endpoint) throw new Error('Child endpoint is not defined'); for (const clusterServerObj of device.clusterServersObjs) { device.endpoint.behaviors.require(clusterServerObj.type, clusterServerObj.options); } device.endpoint.addClusterServers(device.clusterServersIds); device.endpoint.addRequiredClusterServers(); } return this; } logMutableDevice() { this.matterbridge.log.debug(`Device ${idn}${this.deviceName}${rs}${db} serial number ${CYAN}${this.serialNumber}${rs}${db} vendor id ${CYAN}${this.vendorId}${rs}${db} ` + `vendor name ${CYAN}${this.vendorName}${rs}${db} product name ${CYAN}${this.productName}${rs}${db} software version ${CYAN}${this.softwareVersion}${rs}${db} ` + `software version string ${CYAN}${this.softwareVersionString}${rs}${db} hardware version ${CYAN}${this.hardwareVersion}${rs}${db} hardware version string ${CYAN}${this.hardwareVersionString}`); for (const [endpoint, device] of this.mutableDevice) { const deviceTypes = device.deviceTypes.map((d) => '0x' + d.code.toString(16) + '-' + d.name); const clusterServersIds = device.clusterServersIds.map((clusterServerId) => '0x' + clusterServerId.toString(16) + '-' + ClusterRegistry.get(clusterServerId)?.name); const clusterServersObjsIds = device.clusterServersObjs.map((clusterServerObj) => '0x' + clusterServerObj.id.toString(16) + '-' + ClusterRegistry.get(clusterServerObj.id)?.name); this.matterbridge.log.debug(`- endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => friendlyName ${CYAN}${device.friendlyName}${db} ` + `${db}tagList: ${debugStringify(device.tagList)}${db} deviceTypes: ${debugStringify(deviceTypes)}${db} ` + `clusterServersIds: ${debugStringify(clusterServersIds)}${db} clusterServersObjs: ${debugStringify(clusterServersObjsIds)}${db}`); } return this; } }