From 61cfe26b24ecde270f4fb67fd0a8c8c5b57bcff7 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Mon, 9 Oct 2017 21:02:55 -0600 Subject: [PATCH] Restructued to be much cleaner; bumped ts target to es6 --- app/state/websocket.ts | 4 +- app/tsconfig.json | 2 +- common/sprinklers/mqtt/index.ts | 179 +++++++++++++++++--------------- common/tsconfig.json | 2 +- server/tsconfig.json | 3 +- 5 files changed, 102 insertions(+), 88 deletions(-) diff --git a/app/state/websocket.ts b/app/state/websocket.ts index 8d8d3c8..010b47c 100644 --- a/app/state/websocket.ts +++ b/app/state/websocket.ts @@ -2,7 +2,7 @@ import { update } from "serializr"; import logger from "@common/logger"; import * as s from "@common/sprinklers"; -import * as schema from "@common/sprinklers/json"; +import * as schema from "@common/sprinklers/schema"; import * as ws from "@common/sprinklers/websocketData"; import { checkedIndexOf } from "@common/utils"; @@ -134,7 +134,7 @@ export class WebApiClient implements s.ISprinklersApi { if (data.name !== "grinklers") { return log.warn({ data }, "invalid deviceUpdate received"); } - update(schema.sprinklersDeviceSchema, this.device, data.data); + update(schema.sprinklersDevice, this.device, data.data); } private onDeviceCallResponse(data: ws.IDeviceCallResponse) { diff --git a/app/tsconfig.json b/app/tsconfig.json index 4986000..44af363 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -3,7 +3,7 @@ "sourceMap": true, "jsx": "react", "experimentalDecorators": true, - "target": "es5", + "target": "es6", "lib": [ "es6", "dom", diff --git a/common/sprinklers/mqtt/index.ts b/common/sprinklers/mqtt/index.ts index 5307759..a96406d 100644 --- a/common/sprinklers/mqtt/index.ts +++ b/common/sprinklers/mqtt/index.ts @@ -12,7 +12,7 @@ export class MqttApiClient implements s.ISprinklersApi { readonly mqttUri: string; client: mqtt.Client; connected: boolean; - devices: { [prefix: string]: MqttSprinklersDevice } = {}; + devices: Map = new Map(); constructor(mqttUri: string) { this.mqttUri = mqttUri; @@ -38,8 +38,8 @@ export class MqttApiClient implements s.ISprinklersApi { this.client.on("connect", () => { log.info("mqtt connected"); this.connected = true; - for (const prefix of Object.keys(this.devices)) { - const device = this.devices[prefix]; + const values = this.devices.values(); + for (const device of values) { device.doSubscribe(); } }); @@ -49,22 +49,23 @@ export class MqttApiClient implements s.ISprinklersApi { if (/\//.test(prefix)) { throw new Error("Prefix cannot contain a /"); } - if (!this.devices[prefix]) { - const device = this.devices[prefix] = new MqttSprinklersDevice(this, prefix); + let device = this.devices.get(prefix); + if (!device) { + this.devices.set(prefix, device = new MqttSprinklersDevice(this, prefix)); if (this.connected) { device.doSubscribe(); } } - return this.devices[prefix]; + return device; } removeDevice(prefix: string) { - const device = this.devices[prefix]; + const device = this.devices.get(prefix); if (!device) { return; } device.doUnsubscribe(); - delete this.devices[prefix]; + this.devices.delete(prefix); } private onMessageArrived(topic: string, payload: Buffer, packet: mqtt.Packet) { @@ -81,7 +82,7 @@ export class MqttApiClient implements s.ISprinklersApi { const topicIdx = topic.indexOf("/"); // find the first / const prefix = topic.substr(0, topicIdx); // assume prefix does not contain a / const topicSuffix = topic.substr(topicIdx + 1); - const device = this.devices[prefix]; + const device = this.devices.get(prefix); if (!device) { log.debug({ prefix }, "received message for unknown device"); return; @@ -100,13 +101,29 @@ const subscriptions = [ "/section_runner", ]; +type IHandler = (payload: any, ...matches: string[]) => void; + +interface IHandlerEntry { + test: RegExp; + handler: IHandler; +} + +const handler = (test: RegExp) => + (target: MqttSprinklersDevice, propertyKey: string, descriptor: TypedPropertyDescriptor) => { + if (typeof descriptor.value === "function") { + (target.handlers || (target.handlers = [])).push({ + test, handler: descriptor.value, + }); + } + }; + class MqttSprinklersDevice extends s.SprinklersDevice { readonly apiClient: MqttApiClient; readonly prefix: string; - private responseCallbacks: { - [rid: number]: ResponseCallback; - } = {}; + handlers: IHandlerEntry[]; + private nextRequestId: number = Math.floor(Math.random() * 1000000000); + private responseCallbacks: Map = new Map(); constructor(apiClient: MqttApiClient, prefix: string) { super(); @@ -133,73 +150,17 @@ class MqttSprinklersDevice extends s.SprinklersDevice { this.apiClient.client.unsubscribe(topics); } - /** - * Updates this device with the specified message - * @param topic The topic, with prefix removed - * @param payload The payload buffer - */ onMessage(topic: string, payload: string) { - if (topic === "connected") { - this.connected = (payload === "true"); - log.trace(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`); - return; - } - let matches = topic.match(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/); - if (matches != null) { - //noinspection JSUnusedLocalSymbols - /* tslint:disable-next-line:no-unused-variable */ - const [_topic, secStr, subTopic] = matches; - log.trace({ section: secStr, topic: subTopic, payload }); - if (!secStr) { // new number of sections - this.sections.length = Number(payload); - } else { - const secNum = Number(secStr); - let section = this.sections[secNum]; - if (!section) { - this.sections[secNum] = section = new MqttSection(this, secNum); - } - (section as MqttSection).onMessage(subTopic, payload); - } - return; - } - matches = topic.match(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/); - if (matches != null) { - //noinspection JSUnusedLocalSymbols - /* tslint:disable-next-line:no-unused-variable */ - const [_topic, progStr, subTopic] = matches; - log.trace({ program: progStr, topic: subTopic, payload }); - if (!progStr) { // new number of programs - this.programs.length = Number(payload); - } else { - const progNum = Number(progStr); - let program = this.programs[progNum]; - if (!program) { - this.programs[progNum] = program = new MqttProgram(this, progNum); - } - (program as MqttProgram).onMessage(subTopic, payload); - } - return; - } - matches = topic.match(/^section_runner$/); - if (matches != null) { - (this.sectionRunner as MqttSectionRunner).onMessage(payload); - return; - } - matches = topic.match(/^responses\/(\d+)$/); - if (matches != null) { - //noinspection JSUnusedLocalSymbols - /* tslint:disable-next-line:no-unused-variable */ - const [_topic, respIdStr] = matches; - log.trace({ response: respIdStr }); - const respId = parseInt(respIdStr, 10); - const data = JSON.parse(payload) as IResponseData; - const cb = this.responseCallbacks[respId]; - if (typeof cb === "function") { - cb(data); + for (const { test, handler: hndlr } of this.handlers) { + const matches = topic.match(test); + if (!matches) { + continue; } + matches.shift(); + hndlr.call(this, payload, ...matches); return; } - log.warn({ topic }, "MqttSprinklersDevice recieved invalid message"); + log.warn({ topic }, "MqttSprinklersDevice recieved message on invalid topic"); } runSection(section: s.Section | number, duration: s.Duration) { @@ -227,26 +188,78 @@ class MqttSprinklersDevice extends s.SprinklersDevice { return this.makeRequest(`section_runner/unpause`); } - //noinspection JSMethodCanBeStatic - private nextRequestId(): number { - return Math.floor(Math.random() * 1000000000); + private getRequestId(): number { + return this.nextRequestId++; } private makeRequest(topic: string, payload: any = {}): Promise { return new Promise((resolve, reject) => { - const requestId = payload.rid = this.nextRequestId(); + const requestId = payload.rid = this.getRequestId(); const payloadStr = JSON.stringify(payload); const fullTopic = this.prefix + "/" + topic; - this.responseCallbacks[requestId] = (data) => { + this.responseCallbacks.set(requestId, (data) => { if (data.error != null) { reject(data); } else { resolve(data); } - }; + this.responseCallbacks.delete(requestId); + }); this.apiClient.client.publish(fullTopic, payloadStr, { qos: 1 }); }); + } + @handler(/^connected$/) + private handleConnected(payload: string) { + this.connected = (payload === "true"); + log.trace(`MqttSprinklersDevice with prefix ${this.prefix}: ${this.connected}`); + return; + } + + @handler(/^sections(?:\/(\d+)(?:\/?(.+))?)?$/) + private handleSectionsUpdate(payload: string, secNumStr?: string, subTopic?: string) { + log.trace({ section: secNumStr, topic: subTopic, payload }, "handling section update"); + if (!secNumStr) { // new number of sections + this.sections.length = Number(payload); + } else { + const secNum = Number(secNumStr); + let section = this.sections[secNum]; + if (!section) { + this.sections[secNum] = section = new MqttSection(this, secNum); + } + (section as MqttSection).onMessage(payload, subTopic); + } + } + + @handler(/^programs(?:\/(\d+)(?:\/?(.+))?)?$/) + private handleProgramsUpdate(payload: string, progNumStr?: string, subTopic?: string) { + log.trace({ program: progNumStr, topic: subTopic, payload }, "handling program update"); + if (!progNumStr) { // new number of programs + this.programs.length = Number(payload); + } else { + const progNum = Number(progNumStr); + let program = this.programs[progNum]; + if (!program) { + this.programs[progNum] = program = new MqttProgram(this, progNum); + } + (program as MqttProgram).onMessage(payload, subTopic); + } + } + + @handler(/^section_runner$/) + private handleSectionRunnerUpdate(payload: string) { + (this.sectionRunner as MqttSectionRunner).onMessage(payload); + } + + @handler(/^responses\/(\d+)$/) + private handleResponse(payload: string, responseIdStr: string) { + log.trace({ response: responseIdStr }, "handling request response"); + const respId = parseInt(responseIdStr, 10); + const data = JSON.parse(payload) as IResponseData; + const cb = this.responseCallbacks.get(respId); + if (typeof cb === "function") { + cb(data); + } } } @@ -264,7 +277,7 @@ interface IRunSectionJSON { } class MqttSection extends s.Section { - onMessage(topic: string, payload: string) { + onMessage(payload: string, topic: string | undefined) { if (topic === "state") { this.state = (payload === "true"); } else if (topic == null) { @@ -278,7 +291,7 @@ class MqttSection extends s.Section { } class MqttProgram extends s.Program { - onMessage(topic: string, payload: string) { + onMessage(payload: string, topic: string | undefined) { if (topic === "running") { this.running = (payload === "true"); } else if (topic == null) { diff --git a/common/tsconfig.json b/common/tsconfig.json index 774f8f5..2d32d2f 100644 --- a/common/tsconfig.json +++ b/common/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, - "target": "es5", + "target": "es6", "lib": [ "es6" ], diff --git a/server/tsconfig.json b/server/tsconfig.json index 386476b..cc549f0 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -3,7 +3,8 @@ "outDir": "../dist", "sourceMap": true, "experimentalDecorators": true, - "target": "es5", + "target": "es6", + "module": "commonjs", "lib": [ "es6", "dom"