UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

266 lines (240 loc) 8.34 kB
import { DoorLock, FanControl } from '@matter/main/clusters'; import { UnitOfMeasure } from './Definitions/Global/UOM.js'; import { FanLevel, RampRate } from './Definitions/Insteon/index.js'; import { Paths } from 'type-fest'; import { Insteon } from './ISY.js'; import { type StringKeys } from './Utils.js'; let BooleanPercentage: Converter<boolean, number>; let NullConverter: Converter<any, any>; { to: (value: any) => value; from: (value: any) => value; } const StandardConverters = { Boolean: { LevelFrom0To255: { to: (value: boolean) => (value ? 255 : 0), from: (value: number) => value > 0 }, Percent: { to: (value: boolean) => (value ? 100 : 0), from: (value: number) => value > 0 } }, Fahrenheit: { Celsius: { to: (value: number) => (value - 32) / 1.8, from: (value: number) => value * 1.8 + 32 }, LevelFrom0To255: { to: (value: number) => Math.round(value / 2.55), from: (value: number) => Math.round(value * 2.55) } }, Celsius: { LevelFrom0To255: { to: (value: number) => Math.round(value / 2.55), from: (value: number) => Math.round(value * 2.55) } }, LevelFrom0To255: { Percent: { to: (value: number): number => { if (value === 0) return 0; if (value === 255) return 100; return Math.round((value * 100) / 255); }, from: (value: number): number => { if (value === 0) return 0; if (value === 100) return 255; return Math.round((value * 255) / 100); } } } }; //StandardConverters.Percent.LevelFrom0To255 = invert(StandardConverters.LevelFrom0To255.Percent); //StandardConverters.LevelFrom0To255.Boolean = invert(StandardConverters.Boolean.LevelFrom0To255); export const StdConverterRegistry = new Map<UnitOfMeasure | string, Map<UnitOfMeasure | string, Converter<any, any>>>(); export const ConverterRegistry = new Map<string, Converter<any, any>>(); function registerConverters() { for (const from in StandardConverters) { for (const to in StandardConverters[from]) { registerConverter(from, to, StandardConverters[from][to] as Converter<any, any>); registerConverter(to, from, invert(StandardConverters[from][to]) as Converter<any, any>); } } for (const from in Converter.Matter) { for (const to in Converter.Matter[from]) { registerConverter(from, to, Converter.Matter[from][to] as Converter<any, any>); registerConverter(to, from, invert(Converter.Matter[from][to]) as Converter<any, any>); } } } function registerConverter( from: keyof typeof StandardConverters | keyof typeof Converter.Matter | Paths<typeof StandardConverters> | Paths<typeof Converter.Matter> | string, to: keyof typeof StandardConverters | keyof typeof Converter.Matter | string, converter: Converter<any, any> ) { if (!StdConverterRegistry.has(from)) { StdConverterRegistry.set(from, new Map()); } StdConverterRegistry.get(from).set(to, converter); let key = from + '.' + to; if (!ConverterRegistry.has(key)) { ConverterRegistry.set(key, converter); } } export namespace Converter { export const Standard: typeof StandardConverters = StandardConverters; //TODO: Migrate to isy matter //TODO: Simplify export const Matter = { LevelFrom0To255: { LightingLevel: { from: (value: number) => value === 1 ? 0 : value === 254 ? 255 : value, to: (value: number) => value === 0 ? 1 : value === 255 ? 254 : value } }, Percent: { LightingLevel: { from: (value: number) => value === 1 ? 0 : value === 254 ? 100 : Math.round((value / 254) * 100), to: (value: number) => value === 0 ? 1 : value === 100 ? 254 : Math.round((value / 100) * 254) }, InverseBoolean: { from: (value: boolean) => (value ? 0 : 100), to: (value: number) => (value > 0 ? false : true) } }, InsteonLock: { LockState: { from: (value: DoorLock.LockState) => (value == DoorLock.LockState.Locked ? Insteon.Lock.Locked : Insteon.Lock.Unlocked), to: (value: Insteon.Lock) => (value == Insteon.Lock.Locked ? DoorLock.LockState.Locked : DoorLock.LockState.Unlocked) } }, Boolean: { LightingLevel: { from: (value: number) => value > 0, to: (value: boolean) => (value ? 254 : 0) } }, RampRate: { Deciseconds: { from: (value: number) => RampRate.from.seconds(value / 10), to: (value: RampRate) => RampRate.to.seconds(value) * 10 }, Seconds: { from: (value: number) => RampRate.from.seconds(value), to: (value: RampRate) => RampRate.to.seconds(value) } }, FanLevel: { FanMode: { from: (value: FanControl.FanMode) => { switch (value) { case FanControl.FanMode.Low: return FanLevel.Low; case FanControl.FanMode.Medium: return FanLevel.Medium; case FanControl.FanMode.High: return FanLevel.High; case FanControl.FanMode.Off: return FanLevel.Off; default: throw new Error('Invalid Fan Mode: ' + FanControl.FanMode[value]); } }, to: (value: FanLevel) => { switch (value) { case FanLevel.Low: return FanControl.FanMode.Low; case FanLevel.Medium: return FanControl.FanMode.Medium; case FanLevel.High: return FanControl.FanMode.High; case FanLevel.Off: return FanControl.FanMode.Off; } } } } }; export type ConverterTypes = `${StringKeys<typeof StandardConverters>}`; type StandardConverters = Paths<typeof StandardConverters, { maxRecursionDepth: 1; bracketNotation: false }>; type Invert<K extends string> = K extends `${infer T}.${infer U}` ? `${U}.${T}` | K : never; //export type StandardConvertersList = PathsWithLimit<typeof StandardConverters, 1>; export type MatterISYConvertibleTypes = `${StringKeys<(typeof Matter)[`${keyof typeof Matter}`]>}`; export type ISYMatterConvertibleTypes = `${StringKeys<typeof Matter>}`; export type MatterConverters = Paths<typeof Matter, { maxRecursionDepth: 1; bracketNotation: false }>; export type KnownConverters = Invert<StandardConverters> | Invert<MatterConverters>; const cache: { [x: string]: Converter<any, any> } = {}; export function get(label: KnownConverters): Converter<any, any>; export function get(from: UnitOfMeasure, to: UnitOfMeasure): Converter<any, any>; export function get(from: ConverterTypes, to: ConverterTypes); export function get(from: UnitOfMeasure, to: UnitOfMeasure); export function get(from: MatterISYConvertibleTypes, to: ISYMatterConvertibleTypes); export function get(to: ISYMatterConvertibleTypes, from: MatterISYConvertibleTypes); export function get(from: UnitOfMeasure | `${keyof typeof UnitOfMeasure}` | string, to?: UnitOfMeasure | `${keyof typeof UnitOfMeasure}` | string): Converter<any, any> { if (to === undefined) { return ConverterRegistry.get(from as string) ?? NullConverter; } if (cache[`${from}.${to}`]) { return cache[`${from}.${to}`]; } else if (cache[`${to}.${from}`]) { cache[`${from}.${to}`] = invert(cache[`${to}.${from}`]); return cache[`${from}.${to}`]; } let isString = typeof from === 'string'; let fuom = isString ? from : UnitOfMeasure[from]; if (to) { let tuom = typeof to === 'string' ? to : UnitOfMeasure[to]; if (StdConverterRegistry.has(fuom)) { if (StdConverterRegistry.get(fuom).has(tuom)) { return StdConverterRegistry.get(fuom).get(tuom); } } } return NullConverter; } export function convert<F, T>(from: UnitOfMeasure, to: UnitOfMeasure, value: F): T { const converter = get(from, to); if (converter) { return converter.to(value); } return null; } export function register<F, T>(from: UnitOfMeasure, to: UnitOfMeasure, converter: Converter<F, T>) { registerConverter(UnitOfMeasure[from], UnitOfMeasure[to], converter); } export function registerAll(converters: Record<string, Record<string, Converter<any, any>>>) { for (const from in converters) { for (const to in converters[from]) { registerConverter(from, to, converters[from][to]); } } } } registerConverters(); export interface Converter<F, T> { // #region Properties (2) from: (value: F) => T; to: (value: T) => F; } //onst D: d = 'ST'; //type DriverLabel = Values<IdentityOf<DriverType>>; export function invert<F, T>(converter: Converter<F, T>): Converter<T, F> { return { from: converter.to, to: converter.from }; }