@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
137 lines (122 loc) • 5.15 kB
text/typescript
import { Category as BaseCat } from '../../Definitions/Global/Categories.js';
import { NodeInfo } from '../../Model/NodeInfo.js';
import { Family } from '../../Definitions/index.js';
import type { ISYDevice } from '../../ISYDevice.js';
import { NodeFactory } from '../NodeFactory.js';
import { ColorControl, ColorControlCluster, LevelControl, OnOff } from '@matter/main/clusters';
import { ClusterId, type ClusterType } from '@matter/types';
import { ISY, writeDebugFile } from '../../ISY.js';
import type { ISYDeviceNode } from '../ISYDeviceNode.js';
import { writeFile } from 'fs';
import { ColorHueSat, ColorTemperatureLight, ColorXY, DimmableLight, ExtendedColorLight, OnOffLevel } from './index.js';
import { OnOffLight } from './OnOffLight.js';
import { hasBitFlag, type ZigBeeClusterData } from './ZigbeeClusterData.js';
export function parseZigbeeClusterData(input: string): { [x: string]: ZigBeeClusterData<any> } {
const clusters: { [x: ClusterId]: ZigBeeClusterData<ClusterType> } = {};
const clusterRegex = /Cluster (\d+) 0x([0-9A-Fa-f]+) ([\w_]+)/g;
const dataRegex = /(\w+)\s+DATA\s+devices\.\d+\.endpoints\.(\d+)\.clusters\.\d+\.data\.?([\w\.]*)\s*=\s*(.+)/gm;
let currentCluster: ZigBeeClusterData<ClusterType> | null = null;
input?.split('\n').forEach((line) => {
clusterRegex.lastIndex = 0; // Reset lastIndex for clusterRegex
const clusterMatch = clusterRegex.exec(line);
if (clusterMatch) {
if (currentCluster) clusters[currentCluster.clusterName] = currentCluster;
currentCluster = {
clusterId: ClusterId(parseInt(clusterMatch[1], 10)),
clusterName: clusterMatch[3] as Uppercase<string>,
endpoint: 0, // Placeholder, will be updated later
data: {}
};
return;
}
dataRegex.lastIndex = 0; // Reset lastIndex for dataRegex
const dataMatch = dataRegex.exec(line);
if (dataMatch && currentCluster) {
const [, type, endpoint, key, value] = dataMatch;
currentCluster.endpoint = parseInt(endpoint, 10);
currentCluster.data[key] = parseValue(type, value);
}
});
if (currentCluster) {
clusters[currentCluster?.clusterName] = currentCluster;
}
return clusters;
}
function parseValue(type: string, value: string): any {
switch (type) {
case 'Boolean':
return value.trim() === 'True';
case 'Integer':
return parseInt(value, 10);
case 'Binary':
return value.match(/\[([0-9A-Fa-f]+)\]/)?.[1] || value;
case 'Empty':
return null;
default:
return value.trim();
}
}
export class ZigBeeDeviceFactory {
public static async create(isy: ISY, nodeInfo: NodeInfo<Family.ZigBee>): Promise<ISYDevice<Family.ZigBee, any, any, any>> {
let clusterInfo = await isy.sendRequest(`zmatter/zigbee/dh/node/${nodeInfo.address}/cluster/0`, { trailingSlash: false });
if (clusterInfo) {
if (isy.isDebugEnabled) {
await writeDebugFile(clusterInfo, `${nodeInfo.address}_ClusterData.txt`, isy.logger, isy.storagePath);
}
let cd = parseZigbeeClusterData(clusterInfo);
//@ts-expect-error
let cc = cd['COLOR_CONTROL'] as ZigBeeClusterData<ColorControl.Cluster>;
//@ts-expect-error
let lc = cd['LEVEL_CONTROL'] as ZigBeeClusterData<LevelControl.Cluster>;
//@ts-expect-error
let os = cd['ON_OFF_SWITCH'] as ZigBeeClusterData<OnOff.Cluster>;
if (cc) {
if (hasBitFlag(cc.data.colorCapabilities, ColorControl.ColorCapabilities.xy)) {
return new ExtendedColorLight(isy, nodeInfo, cd);
} else if (hasBitFlag(cc.data.colorCapabilities, ColorControl.ColorCapabilities.colorTemperature)) {
return new ColorTemperatureLight(isy, nodeInfo);
}
} else if (lc && os) return new DimmableLight(isy, nodeInfo);
else if (os) return new OnOffLight(isy, nodeInfo);
}
}
// public static buildDeviceMap() {
// var fams = new Map<Family, FamilyDef<Family>>();
// s.forEach((item) => {
// var id = item.id as Family;
// fams.set(id, { id: item.id, description: item.description, name: item.name, categories: new Map<string, CategoryDef<typeof id>>() });
// var famDef = fams[id] as FamilyDef<Family>;
// item.categories.forEach((element) => {
// var catDef = { id: element.id, name: element.name, devices: new Map<string, DeviceDef<Family>> };
// element.devices.forEach(
// device => {
// const r = this.getDeviceDetails({
// family: item.id,
// type: `${element.id}.${device.id}.0.0`,
// address: '0 0 0 1',
// nodeDefId: '',
// enabled: undefined,
// pnode: undefined,
// name: '',
// startDelay: 0,
// hint: '',
// endDelay: 0,
// wattage: 0,
// dcPeriod: 0
// });
// if (!r.unsupported) {
// device.name = r.name;
// device.modelNumber = r.modelNumber;
// device.class = r.class.name;
// }
// catDef.devices.set(`${device.id}`, { id: device.id, modelNumber: device.modelNumber, name: device.name, class: r.class });
// }
// );
// famDef.categories.set(element.name, catDef);
// element.devices = element.devices.sort((a, b) => a.id - b.id);
// });
// }
// );
// writeFileSync("DeviceMapClean.json", JSON.stringify(fams));
// }
}