@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
530 lines (464 loc) • 16.3 kB
text/typescript
import EventEmitter from 'events';
import { isBoxedPrimitive } from 'util/types';
import type { ISYNode } from '../../ISYNode.js';
import { UnitOfMeasure } from './UOM.js';
import type { Identity, MaybePromise, UnionToIntersection } from '@matter/general';
import { server } from 'typescript';
import { Converter } from '../../Converters.js';
import type { ISYDeviceNode } from '../../Devices/ISYDeviceNode.js';
import type { DriverState } from '../../Model/DriverState.js';
import type { StringKeys } from '../../Utils.js';
import type { Family } from './Families.js';
export enum StandardDrivers {
AccelerationXAxis = 'ACCX',
AccelerationYAxis = 'ACCY',
AccelerationZAxis = 'ACCZ',
AirFlow = 'AIRFLOW',
AirQualityIndex = 'AQI',
Alarm = 'ALARM',
AnglePosition = 'ANGLPOS',
AtmosphericPressure = 'ATMPRES',
AutoDRProcessingState = 'ADRPST',
Awake = 'AWAKE',
BarometricPressure = 'BARPRES',
BatteryLevel = 'BATLVL',
Beep = 'BEEP',
BloodPressureDiastolic = 'BPDIA',
BloodPressureSystolic = 'BPSYS',
BodyMassIndex = 'BMI',
BoneMass = 'BONEM',
Brighten = 'BRT',
CarbonMonoxideLevel = 'CO',
CO2Level = 'CO2LVL',
ControllerAction = 'CTL',
CoolSetpoint = 'CLISPC',
CurrentCurrent = 'CC',
CurrentPowerUsed = 'CPW',
CurrentTemperature = 'CLITEMP',
CurrentVoltage = 'CV',
CustomControl0 = 'GV0',
CustomControl1 = 'GV1',
CustomControl2 = 'GV2',
CustomControl3 = 'GV3',
CustomControl4 = 'GV4',
CustomControl5 = 'GV5',
CustomControl6 = 'GV6',
CustomControl7 = 'GV7',
CustomControl8 = 'GV8',
CustomControl9 = 'GV9',
CustomControl10 = 'GV10',
CustomControl11 = 'GV11',
CustomControl12 = 'GV12',
CustomControl13 = 'GV13',
CustomControl14 = 'GV14',
CustomControl15 = 'GV15',
CustomControl16 = 'GV16',
CustomControl17 = 'GV17',
CustomControl18 = 'GV18',
CustomControl19 = 'GV19',
CustomControl20 = 'GV20',
CustomControl21 = 'GV21',
CustomControl22 = 'GV22',
CustomControl23 = 'GV23',
CustomControl24 = 'GV24',
CustomControl25 = 'GV25',
CustomControl26 = 'GV26',
CustomControl27 = 'GV27',
CustomControl28 = 'GV28',
CustomControl29 = 'GV29',
CustomControl30 = 'GV30',
Delay = 'DELAY',
DewPoint = 'DEWPT',
DeviceIsBusy = 'BUSY',
DeviceSecureMode = 'SECMD',
Dim = 'DIM',
Distance = 'DISTANC',
DomesticHotWaterTemperature = 'WATERTD',
Duration = 'DUR',
ElectricalConductivity = 'ELECCON',
ElectricalResistivity = 'ELECRES',
EnergyMode = 'CLIEMD',
Error = 'ERR',
Evapotranspiration = 'ETO',
ExhaustTemperature = 'TEMPEXH',
FadeDown = 'FDDOWN',
FadeStop = 'FDSTOP',
FadeUp = 'FDUP',
FanRunningState = 'CLIFRS',
FanSetting = 'CLIFS',
FanSettingOverride = 'CLIFSO',
FastOff = 'DFOF',
FastOn = 'DFON',
FormaldehydeCH2OLevel = 'CH20',
Frequency = 'FREQ',
GeneralPurposeValue = 'GPV',
GenericVolume = 'GVOL',
Gust = 'GUST',
HeatCoolState = 'CLIHCS',
HeatIndex = 'HEATIX',
HeatSetpoint = 'CLISPH',
Hail = 'HAIL',
HeartRate = 'HR',
Humidity = 'CLIHUM',
Luminance = 'LUMIN',
MethaneDensity = 'METHANE',
Mode = 'MODE',
Moisture = 'MOIST',
MoonPhase = 'MOON',
MuscleMass = 'MUSCLEM',
Off = 'DOF',
Off3KeyPresses = 'DOF3',
Off4KeyPresses = 'DOF4',
Off5KeyPresses = 'DOF5',
On = 'DON',
On3KeyPresses = 'DON3',
On4KeyPresses = 'DON4',
On5KeyPresses = 'DON5',
OnLevel = 'OL',
Ozone = 'OZONE',
ParticulateMatter10 = 'PM10',
ParticulateMatter25 = 'PM25',
PercentChanceOfPrecipitation = 'POP',
PolarizedPowerUsed = 'PPW',
PowerFactor = 'PF',
Precipitation = 'PRECIP',
PulseCount = 'PULSCNT',
QueryDevice = 'QUERY',
RadonConcentration = 'RADON',
RainRate = 'RAINRT',
RampRate = 'RR',
RelativeModulationLevel = 'RELMOD',
ResetValues = 'RESET',
RespiratoryRate = 'RESPR',
RFSignalStrength = 'RFSS',
Rotation = 'ROTATE',
ScheduleMode = 'CLISMD',
SeismicIntensity = 'SEISINT',
SeismicMagnitude = 'SEISMAG',
SmokeDensity = 'SMOKED',
SoilHumidity = 'SOILH',
SoilReactivity = 'SOILR',
SoilSalinity = 'SOILS',
SoilTemperature = 'SOILT',
SolarRadiation = 'SOLRAD',
SoundVolume = 'SVOL',
Speed = 'SPEED',
Status = 'ST',
TankCapacity = 'TANKCAP',
TheUserAccessCodeThatAssociatedWithTheMostRecentAlarm = 'USRNUM',
ThermostatMode = 'CLIMD',
TideLevel = 'TIDELVL',
Time = 'TIME',
TimeRemaining = 'TIMEREM',
TotalBodyWater = 'TBW',
TotalPowerUsed = 'TPW',
Ultraviolet = 'UV',
ValidUserAccessCodeEntered = 'UAC',
Velocity = 'SPEED',
VOCLevel = 'VOCLVL',
WaterFlow = 'WATERF',
WaterPressure = 'WATERP',
WaterTemperature = 'WATERT',
WaterVolume = 'WVOL',
Weight = 'WEIGHT',
WindChill = 'WINDCH',
WindDirection = 'WINDDIR',
BoilerWaterTemperature = 'WATERTB',
OutsideTemperature = 'TEMPOUT'
}
type a = keyof typeof StandardDrivers;
type EnumFromLabel<L extends keyof N, N> = N[L];
type DriverTypeFromName<N extends keyof typeof StandardDrivers> = EnumFromLabel<N, typeof StandardDrivers>;
//export type DriverLabel<T extends DriverType> = ReturnType>LabelMap.get>
export type EnumLiteral<T> =
T extends string ? `${T}`
: T extends boolean ?
T extends true ?
true
: false
: T;
type y = EnumLiteral<StandardDrivers.Status>;
type z = EnumLiteral<StandardDrivers.Status | StandardDrivers.Precipitation>;
type T = DriverTypeFromName<keyof typeof StandardDrivers>;
//type Driver<D extends DriverType> = D extends D[0] | infer K extends DriverType ? D[0] | DriverLabels<K> : never;
type s = EnumLiteral<{ 100: true; 200: false }>;
export type DriverList<D> =
D extends StandardDrivers ? { [K in D]?: Driver<K, any, any> } & { add(driver: Driver<D, any, any>): void }
: D extends EnumLiteral<infer R extends StandardDrivers> ? { [K in R]?: Driver<R, any, any> } & { add(driver: Driver<R, any, any>): void }
: {}; //& { [N in keyof typeof DriverType]?: Driver<DriverTypeFromName<N>> };
type KeyOrValue<K, D> =
K extends D ? D
: K extends keyof D ? D[K]
: never;
// const LabelMap = new Map<StandardDrivers, keyof typeof StandardDrivers>(Object.entries(StandardDrivers).map(([a, b]) => [b, a]) as any);
///onst DriverNames = Object.entries(DriverType).(a)=>[a[1],a[0]]) as [DriverType,(keyof typeof DriverType)][];
/* export class Drivers<D extends StandardDrivers> {
// #region Properties (1)
public DriverHandler: ProxyHandler<Drivers<D>> = {
set(target, p: StandardDrivers | keyof typeof StandardDrivers, newValue, receiver) {
if (p in target) {
if (p in StandardDrivers) {
target[p] = newValue;
target[LabelMap.get(p as StandardDrivers)] = newValue;
return true;
} else if (typeof p === 'string' && p in LabelMap.values()) {
target[StandardDrivers[p]] = newValue;
target[p] = newValue;
return true;
}
return true;
}
return false;
},
get(target, p: StandardDrivers | keyof typeof StandardDrivers, receiver) {
if (p in target) {
return target[p];
}
return undefined;
}
};
// #endregion Properties (1)
// #region Constructors (1)
constructor() {
return new Proxy(this, this.DriverHandler);
}
// #endregion Constructors (1)
// #region Public Methods (1)
public add<K extends D>(driver: Driver<K, any, any>) {
(this as unknown)[driver.id] = driver;
(this as unknown)[LabelMap.get(driver.id)] = driver;
}
// #endregion Public Methods (1)
} */
export interface Driver<D extends StringKeys<StandardDrivers> | string, U extends UnitOfMeasure, T = number, SU extends UnitOfMeasure = U, ST = T, N extends string = D, L extends string = N> {
// #region Properties (8)
readonly value: T;
id: D;
label: L;
name: N;
changedEventName: `${N}Changed`;
query: () => Promise<DriverState>;
serverUom?: SU;
state: {
initial: boolean;
value: T;
formattedValue?: any;
pendingValue: T;
rawValue?: ST;
};
initialized: boolean;
uom: U;
// #endregion Properties (8)
// #region Public Methods (2)
apply(state: DriverState, notify?: boolean): boolean;
patch(value: T, formattedValue: string, uom: UnitOfMeasure, prec: number, notify?: boolean): boolean;
// #endregion Public Methods (2)
//override on('change', listener: (value: any) => void): this;
}
export interface StatelessDriver<D extends StandardDrivers | EnumLiteral<StandardDrivers> | `${string}.${EnumLiteral<StandardDrivers>}` | string, U extends UnitOfMeasure, T = number, SU extends UnitOfMeasure = U, ST = T, N = D, L = N> {
// #region Properties (9)
readonly value: Promise<T>;
id: D;
label: L;
name: N;
query: () => Promise<DriverState>;
serverUom?: SU;
state: {
initial: boolean;
value: T;
formattedValue?: any;
pendingValue: T;
rawValue?: ST;
};
stateless: true;
uom: U;
// #endregion Properties (9)
//override on('change', listener: (value: any) => void): this;
}
export function isStateless(x: any): x is StatelessDriver<any, any, any> {
return x.stateless;
}
export function isTrue(x: true | false): x is true {
return x;
}
export type EnumWithLiteral<D extends string | bigint | number | boolean> = D | EnumLiteral<D>;
export namespace Driver {
export type Signature<U extends UnitOfMeasure = UnitOfMeasure, V = any, SU extends UnitOfMeasure = U, N extends string = string, L extends string = N> = { uom: U; value: V; name: N; label: L };
export const Standard = StandardDrivers;
export type Signatures<D> = UnionToIntersection<
D extends string ? { [K in D]: Signature<UnitOfMeasure, any, UnitOfMeasure, K, K> }
: D extends { [K in keyof D]: Signature } ? D
: never
>;
///D extends (infer R extends string | infer K extends {[L in keyof K]: Signature}) ? {[J in R & string]: Signature} & K : D extends string ? { [K in D]: Signature } : D extends {[K in keyof D]: Signature} ? D : never;
type test = Signatures<StandardDrivers.Status | { ERR: Signature; BUSY: Signature } | { ERR: Signature; BUSY: Signature } | { ERR: Signature; BUSY: Signature }>;
type Drivers = {
ST?: {
uom: UnitOfMeasure.Percent;
value: number;
name: 'status';
label: 'Status';
};
ERR?: {
uom: UnitOfMeasure.Index;
value: number;
name: 'responding';
label: 'Responding';
};
};
export type For<D extends string, T, S extends boolean = false> = T extends Signature<infer U, infer V, infer SU, infer N, infer L> ? StatelessOrStateful<D, U, V, SU, V, N, L, S> : never;
export type ForAll<T, S extends boolean = false> = {
[K in StringKeys<T>]: T[K] extends Signature<UnitOfMeasure, any, UnitOfMeasure, string, string> ? For<K, T[K], S> : never;
};
export type SignaturesOf<D> = D extends ForAll<infer R> ? R : never;
type test2 = SignaturesOf<ForAll<Drivers>>;
export type Type = StandardDrivers;
export type Literal = EnumLiteral<StandardDrivers>;
export type LiteralWithExtensions = Literal | `${string}.${Literal}`;
export type LiteralWithType = EnumWithLiteral<StandardDrivers>;
type StatelessOrStateful<D extends string, U extends UnitOfMeasure, T = number, SU extends UnitOfMeasure = U, ST = T, N extends string = D, L extends string = N, S extends boolean = false> = S extends true ? StatelessDriver<D, U, T, SU, ST, N, L> : Driver<D, U, T, SU, ST, N, L>;
export function create<D extends Literal, U extends UnitOfMeasure, T = number, N extends string = string, L extends string = string, S extends boolean = false>(
driver: EnumWithLiteral<D>,
node?: ISYNode<Family, any, any, any>,
initState?: DriverState,
driverSignature?: { uom: U; label: L; name: N },
stateless?: S,
converter?: Converter<any, T>
): StatelessOrStateful<D, U, T, UnitOfMeasure, any, N, L, S> {
const query = async () => {
return await node.readProperty(driver as D);
};
const name = initState?.name ?? driverSignature?.name ?? driver;
const label = driverSignature?.label ?? name;
const changedEventName = `${name}Changed`;
if (isTrue(stateless)) {
return {
id: driver as D,
stateless: true,
uom: driverSignature.uom as U,
serverUom: initState?.uom != driverSignature.uom ? initState?.uom : undefined,
state: {
initial: true,
value: initState?.value, //TODO include converter
formattedValue: initState?.formatted,
pendingValue: null
},
query,
get value() {
return query().then((p) => p.value) as Promise<T>;
},
name: initState?.name ?? driverSignature.name ?? driverSignature.label ?? driver,
label: driverSignature.label ?? driver
} as StatelessOrStateful<D, U, T, UnitOfMeasure, any, N, L, typeof stateless>;
}
var c = {
id: driver as D,
initialized: initState && initState?.uom != 0 && initState?.uom != null ? true : false,
uom: driverSignature?.uom as U,
serverUom: initState?.uom != driverSignature?.uom ? initState?.uom : undefined,
state: {
initial: true,
value:
initState ?
converter ? converter.from(initState.value)
: driverSignature?.uom && initState?.uom != driverSignature?.uom ? Converter.convert(initState.uom, driverSignature.uom, initState.value)
: initState.value
: null,
rawValue: initState ? initState.value : null,
formattedValue: initState ? initState.formatted : null,
pendingValue: null
},
async query() {
let s = await query();
this.state.value =
converter ? converter.from(s.value)
: this.uom !== s.uom ? Converter.convert(s.value, s.uom, this.uom)
: s.value;
this.state.formattedValue = s.formatted;
this.state.rawValue = s.value;
return s;
},
apply(state: DriverState, notify = true) {
if (state == null || state.uom == null || state.uom == 0) {
return false;
}
if (state.id == this.id) {
let previousRawValue = this.state.rawValue;
let previousValue = this.state.value;
this.state.rawValue = state.value;
this.state.formattedValue = state.formatted;
if (previousRawValue == this.state.rawValue) {
return false;
}
if (state.uom != this.uom) {
this.serverUom = state.uom;
this.state.value = converter ? converter.from(this.state.rawValue) : Converter.convert(state.uom, this.uom, this.state.rawValue);
} else if (converter) {
this.state.value = converter.from(state.value);
} else {
this.state.value = state.value;
}
if (notify) node.events.emit(changedEventName, driver as D, this.state.value, previousValue, this.state.formattedValue);
if (!this.initialized) {
this.initialized = true;
} else {
this.state.initial = false;
}
return true;
}
},
patch(value: T, formattedValue: string, uom: UnitOfMeasure, prec: number, notify = true) {
if (uom == 0 || uom == null) {
return false;
}
let previousRawValue = this.state.rawValue;
let previousValue = this.state.value;
this.state.rawValue = value;
this.state.formattedValue = formattedValue;
if (uom != this.uom) {
this.serverUom == uom;
this.state.value = converter ? converter.from(this.state.rawValue) : Converter.convert(uom, this.uom, this.state.rawValue);
} else if (converter) {
this.state.value = converter.from(value);
} else {
this.state.value = value;
}
if (previousRawValue == this.state.rawValue) {
return false;
}
if (notify)
if (node.events.emit(changedEventName, driver as D, this.state.value, previousValue, formattedValue)) {
node.logger(`Driver ${this.id} changed from ${previousValue} ${previousRawValue} to ${this.state.value} (${this.state.rawValue})`);
}
if (!this.initialized) {
this.initialized = true;
} else {
this.state.initial = false;
}
return true;
},
get value() {
return c.state.value;
},
name: name,
label: label,
changedEventName: changedEventName
};
// node.on('PropertyChanged', (propertyName, newValue, oldValue, formattedValue) => {
// if (propertyName === driver) {
// c.state.initial = false;
// c.state.value = node.convertFrom(newValue,c.uom,driver as D);
// c.state.formattedValue = formattedValue;
// }
//});
return c as StatelessOrStateful<D, U, T, UnitOfMeasure, any, N, L, typeof stateless>;
}
}
// type EnumValues2<T extends string> = T extends { [Type in keyof T]: string } ? T extenDrivers[0] | : never;
// type EnumValues<T> = T extends { [x in keyof T]: string }
// ? T extends string | infer K extends string
// ? [keyof T][0] | EnumValues<K>
// : never
// : T;
// type Driver = EnumValues2<Drivers>
// let x : Driver = "ACvCX"