import { ControlExecutionSpec, DatapointState, DatapointTargetSpec, Device, DeviceControlResponse, DeviceDatapoint, DeviceStatus, LocalDatapoint, LocalDatapointId, } from "jm-castle-ac-dc-types"; import { getDateFormat } from "jm-castle-types/build"; import { DateTime } from "luxon"; import { EngineContextConsumer } from "../engines/Types.mjs"; import { DeviceType, supportedDeviceTypes } from "./DeviceTypes.mjs"; interface DatapointIdMapping { "local-private-to-local-public": Record; "local-public-to-local-private": Record; "global-public-to-local-private": Record; } export const getAllPublicControlDatapoints = ( validDevices: Record ) => { const all: { __global: Record } & Record< string, Record > = { __global: {} }; Object.entries(validDevices).forEach(([k, device]) => { const { type } = device; const deviceType = supportedDeviceTypes[type]; const { publicControlDatapoints } = getControlDatapoints( device, deviceType ); const entries = Object.entries(publicControlDatapoints); if (entries.length) all[device.id] = {}; entries.forEach(([k, datapoint]) => { all.__global[datapoint.id] = datapoint; all[device.id][datapoint.localId] = datapoint; }); }); return all; }; export const getDeviceDatapoints = (device: Device, deviceType: DeviceType) => { const privateDatapoints: Record = {}; const publicDatapoints: Record = {}; const localPrivateToPublicMap: Record = {}; const localPublicToLocalPrivateMap: Record< LocalDatapointId, LocalDatapointId > = {}; const globalPublicToLocalPrivateMap: Record = {}; if (device.datapoints) { const points = device.datapoints; Object.entries(points).forEach(([k, localPoint]) => { privateDatapoints[localPoint.localId] = { ...localPoint }; const id = `${localPoint.localId}@${device.id}`; const devicePoint: DeviceDatapoint = { deviceId: device.id, id, ...localPoint, }; publicDatapoints[devicePoint.localId] = devicePoint; localPrivateToPublicMap[localPoint.localId] = localPoint.localId; localPublicToLocalPrivateMap[devicePoint.localId] = localPoint.localId; globalPublicToLocalPrivateMap[devicePoint.id] = localPoint.localId; }); } else { const points = deviceType.datapoints; const mapPoints = device.mapDatapoints; Object.entries(points).forEach(([k, localPoint]) => { privateDatapoints[localPoint.localId] = { ...localPoint }; const mapping = mapPoints ? mapPoints[k] : undefined; const { localId, name, id } = mapping || {}; const usedLocalId = localId || localPoint.localId; const nameWithDeviceId = `${localPoint.name} (${device.id})`; const usedName = name || nameWithDeviceId; const globalId = id || `${usedLocalId}@${device.id}`; const devicePoint: DeviceDatapoint = { deviceId: device.id, localId: usedLocalId, id: globalId, name: usedName, valueType: localPoint.valueType, valueUnit: localPoint.valueUnit, }; publicDatapoints[devicePoint.localId] = devicePoint; localPrivateToPublicMap[localPoint.localId] = devicePoint.localId; localPublicToLocalPrivateMap[devicePoint.localId] = localPoint.localId; globalPublicToLocalPrivateMap[devicePoint.id] = localPoint.localId; }); } return { publicDatapoints, privateDatapoints, mapDatapointIds: { "local-private-to-local-public": localPrivateToPublicMap, "global-public-to-local-private": globalPublicToLocalPrivateMap, "local-public-to-local-private": localPublicToLocalPrivateMap, }, }; }; export const getControlDatapoints = ( device: Device, deviceType: DeviceType ) => { const privateControlDatapoints: Record = {}; const publicControlDatapoints: Record = {}; const localPrivateToPublicMap: Record = {}; const localPublicToLocalPrivateMap: Record< LocalDatapointId, LocalDatapointId > = {}; const globalPublicToLocalPrivateMap: Record = {}; if (device.controlDatapoints) { const points = device.controlDatapoints; Object.entries(points).forEach(([k, localPoint]) => { privateControlDatapoints[localPoint.localId] = { ...localPoint }; const id = `${localPoint.localId}@${device.id}`; const devicePoint: DeviceDatapoint = { deviceId: device.id, id, ...localPoint, }; publicControlDatapoints[devicePoint.localId] = devicePoint; localPublicToLocalPrivateMap[devicePoint.localId] = localPoint.localId; globalPublicToLocalPrivateMap[devicePoint.id] = localPoint.localId; }); } else { const controlPoints = deviceType.controlDatapoints; const mapControlPoints = device.mapControlDatapoints; Object.entries(controlPoints).forEach(([k, localPoint]) => { privateControlDatapoints[localPoint.localId] = { ...localPoint }; const mapping = mapControlPoints ? mapControlPoints[k] : undefined; const { localId, name, id } = mapping || {}; const usedLocalId = localId || localPoint.localId; const nameWithDeviceId = `${localPoint.name} (${device.id})`; const usedName = name || nameWithDeviceId; const globalId = id || `${usedLocalId}@${device.id}`; const devicePoint: DeviceDatapoint = { deviceId: device.id, localId: usedLocalId, id: globalId, name: usedName, valueType: localPoint.valueType, valueUnit: localPoint.valueUnit, }; publicControlDatapoints[devicePoint.localId] = devicePoint; localPrivateToPublicMap[localPoint.localId] = devicePoint.localId; localPublicToLocalPrivateMap[devicePoint.localId] = localPoint.localId; globalPublicToLocalPrivateMap[devicePoint.id] = localPoint.localId; }); } return { publicControlDatapoints, privateControlDatapoints, mapControlDatapointIds: { "local-private-to-local-public": localPrivateToPublicMap, "global-public-to-local-private": globalPublicToLocalPrivateMap, "local-public-to-local-private": localPublicToLocalPrivateMap, }, }; }; export class DeviceInstance { constructor(device: Device, deviceType: DeviceType) { this.device = device; this.deviceType = deviceType; const { publicDatapoints, mapDatapointIds } = getDeviceDatapoints( device, deviceType ); this.publicDatapoints = publicDatapoints; this.mapDatapointIds = mapDatapointIds; const { privateControlDatapoints, publicControlDatapoints, mapControlDatapointIds, } = getControlDatapoints(device, deviceType); this.privateControlDatapoints = privateControlDatapoints; this.publicControlDatapoints = publicControlDatapoints; this.mapControlDatapointIds = mapControlDatapointIds; return this; } private device: Device; private deviceType: DeviceType; private mapDatapointIds: DatapointIdMapping; private mapControlDatapointIds: DatapointIdMapping; private previousStatus: DeviceStatus; private consecutiveSuppressedPeaks: Record = {}; /** * Pro öffentlicher (also evtl. mapped) local id: DeviceDatapoint */ private publicDatapoints: Record = {}; /** * Pro öffentlicher (also evtl. mapped) local id: DeviceDatapoint */ private publicControlDatapoints: Record = {}; /** * Pro privater (also id wie im deviceType) local id: DeviceDatapoint */ private privateControlDatapoints: Record = {}; public getDevice = () => this.device; public getDeviceType = () => this.deviceType; public getTypeId = () => this.deviceType.id; public getDeviceId = () => this.device.id; public getPublicDatapointForPrivateLocalId = (id: string) => { const publicLocalId = this.mapDatapointIds["local-private-to-local-public"][id]; return this.publicDatapoints[publicLocalId]; }; public getPublicDatapointForPublicLocalId = (id: string) => { return this.publicDatapoints[id]; }; public getPublicDatapointForPublicGlobalId = (id: string) => { const publicLocalId = this.mapDatapointIds["local-private-to-local-public"][ this.mapDatapointIds["global-public-to-local-private"][id] ]; return this.publicDatapoints[publicLocalId]; }; public getPublicControlDatapointForPublicLocalId = (id: string) => { return this.publicControlDatapoints[id]; }; public getPublicControlDatapointForPublicGlobalId = (id: string) => { const publicLocalId = this.mapControlDatapointIds["local-private-to-local-public"][ this.mapControlDatapointIds["global-public-to-local-private"][id] ]; return this.publicControlDatapoints[publicLocalId]; }; public getPublicDatapoints = () => { return { ...this.publicDatapoints }; }; public addDeviceEventConsumer = (consumer: EngineContextConsumer) => { if (this.deviceType.addDeviceEventConsumer) { this.deviceType.addDeviceEventConsumer(consumer, this.device); } }; public fetchDeviceStatus = async (): Promise => { const status = await this.deviceType.fetchStatus(this); const { suppressPeaks } = this.device; const { datapoints: states, error, responsive, accessedAt } = status; const { datapoints: previousStates } = this.previousStatus || {}; const mappedDatapoints: Record = {}; states && Object.entries(states).forEach(([k, state]) => { const { valueNum: fetchedValueNum } = state; const { max } = (suppressPeaks && suppressPeaks[k]) || {}; let stateValueNum: number | undefined = fetchedValueNum; if ( typeof max === "number" && this.previousStatus && typeof fetchedValueNum === "number" && fetchedValueNum > max ) { if ((this.consecutiveSuppressedPeaks[k] || 0) < 3) { // Verwende den vorherigen Wert, wenn der aktuelle einer der ersten 3 peaks ist console.log( `suppressed peak ${fetchedValueNum} for value ${k}, consecutive: ${ (this.consecutiveSuppressedPeaks[k] || 0) + 1 }, at: ${DateTime.now().toFormat(getDateFormat("second"))}` ); const previousValueNum = previousStates ? previousStates[k]?.valueNum : undefined; if ( typeof previousValueNum === "number" && previousValueNum <= max ) { console.log(`Using previous: ${previousValueNum}`); stateValueNum = previousValueNum; this.consecutiveSuppressedPeaks[k] = (this.consecutiveSuppressedPeaks[k] || 0) + 1; status.datapoints[k] = { ...status.datapoints[k], valueNum: stateValueNum, }; } } } else { this.consecutiveSuppressedPeaks[k] = 0; } const datapoint = this.getPublicDatapointForPrivateLocalId(k); const mappedKey = datapoint ? datapoint.id : k; mappedDatapoints[mappedKey] = { ...state, valueNum: stateValueNum, id: mappedKey, }; }); this.previousStatus = status.error ? this.previousStatus : status; return { error, responsive, accessedAt, datapoints: mappedDatapoints }; }; public executeControlRequest = async ( targets: Record< string, { target: DatapointTargetSpec; state: DatapointState; } & ControlExecutionSpec > ): Promise => { if (!this.deviceType.executeControlRequest) { return { success: false, error: `The device type ${this.deviceType.id} can not be controlled.`, }; } const localDatapointStates: DatapointState[] = []; Object.entries(targets).forEach(([k, pair]) => { const { state, target } = pair; const { datapoint } = target; const localDatapointId = this.mapControlDatapointIds["global-public-to-local-private"][ datapoint ] || this.mapControlDatapointIds["local-public-to-local-private"][datapoint]; localDatapointId && localDatapointStates.push({ ...state, id: localDatapointId }); }); return this.deviceType.executeControlRequest( this.device, localDatapointStates ); }; }