@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
266 lines (240 loc) • 8.34 kB
text/typescript
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
};
}