UNPKG

node-red-contrib-mysensors

Version:

Mysensors related nodes, for decoding / encoding mysensors serial protocol and MQTT topic, and wrapping arbitrary messages into mysensors compatible messages

164 lines (138 loc) 5.59 kB
import { IStorage } from './storage-interface'; import { AutoDecode } from './decoder/auto-decode'; import { IDecoder } from './decoder/decoder-interface'; import { MysensorsMqtt } from './decoder/mysensors-mqtt'; import { MysensorsSerial } from './decoder/mysensors-serial'; import { IMysensorsMsg, IStrongMysensorsMsg, MsgOrigin, MysensorsCommand } from './mysensors-msg'; import { mysensor_command, mysensor_internal } from './mysensors-types'; import { utcToZonedTime } from 'date-fns-tz'; export class MysensorsController { constructor( private database: IStorage, private handleIds: boolean, private timeResponse: boolean, private timeZone: string, private measurementSystem: string, private mqttRoot: string, private addSerialNewline: boolean, ) {} public async messageHandler( IncommingMsg: Readonly<IMysensorsMsg>, ): Promise<IMysensorsMsg | undefined> { const msg = await AutoDecode(IncommingMsg); if (!msg) { return; } await this.updateHeard(msg); if ( msg.messageType === mysensor_command.C_PRESENTATION ) { await this.database.child( msg.nodeId, msg.childSensorId, msg.subType, msg.payload as string, ); } if (msg.messageType !== mysensor_command.C_INTERNAL) { return; } switch (msg.subType) { case mysensor_internal.I_ID_REQUEST: return this.encode(await this.handleIdRequest(msg)); case mysensor_internal.I_SKETCH_NAME: case mysensor_internal.I_SKETCH_VERSION: await this.handleSketchVersion(msg); break; case mysensor_internal.I_LOG_MESSAGE: await this.handleDebug(msg); break; case mysensor_internal.I_TIME: return this.encode(await this.handleTimeResponse(msg)); case mysensor_internal.I_CONFIG: return this.encode(await this.handleConfig(msg)); case mysensor_internal.I_BATTERY_LEVEL: await this.handleBattery(msg); break; } } private async updateHeard(msg: Readonly<IStrongMysensorsMsg<MysensorsCommand>>) { await this.database.nodeHeard(msg.nodeId); await this.database.childHeard(msg.nodeId, msg.childSensorId); } private async handleConfig( msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>, ): Promise<IStrongMysensorsMsg<mysensor_command.C_INTERNAL> | undefined> { if (this.measurementSystem === 'N') { return; } return { ...msg, payload: this.measurementSystem }; } private async handleTimeResponse( msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>, ): Promise<IStrongMysensorsMsg<mysensor_command.C_INTERNAL> | undefined> { if (!this.timeResponse || !msg.messageType) { return; } const msgCopy = {...msg}; msgCopy.subType = mysensor_internal.I_TIME; if (this.timeZone === 'Z') { msgCopy.payload = Math.trunc(new Date().getTime() / 1000).toString(); } else { msgCopy.payload = Math.trunc(utcToZonedTime(new Date(), this.timeZone).getTime() / 1000).toString(); } return msgCopy; } private async handleIdRequest( msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>, ): Promise<IStrongMysensorsMsg<mysensor_command.C_INTERNAL> | undefined> { if (!this.handleIds) { return; } const newNodeId = await this.database.getFreeNodeId(); if (!newNodeId) { return; } return { ...msg, subType: mysensor_internal.I_ID_RESPONSE, payload: newNodeId.toString() }; } private async handleDebug(msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>): Promise<void> { const r = /TSF:MSG:READ,(\d+)-(\d+)-(\d+)/; const m = r.exec(msg.payload as string); if (!m) { return; } return this.database.setParent(Number(m[1]), Number(m[2])); } private async handleBattery(msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>): Promise<void> { await this.database.setBatteryLevel(msg.nodeId, Number(msg.payload)); } private async handleSketchVersion(msg: Readonly<IStrongMysensorsMsg<mysensor_command.C_INTERNAL>>): Promise<void> { if (msg.subType === mysensor_internal.I_SKETCH_VERSION) { return this.database.sketchVersion(msg.nodeId, msg.payload as string); } else if (msg.subType === mysensor_internal.I_SKETCH_NAME) { return this.database.sketchName(msg.nodeId, msg.payload as string); } } private encode(msg: Readonly<IStrongMysensorsMsg<MysensorsCommand>> | undefined) { if (!msg) { return; } let encoder: IDecoder | undefined; if (msg.origin === MsgOrigin.serial) { encoder = new MysensorsSerial(undefined, undefined, this.addSerialNewline); } else if (msg.origin === MsgOrigin.mqtt) { encoder = new MysensorsMqtt(); } if (!encoder) { return msg; } return encoder.encode({...msg, topicRoot: this.mqttRoot}); } }