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 PathsWithLimit, type StringKeys } from './Utils.js'; let BooleanPercentage: Converter; let NullConverter: Converter; { 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>>(); export const ConverterRegistry = new Map>(); function registerConverters() { for (const from in StandardConverters) { for (const to in StandardConverters[from]) { registerConverter(from, to, StandardConverters[from][to] as Converter); registerConverter(to, from, invert(StandardConverters[from][to]) as Converter); } } for (const from in Converter.Matter) { for (const to in Converter.Matter[from]) { registerConverter(from, to, Converter.Matter[from][to] as Converter); registerConverter(to, from, invert(Converter.Matter[from][to]) as Converter); } } } function registerConverter( from: | keyof typeof StandardConverters | keyof typeof Converter.Matter | Paths | Paths | string, to: keyof typeof StandardConverters | keyof typeof Converter.Matter | string, converter: Converter ) { 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), }, }, ZWaveLock: { LockState: { from: (value: DoorLock.LockState) => (value == DoorLock.LockState.Locked ? 101 : 0), to: (value: Insteon.Lock) => (value == 0 ? DoorLock.LockState.Unlocked : DoorLock.LockState.Locked), }, }, 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}`; type StandardConverters = Paths< typeof StandardConverters, { maxRecursionDepth: 1; bracketNotation: false; depth: 1 } >; type Invert = K extends `${infer T}.${infer U}` ? `${U}.${T}` | K : never; //export type StandardConvertersList = PathsWithLimit; export type MatterISYConvertibleTypes = `${StringKeys<(typeof Matter)[`${keyof typeof Matter}`]>}`; export type ISYMatterConvertibleTypes = `${StringKeys}`; export type MatterConverters = Paths< typeof Matter, { maxRecursionDepth: 1; bracketNotation: false; leavesOnly: true } >; export type KnownConverters = | StandardConverters | MatterConverters | Invert | Invert; const cache: { [x: string]: Converter } = {}; export function get(label: KnownConverters): Converter; export function get(from: UnitOfMeasure, to: UnitOfMeasure): Converter; 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 { 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(from: UnitOfMeasure, to: UnitOfMeasure, value: F): T { const converter = get(from, to); if (converter) { return converter.to(value); } return null; } export function register(from: UnitOfMeasure, to: UnitOfMeasure, converter: Converter) { registerConverter(UnitOfMeasure[from], UnitOfMeasure[to], converter); } export function registerAll(converters: Record>>) { for (const from in converters) { for (const to in converters[from]) { registerConverter(from, to, converters[from][to]); } } } } registerConverters(); export interface Converter { // #region Properties (2) from: (value: F) => T; to: (value: T) => F; } //onst D: d = 'ST'; //type DriverLabel = Values>; export function invert(converter: Converter): Converter { return { from: converter.to, to: converter.from, }; }