UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

411 lines (375 loc) 11.9 kB
import { merge } from 'moderndash'; import type { StandardDrivers } from '../Definitions/Global/Drivers.js'; import { Family } from '../Definitions/Global/Families.js'; import '../Utils.js'; export enum NLSRecordType { Generic = 'GEN', Driver = 'ST', Command = 'CMD', NodeDef = 'ND', NodeDefNLS = 'NDN', DeviceInfo = 'DEV', CommandParameter = 'CMDP', CommandParameterNLS = 'CMDPN', LinkProtocol = 'LNKP', LinkParameter = 'LNKD', Index = 'IX', Other = 'OTHER', Program = 'PGM' } export type NLSRecordTypeMap = { GEN?: NLSGenericRecord[]; ST?: NLSDriverRecord[]; CMD?: NLSCommandRecord[]; ND?: { type: NLSRecordType.NodeDef; nodeDefId: string; property: string; meta: string; value: string }[]; NDN?: { type: NLSRecordType.NodeDefNLS; nlsId: string; property: string; meta: string; value: string }[]; DEV?: { type: NLSRecordType.DeviceInfo; deviceCode: string; property: string; meta: string; value: string }[]; CMDP?: NLSCommandParameterRecord[]; CMDPN?: NLSCommandParameterRecord[]; LNKP?: { type: NLSRecordType.LinkProtocol; protocol: string; property: string; meta: string; value: string }[]; LNKD?: { type: NLSRecordType.LinkParameter; parameter: string; property: string; meta: string; value: string }[]; IX?: { type: NLSRecordType.Index; indexType: string; indexValue: number; value: string }[]; OTHER?: { type: NLSRecordType.Other; key: string; value: string }[]; PGM?: { type: NLSRecordType.Program; key: string; value: string }[]; }; export interface NLSRecord<T extends NLSRecordType> { type: T; value: string; nlsId?: string; } export class NLSBaseRecord<T extends NLSRecordType> implements NLSRecord<T> { type: T; key: string; value: string; } interface NLSRecordMap { [key: string]: NLSRecord<NLSRecordType>; } export abstract class NLSNodeDefFilteredRecord<T extends NLSRecordType> implements NLSRecord<T> { readonly type: T; nlsId?: string; readonly value: string; meta: string; constructor(type: T, tokens: string[], value: string) { this.type = type; this.value = value; } parseKey(tokens: string[]) { this.nlsId = tokens.pop() ?? 'Generic'; this.meta = tokens.length > 0 ? tokens.join('-') : undefined; } } export class NLSCommandRecord extends NLSNodeDefFilteredRecord<NLSRecordType.Command> { control: string; property: string; constructor(tokens: string[], value: string) { super(NLSRecordType.Command, tokens, value); this.parseKey(tokens); } override parseKey(tokens: string[]) { this.property = tokens.pop(); this.control = tokens.pop(); super.parseKey(tokens); } } export class NLSGenericRecord extends NLSNodeDefFilteredRecord<NLSRecordType.Generic> { control: string; property: string; constructor(tokens: string[], value: string) { super(NLSRecordType.Generic, tokens, value); this.parseKey(tokens); } override parseKey(tokens: string[]) { this.property = tokens.pop() ?? 'NAME'; this.control = tokens.pop(); super.parseKey(tokens); //this.property = tokens[1]; } } export class NLSCommandParameterRecord extends NLSNodeDefFilteredRecord<NLSRecordType.CommandParameter | NLSRecordType.CommandParameterNLS> { control: string; property: string; editorId: string; constructor(type: NLSRecordType.CommandParameter | NLSRecordType.CommandParameterNLS, tokens: string[], value: string) { super(type, tokens, value); this.parseKey(tokens); } override parseKey(tokens: string[]) { this.property = tokens.pop(); this.control = tokens.pop(); if (this.type == NLSRecordType.CommandParameter) { this.editorId = tokens.pop() ?? undefined; } super.parseKey(tokens); } } export class NLSDriverRecord extends NLSNodeDefFilteredRecord<NLSRecordType.Driver> { control: StandardDrivers; property: string; constructor(tokens: string[], value: string) { super(NLSRecordType.Driver, tokens, value); this.parseKey(tokens); } override parseKey(tokens: string[]) { this.property = tokens.pop(); this.control = tokens.pop() as StandardDrivers; super.parseKey(tokens); } } export const NLSIndexMap: Map<Family, { [x: string]: { [y: number]: string } }> = new Map(); export const NLSTranslations: Map<Family, { [y: string]: string }> = new Map(); const StdTranslations = { ONOFF: 'OnOff', ZY: 'ZWave', ZB: 'ZigBee', ZM: 'ZMatter', BOOL: 'Boolean', L255: 'Level255', PCT: 'Percent', DIR: 'Direction', SIR: 'Siren', MD: 'Mode', LVL: 'Level', IX: 'Index', OPTS: 'Options', LST: 'List', TMR: 'Timer', BRD: 'Bridge', CFN: 'Configuration', PWR: 'Power', VOLT: 'Voltage', CURR: 'Current', BATLVL: 'BatteryLevel', BAT: 'Battery', TEMP: 'Temperature', HUM: 'Humidity', LUM: 'Luminance', RR: 'RampRate', FL: 'FanLevel', BL: 'Backlight', ST: 'State', STS: 'Status', ERR: 'Error', PARAM: 'Parameter', LEN: 'Length', OL: 'OnLevel', DLY: 'Delay', '+': 'With', '(': '', ')': '', TVOL: 'ToneVolume', VIB: 'Vibration', LGT: 'Light', SIRMD: 'SirenMode', SIRTM: 'SirenTime', SIRVL: 'SirenVolume', SIL: 'Silent', ALRT: 'Alert', TF: 'TrueFalse', FTU: 'TrueFalseUnknown' }; NLSTranslations.set(Family.Generic, StdTranslations); export function addToIndexMap<T extends Family>(family: T, record: { type: NLSRecordType; indexType: string; indexValue: number; value: string; comment: string }) { if (!NLSIndexMap.has(family)) { NLSIndexMap.set(family, {}); } const indexMap = NLSIndexMap.get(family); if (!indexMap[record.indexType]) { indexMap[record.indexType] = {}; } indexMap[record.indexType][record.indexValue] = record.value; } export function addToTranslationMap<T extends Family>(family: T, record: NLSGenericRecord | NLSCommandRecord | NLSDriverRecord) { if (!NLSTranslations.has(family)) { NLSTranslations.set(family, {}); } const translationMap = NLSTranslations.get(family); if (record.property === 'NAME') { if (record.nlsId == 'Generic') { if (record instanceof NLSGenericRecord) translationMap[record.control] = record.value; else if (record instanceof NLSDriverRecord) translationMap[record.control] = record.value; else if (record instanceof NLSCommandRecord) translationMap[record.control] = record.value; } //translationMap[record.key] = record.value; } } export function applyTranslations(family: Family, value: string): string { if (value.includes('_')) { return value .split('_') .map((v) => applyTranslations(family, v)) .join('_'); } value = value.trim(); if (family !== Family.Generic) { value = applyTranslations(Family.Generic, value); } if (NLSRecordMap.has(family)) { const map = NLSTranslations.get(family); for (const key in map) { if (value == key) { return map[key]; } } } return value; } export function createMap<T extends Family>(content: string, family: T): { [y: string]: NLSRecordTypeMap } { const map: { [y: string]: NLSRecordTypeMap } = {}; NLSRecordMap.delete(family); for (const record of parseNLSContent(content, family)) { const key = record.nlsId ?? 'Generic'; if (!map[key]) { map[key] = {}; } if (map[key][record.type]) { map[key][record.type].push(record as any); } else { map[key][record.type] = [record as any]; } } NLSRecordMap.set(family, map); return map; } export const NLSRecordMap: Map<Family, { [y: string]: NLSRecordTypeMap }> = new Map(); export function parseNLSContent<T extends Family>(content: string, family: T): NLSRecord<NLSRecordType>[] { const lines = content.split('\n'); const result = []; const NLSRecords = new Array<NLSRecord<NLSRecordType>>(); let currentComment = ''; lines.forEach((line) => { line = line.trim(); if (line === '') { return; // Skip comments and empty lines } else if (line.startsWith('#')) { currentComment = line.trim(); } let [key, value] = line.split('='); if (key && value) { key = key.trim(); const tokens = key.split('-'); value = value.trim(); if (tokens.length > 0) { if (tokens[0].startsWith('IX')) { const recordType = NLSRecordType.Index; const indexType = tokens.shift(); const indexValue = tokens.shift(); const indexNLS = { comment: currentComment, type: recordType, value: value, indexType: indexType, indexValue: Number(indexValue) }; NLSRecords.push(indexNLS); addToIndexMap(family, indexNLS); } else { const recordType = tokens.shift() as NLSRecordType; switch (recordType) { case NLSRecordType.Generic: const genericRecord = new NLSGenericRecord(tokens, value); NLSRecords.push(genericRecord); addToTranslationMap(family, genericRecord); break; case NLSRecordType.Driver: const driverRecord = new NLSDriverRecord(tokens, value); NLSRecords.push(driverRecord); addToTranslationMap(family, driverRecord); break; case NLSRecordType.Command: const commandRecord = new NLSCommandRecord(tokens, value); NLSRecords.push(commandRecord); addToTranslationMap(family, commandRecord); break; case NLSRecordType.NodeDef: const nodeDefRecord = { type: recordType, value: value, nodeDefId: tokens.shift(), property: tokens.shift(), meta: tokens.length > 0 ? tokens.join('-') : undefined }; NLSRecords.push(nodeDefRecord); break; case NLSRecordType.NodeDefNLS: const nodeDefNameRecord = { type: recordType, value: value, nlsId: tokens.shift(), property: tokens.shift(), meta: tokens.length > 0 ? tokens.join('-') : undefined }; NLSRecords.push(nodeDefNameRecord); break; case NLSRecordType.DeviceInfo: const deviceInfoRecord = { type: recordType, nlsId: tokens.shift(), property: tokens.shift(), meta: tokens.join('-'), value }; NLSRecords.push(deviceInfoRecord); break; case NLSRecordType.CommandParameterNLS: case NLSRecordType.CommandParameter: const commandParameterRecord = new NLSCommandParameterRecord(recordType, tokens, value); NLSRecords.push(commandParameterRecord); break; case NLSRecordType.LinkProtocol: const linkProtocolRecord = { type: recordType, protocol: tokens.shift(), property: tokens.shift(), meta: tokens.length > 0 ? tokens.join('-') : undefined, value }; NLSRecords.push(linkProtocolRecord); break; case NLSRecordType.LinkParameter: const linkParameterRecord = { type: recordType, parameter: tokens.shift(), property: tokens.shift(), meta: tokens.join('-'), value }; NLSRecords.push(linkParameterRecord); break; case NLSRecordType.Program: const programRecord = { type: recordType, key: tokens.join('-'), value }; NLSRecords.push(programRecord); break; default: const otherRecord = { type: recordType, key: tokens.join('-'), value }; NLSRecords.push(otherRecord); break; } } const key = tokens; } result.push({ tokens, value: value.trim() }); } }); return NLSRecords; } export namespace NLS { export function get<T extends Family>(family: T, nlsId: string) { if (NLSRecordMap.has(family)) { return merge(NLSRecordMap.get(Family.Generic)[nlsId], NLSRecordMap.get(family)[nlsId]); } } export const Map = NLSRecordMap; } export namespace Translation { export const apply = applyTranslations; export const Map = NLSTranslations; } export namespace IndexDef { export function get<T extends Family>(family: T, indexType: string): { [y: number]: string } { if (NLSIndexMap.has(family)) { const indexMap = NLSIndexMap.get(family); if (indexMap[indexType]) { return indexMap[indexType]; } } if (family != Family.Generic) { return get(Family.Generic, indexType); } } export const Map = NLSIndexMap; }