UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

530 lines (464 loc) 16.3 kB
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"