diff --git a/README.md b/README.md
index 414a7a6d..592aaddf 100755
--- a/README.md
+++ b/README.md
@@ -93,21 +93,19 @@ Ready for 6.0;
* [Vera Home Automation Hub](https://github.com/rstrouse/nodejs-poolController-veraPlugin) - A plugin that integrates with nodejs-poolController.
* [SmartThings/Hubitat](https://github.com/bsileo/hubitat_poolcontroller) by @bsileo (prev help from @johnny2678, @donkarnag, @arrmo)
* [Homebridge/Siri/EVE](https://github.com/gadget-monk/homebridge-poolcontroller) by @gadget-monk, adopted from @leftyflip
+* InfluxDB
Need to be updated:
* [Another SmartThings Controller](https://github.com/dhop90/pentair-pool-controller/blob/master/README.md) by @dhop90
* [ISY](src/integrations/socketISY.js). Original credit to @blueman2, enhancements by @mayermd
* [ISY Polyglot NodeServer](https://github.com/brianmtreese/nodejs-pool-controller-polyglotv2) created by @brianmtreese
* [MQTT](https://github.com/crsherman/nodejs-poolController-mqtt) created by @crsherman.
-* InfluxDB
# Support
1. For discussions, recommendations, designs, and clarifications, we recommend you join our [Gitter Chat room](https://gitter.im/pentair_pool/Lobby).
1. Check the [wiki](https://github.com/tagyoureit/nodejs-poolController/wiki) for tips, tricks and additional documentation.
1. For bug reports you can open a [github issue](https://github.com/tagyoureit/nodejs-poolController/issues/new),
-
-
### Virtual Controller
v6 adds all new configuration and support for virtual pumps, chlorinators (and soon, Intellichem)
@@ -115,7 +113,6 @@ v6 adds all new configuration and support for virtual pumps, chlorinators (and s
* [Virtual Chlorinator Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Virtual-Chlorinator-Controller-v6)
-
# Changed/dropped since 5.3
1. Ability to load different config.json files
1. Automatic upgrade of config.json files (tbd)
diff --git a/config/Config.ts b/config/Config.ts
index e94c39ea..0e94a6f4 100755
--- a/config/Config.ts
+++ b/config/Config.ts
@@ -1,19 +1,19 @@
-/* nodejs-poolController. An application to control pool equipment.
-Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
import * as path from "path";
import * as fs from "fs";
const extend = require("extend");
@@ -22,15 +22,31 @@ class Config {
private cfgPath: string;
private _cfg: any;
private _isInitialized: boolean=false;
+ private _fileTime: Date = new Date(0);
+ private _isLoading: boolean = false;
constructor() {
+ let self=this;
this.cfgPath = path.posix.join(process.cwd(), "/config.json");
// RKS 05-18-20: This originally had multiple points of failure where it was not in the try/catch.
try {
+ this._isLoading = true;
this._cfg = fs.existsSync(this.cfgPath) ? JSON.parse(fs.readFileSync(this.cfgPath, "utf8")) : {};
const def = JSON.parse(fs.readFileSync(path.join(process.cwd(), "/defaultConfig.json"), "utf8").trim());
const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), "/package.json"), "utf8").trim());
this._cfg = extend(true, {}, def, this._cfg, { appVersion: packageJson.version });
this._isInitialized = true;
+ fs.watch(this.cfgPath, (event, fileName) => {
+ if (fileName && event === 'change') {
+ if (self._isLoading) return; // Need a debounce here. We will use a semaphore to cause it not to load more than once.
+ const stats = fs.statSync(self.cfgPath);
+ if (stats.mtime.valueOf() === self._fileTime.valueOf()) return;
+ this._cfg = fs.existsSync(this.cfgPath) ? JSON.parse(fs.readFileSync(this.cfgPath, "utf8")) : {};
+ this._cfg = extend(true, {}, def, this._cfg, { appVersion: packageJson.version });
+ logger.init(); // only reload logger for now; possibly expand to other areas of app
+ logger.info(`Reloading app config: ${fileName}`);
+ }
+ });
+ this._isLoading = false;
} catch (err) {
console.log(`Error reading configuration information. Aborting startup: ${ err }`);
// Rethrow this error so we exit the app with the appropriate pause in the console.
@@ -41,10 +57,12 @@ class Config {
// Don't overwrite the configuration if we failed during the initialization.
try {
if (!this._isInitialized) return;
+ this._isLoading = true;
fs.writeFileSync(
this.cfgPath,
JSON.stringify(this._cfg, undefined, 2)
);
+ setTimeout(()=>{this._isLoading = false;}, 2000);
}
catch (err) {
logger.error("Error writing configuration file %s", err);
diff --git a/controller/State.ts b/controller/State.ts
index a0b50ff1..d1091b16 100755
--- a/controller/State.ts
+++ b/controller/State.ts
@@ -1,19 +1,19 @@
-/* nodejs-poolController. An application to control pool equipment.
-Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
import * as path from 'path';
import * as fs from 'fs';
import * as extend from 'extend';
@@ -1008,7 +1008,7 @@ export class VirtualCircuitStateCollection extends EqStateCollection {
public createItem(data: any): CircuitState { return new CircuitState(data); }
- public setCircuitState(id: number, val: boolean) { return sys.board.circuits.setCircuitStateAsync(id, val); }
+ public setCircuitStateAsync(id: number, val: boolean):Promise { return sys.board.circuits.setCircuitStateAsync(id, val); }
public async toggleCircuitStateAsync(id: number) { return sys.board.circuits.toggleCircuitStateAsync(id); }
public async setLightThemeAsync(id: number, theme: number) { return sys.board.circuits.setLightThemeAsync(id, theme); }
public getInterfaceById(id: number, add?: boolean): ICircuitState {
@@ -1301,7 +1301,7 @@ export class ChemControllerState extends EqState {
let chem = sys.chemControllers.getItemById(this.id);
let obj = this.get(true);
obj.saturationIndex = this.saturationIndex || 0;
- obj.alkalinty = chem.alkalinity;
+ obj.alkalinity = chem.alkalinity;
obj.body = sys.board.valueMaps.bodies.transform(chem.body);
obj.calciumHardness = chem.calciumHardness;
obj.cyanuricAcid = chem.cyanuricAcid;
diff --git a/controller/boards/EasyTouchBoard.ts b/controller/boards/EasyTouchBoard.ts
index 769c3939..26354163 100644
--- a/controller/boards/EasyTouchBoard.ts
+++ b/controller/boards/EasyTouchBoard.ts
@@ -862,7 +862,7 @@ class TouchCircuitCommands extends CircuitCommands {
sys.board.virtualPumpControllers.start();
// sys.board.virtualPumpControllers.setTargetSpeed();
state.emitEquipmentChanges();
- resolve(cstate.get(true));
+ resolve(cstate);
}
}
});
@@ -883,6 +883,9 @@ class TouchCircuitCommands extends CircuitCommands {
public async setLightGroupStateAsync(id: number, val: boolean): Promise { return this.setCircuitGroupStateAsync(id, val); }
public async toggleCircuitStateAsync(id: number) {
let cstate = state.circuits.getInterfaceById(id);
+ if (cstate instanceof LightGroupState) {
+ return this.setLightGroupThemeAsync(id, sys.board.valueMaps.lightThemes.getValue(cstate.isOn?'off':'on'));
+ }
return this.setCircuitStateAsync(id, !cstate.isOn);
}
private createLightGroupMessages(group: LightGroup) {
@@ -1044,17 +1047,16 @@ class TouchCircuitCommands extends CircuitCommands {
if (err) reject(err);
else {
try {
- /* for (let i = 0; i < sys.intellibrite.circuits.length; i++) {
- let c = sys.intellibrite.circuits.getItemByIndex(i);
- let cstate = state.circuits.getItemById(c.circuit);
- if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
- } */
// Let everyone know we turned these on. The theme messages will come later.
for (let i = 0; i < grp.circuits.length; i++) {
let c = grp.circuits.getItemByIndex(i);
let cstate = state.circuits.getItemById(c.circuit);
- if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
+ // if theme is 'off' light groups should not turn on
+ if (cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) === 'off')
+ await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
+ else if (!cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) !== 'off') await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
}
+ sgrp.isOn = sys.board.valueMaps.lightThemes.getName(theme) === 'off' ? false: true;
switch (theme) {
case 0: // off
case 1: // on
diff --git a/controller/boards/SystemBoard.ts b/controller/boards/SystemBoard.ts
index 1b037c16..38451e89 100644
--- a/controller/boards/SystemBoard.ts
+++ b/controller/boards/SystemBoard.ts
@@ -1535,7 +1535,7 @@ export class CircuitCommands extends BoardCommands {
}
state.emitEquipmentChanges();
sys.board.virtualPumpControllers.start();
- return Promise.resolve(circ);
+ return Promise.resolve(state.circuits.getInterfaceById(circ.id));
}
public toggleCircuitStateAsync(id: number): Promise {
diff --git a/logger/Logger.ts b/logger/Logger.ts
index 3b4b96e8..4dcbd312 100755
--- a/logger/Logger.ts
+++ b/logger/Logger.ts
@@ -1,19 +1,19 @@
-/* nodejs-poolController. An application to control pool equipment.
-Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
import * as path from 'path';
import * as fs from 'fs';
import * as winston from 'winston';
@@ -30,7 +30,6 @@ class Logger {
this.pktPath = path.join(process.cwd(), '/logs', this.getPacketPath());
this.captureForReplayBaseDir = path.join(process.cwd(), '/logs/', this.getLogTimestamp());
/* this.captureForReplayPath = path.join(this.captureForReplayBaseDir, '/packetCapture.json'); */
- this.cfg = config.getSection('log');
this.pkts = [];
}
private cfg;
@@ -61,6 +60,7 @@ class Logger {
private _logger: winston.Logger;
public init() {
+ this.cfg = config.getSection('log');
logger._logger = winston.createLogger({
format: winston.format.combine(winston.format.colorize(), winston.format.splat(), winston.format.simple()),
transports: [this.transports.console]
diff --git a/package.json b/package.json
index 3b37f62f..c476c020 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nodejs-poolcontroller",
- "version": "6.0.0",
+ "version": "6.0.1",
"description": "nodejs-poolController",
"main": "app.js",
"author": {
diff --git a/web/Server.ts b/web/Server.ts
index 4e165275..58c3cdb7 100755
--- a/web/Server.ts
+++ b/web/Server.ts
@@ -1,19 +1,19 @@
-/* nodejs-poolController. An application to control pool equipment.
-Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
import * as path from "path";
import * as fs from "fs";
import express=require('express');
@@ -37,6 +37,7 @@ import * as ssdp from 'node-ssdp';
import * as os from 'os';
import { URL } from "url";
import { HttpInterfaceBindings } from './interfaces/httpInterface';
+import { InfluxInterfaceBindings } from './interfaces/influxInterface';
import {Timestamp} from '../controller/Constants';
import extend = require("extend");
import { ConfigSocket } from "./services/config/ConfigSocket";
@@ -45,6 +46,7 @@ import { ConfigSocket } from "./services/config/ConfigSocket";
// external interfaces as well as an internal dashboard.
export class WebServer {
private _servers: ProtoServer[]=[];
+ private family='IPv4';
constructor() { }
public init() {
let cfg = config.getSection('web');
@@ -83,6 +85,11 @@ export class WebServer {
int.init(c);
this._servers.push(int);
break;
+ case 'influx':
+ int = new InfluxInterfaceServer();
+ int.init(c);
+ this._servers.push(int);
+ break;
}
}
}
@@ -103,16 +110,6 @@ export class WebServer {
if (typeof this._servers[s].stop() === 'function') this._servers[s].stop();
}
}
-}
-class ProtoServer {
- // base class for all servers.
- public isRunning: boolean=false;
- public emitToClients(evt: string, ...data: any) { }
- public emitToChannel(channel: string, evt: string, ...data: any) { }
- public stop() { }
- protected _dev: boolean=process.env.NODE_ENV !== 'production';
- // todo: how do we know if the client is using IPv4/IPv6?
- private family='IPv4';
private getInterface() {
const networkInterfaces = os.networkInterfaces();
// RKS: We need to get the scope-local nic. This has nothing to do with IP4/6 and is not necessarily named en0 or specific to a particular nic. We are
@@ -130,13 +127,22 @@ class ProtoServer {
}
}
}
- protected ip() {
+ public ip() {
return typeof this.getInterface() === 'undefined' ? '0.0.0.0' : this.getInterface().address;
}
- protected mac() {
+ public mac() {
return typeof this.getInterface() === 'undefined' ? '00:00:00:00' : this.getInterface().mac;
}
}
+class ProtoServer {
+ // base class for all servers.
+ public isRunning: boolean=false;
+ public emitToClients(evt: string, ...data: any) { }
+ public emitToChannel(channel: string, evt: string, ...data: any) { }
+ public stop() { }
+ protected _dev: boolean=process.env.NODE_ENV !== 'production';
+ // todo: how do we know if the client is using IPv4/IPv6?
+}
export class Http2Server extends ProtoServer {
public server: http2.Http2Server;
public app: Express.Application;
@@ -326,11 +332,11 @@ export class SsdpServer extends ProtoServer {
let self = this;
logger.info('Starting up SSDP server');
- var udn = 'uuid:806f52f4-1f35-4e33-9299-' + this.mac();
+ var udn = 'uuid:806f52f4-1f35-4e33-9299-' + webApp.mac();
// todo: should probably check if http/https is enabled at this point
var port = config.getSection('web').servers.http.port || 4200;
//console.log(port);
- let location = 'http://' + this.ip() + ':' + port + '/device';
+ let location = 'http://' + webApp.ip() + ':' + port + '/device';
var SSDP = ssdp.Server;
this.server = new SSDP({
logLevel: 'INFO',
@@ -368,7 +374,7 @@ export class SsdpServer extends ProtoServer {
https://github.com/tagyoureit/nodejs-poolController
An application to control pool equipment.
0
- uuid:806f52f4-1f35-4e33-9299-${this.mac() }
+ uuid:806f52f4-1f35-4e33-9299-${webApp.mac() }
`;
@@ -419,7 +425,7 @@ export class MdnsServer extends ProtoServer {
name: '_poolcontroller._tcp.local',
type: 'A',
ttl: 300,
- data: self.ip()
+ data: webApp.ip()
},
{
name: 'api._poolcontroller._tcp.local',
@@ -507,7 +513,66 @@ export class HttpInterfaceServer extends ProtoServer {
});
}
}
+ return true;
+ }
+ catch (err) {
+ logger.error(`Error initializing interface bindings: ${err}`);
+ }
+ return false;
+ }
+ public emitToClients(evt: string, ...data: any) {
+ if (this.isRunning) {
+ // Take the bindings and map them to the appropriate http GET, PUT, DELETE, and POST.
+ this.bindings.bindEvent(evt, ...data);
+ }
+ }
+}
+export class InfluxInterfaceServer extends ProtoServer {
+ public bindingsPath: string;
+ public bindings: InfluxInterfaceBindings;
+ private _fileTime: Date = new Date(0);
+ private _isLoading: boolean = false;
+ public init(cfg) {
+ if (cfg.enabled) {
+ if (cfg.fileName && this.initBindings(cfg)) this.isRunning = true;
+ }
+ }
+ public loadBindings(cfg): boolean {
+ this._isLoading = true;
+ if (fs.existsSync(this.bindingsPath)) {
+ try {
+ let bindings = JSON.parse(fs.readFileSync(this.bindingsPath, 'utf8'));
+ let ext = extend(true, {}, typeof cfg.context !== 'undefined' ? cfg.context.options : {}, bindings);
+ this.bindings = Object.assign(new InfluxInterfaceBindings(cfg), ext);
+ this.isRunning = true;
+ this._isLoading = false;
+ const stats = fs.statSync(this.bindingsPath);
+ this._fileTime = stats.mtime;
+ return true;
+ }
+ catch (err) {
+ logger.error(`Error reading interface bindings file: ${this.bindingsPath}. ${err}`);
+ this.isRunning = false;
+ this._isLoading = false;
+ }
+ }
+ return false;
+ }
+ public initBindings(cfg): boolean {
+ let self = this;
+ try {
+ this.bindingsPath = path.posix.join(process.cwd(), "/web/bindings") + '/' + cfg.fileName;
+ fs.watch(this.bindingsPath, (event, fileName) => {
+ if (fileName && event === 'change') {
+ if (self._isLoading) return; // Need a debounce here. We will use a semaphore to cause it not to load more than once.
+ const stats = fs.statSync(self.bindingsPath);
+ if (stats.mtime.valueOf() === self._fileTime.valueOf()) return;
+ self.loadBindings(cfg);
+ logger.info(`Reloading ${cfg.name || ''} interface config: ${fileName}`);
+ }
+ });
+ this.loadBindings(cfg);
return true;
}
catch (err) {
@@ -522,4 +587,5 @@ export class HttpInterfaceServer extends ProtoServer {
}
}
}
+
export const webApp = new WebServer();
diff --git a/web/bindings/influxDB.json b/web/bindings/influxDB.json
new file mode 100644
index 00000000..cf73a7a3
--- /dev/null
+++ b/web/bindings/influxDB.json
@@ -0,0 +1,512 @@
+{
+ "context": {
+ "name": "InfluxDB",
+ "options": {
+ "tags": [
+ {
+ "name": "sourceIP",
+ "value": "@bind=Server_1.webApp.ip();"
+ },
+ {
+ "name": "sourceApp",
+ "value": "njspc"
+ }
+ ]
+ }
+ },
+ "events": [
+ {
+ "name": "temps",
+ "description": "Bind temperatures to measurements",
+ "points": [
+ {
+ "measurement": "ambientTemps",
+ "tags": [
+ {
+ "name": "units",
+ "value": "@bind=data.units.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "airTemp",
+ "value": "@bind=data.air;",
+ "type": "int"
+ },
+ {
+ "name": "solarTemp",
+ "value": "@bind=data.solar;",
+ "type": "int"
+ }
+ ]
+ },
+ {
+ "measurement": "bodyTemps",
+ "tags": [
+ {
+ "name": "units",
+ "value": "@bind=data.units.desc;"
+ },
+ {
+ "name": "heatMode",
+ "value": "@bind=data.bodies[0].heatMode.desc;"
+ },
+ {
+ "name": "heatStatus",
+ "value": "@bind=data.bodies[0].heatStatus.desc;"
+ },
+ {
+ "name": "body",
+ "value": "@bind=data.bodies[0].name;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "@bind=data.bodies[0].name;",
+ "value": "@bind=data.bodies[0].temp;",
+ "type": "int"
+ }
+ ]
+ },
+ {
+ "measurement": "bodyTemps",
+ "tags": [
+ {
+ "name": "units",
+ "value": "@bind=data.units.desc;"
+ },
+ {
+ "name": "heatMode",
+ "value": "@bind=data.bodies[1].heatMode.desc;"
+ },
+ {
+ "name": "heatStatus",
+ "value": "@bind=data.bodies[1].heatStatus.desc;"
+ },
+ {
+ "name": "body",
+ "value": "@bind=data.bodies[1].name;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "@bind=data.bodies[1].name;",
+ "value": "@bind=data.bodies[1].temp;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "body",
+ "description": "Bind bodies to measurements",
+ "points": [
+ {
+ "measurement": "bodyTemps",
+ "tags": [
+ {
+ "name": "heatMode",
+ "value": "@bind=data.heatMode.desc;"
+ },
+ {
+ "name": "heatStatus",
+ "value": "@bind=data.heatStatus.desc;"
+ },
+ {
+ "name": "body",
+ "value": "@bind=data.name;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "@bind=data.name;",
+ "value": "@bind=data.temp;",
+ "type": "int"
+ },
+ {
+ "name": "@bind=data.name+'Setpoint';",
+ "value": "@bind=data.setPoint;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "chemController",
+ "description": "Bind chemController emit",
+ "points": [
+ {
+ "measurement": "chemControllers",
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ },
+ {
+ "name": "status",
+ "value": "@bind=data.status.desc;"
+ },
+ {
+ "name": "status1",
+ "value": "@bind=data.status1.desc;"
+ },
+ {
+ "name": "status2",
+ "value": "@bind=data.status2.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "pHLevel",
+ "value": "@bind=data.pHLevel;",
+ "type": "float"
+ },
+ {
+ "name": "pHSetpoint",
+ "value": "@bind=data.pHSetpoint;",
+ "type": "float"
+ },
+ {
+ "name": "orpLevel",
+ "value": "@bind=data.orpLevel;",
+ "type": "float"
+ },
+ {
+ "name": "orpSetpoint",
+ "value": "@bind=data.orpSetpoint;",
+ "type": "float"
+ },
+ {
+ "name": "acidTankLevel",
+ "value": "@bind=data.acidTankLevel;",
+ "type": "int"
+ },
+ {
+ "name": "orpTankLevel",
+ "value": "@bind=data.orpTankLevel;",
+ "type": "int"
+ },
+ {
+ "name": "saturationIndex",
+ "value": "@bind=data.saturationIndex;",
+ "type": "float"
+ },
+ {
+ "name": "CYA",
+ "value": "@bind=data.cyanuricAcid;",
+ "type": "int"
+ },
+ {
+ "name": "CH",
+ "value": "@bind=data.calciumHardness;",
+ "type": "int"
+ },
+ {
+ "name": "Alk",
+ "value": "@bind=data.alkalinity;",
+ "type": "int"
+ },
+ {
+ "name": "phDosingTime",
+ "value": "@bind=data.pHDosingTime;",
+ "type": "float"
+ },
+ {
+ "name": "orpDosingTime",
+ "value": "@bind=data.orpDosingTime;",
+ "type": "float"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "circuit",
+ "description": "Bind circuit emit",
+ "points": [
+ {
+ "measurement": "circuits",
+ "storePrevState": true,
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "isOn",
+ "value": "@bind=data.isOn;",
+ "type": "boolean"
+ },
+ {
+ "name": "isOnVal",
+ "value": "@bind=data.isOn?1:0;",
+ "type": "integer"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "feature",
+ "description": "Bind feature emit",
+ "points": [
+ {
+ "measurement": "circuits",
+ "storePrevState": true,
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "isOn",
+ "value": "@bind=data.isOn;",
+ "type": "boolean"
+ },
+ {
+ "name": "isOnVal",
+ "value": "@bind=data.isOn?1:0;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "virtualCircuit",
+ "description": "Bind virtualCircuit emit",
+ "points": [
+ {
+ "measurement": "circuits",
+ "storePrevState": true,
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "isOn",
+ "value": "@bind=data.isOn;",
+ "type": "boolean"
+ },
+ {
+ "name": "isOnVal",
+ "value": "@bind=data.isOn?1:0;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "lightGroup",
+ "description": "Bind lightGroup emit",
+ "points": [
+ {
+ "measurement": "circuits",
+ "storePrevState": true,
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "isOn",
+ "value": "@bind=data.isOn;",
+ "type": "boolean"
+ },
+ {
+ "name": "isOnVal",
+ "value": "@bind=data.isOn?1:0;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "circuitGroup",
+ "description": "Bind circuitGroup emit",
+ "points": [
+ {
+ "measurement": "circuits",
+ "storePrevState": true,
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "isOn",
+ "value": "@bind=data.isOn;",
+ "type": "boolean"
+ },
+ {
+ "name": "isOnVal",
+ "value": "@bind=data.isOn?1:0;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "pump",
+ "description": "Bind circuit emit",
+ "points": [
+ {
+ "measurement": "pumps",
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "type",
+ "value": "@bind=data.type.desc;"
+ },
+ {
+ "name": "status",
+ "value": "@bind=data.status.desc;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "rpm",
+ "value": "@bind=data.rpm;",
+ "type": "int"
+ },
+ {
+ "name": "gpm",
+ "value": "@bind=data.gpm;",
+ "type": "int"
+ },
+ {
+ "name": "watts",
+ "value": "@bind=data.watts;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "chlorinator",
+ "description": "Bind circuit emit",
+ "points": [
+ {
+ "measurement": "chlorinators",
+ "tags": [
+ {
+ "name": "name",
+ "value": "@bind=data.name;"
+ },
+ {
+ "name": "id",
+ "value": "@bind=data.id;"
+ },
+ {
+ "name": "status",
+ "value": "@bind=data.status.desc;"
+ },
+ {
+ "name": "superChlor",
+ "value": "@bind=data.superChlor;"
+ },
+ {
+ "name": "superChlorHours",
+ "value": "@bind=data.superChlorHours;"
+ }
+ ],
+ "fields": [
+ {
+ "name": "currentOutput",
+ "value": "@bind=data.currentOutput;",
+ "type": "int"
+ },
+ {
+ "name": "poolSetpoint",
+ "value": "@bind=data.poolSetpoint;",
+ "type": "int"
+ },
+ {
+ "name": "saltLevel",
+ "value": "@bind=data.saltLevel;",
+ "type": "int"
+ },
+ {
+ "name": "spaSetpoint",
+ "value": "@bind=data.spaSetpoint;",
+ "type": "int"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "config",
+ "description": "Not used for updates",
+ "enabled": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/web/interfaces/baseInterface.ts b/web/interfaces/baseInterface.ts
new file mode 100644
index 00000000..a813bcab
--- /dev/null
+++ b/web/interfaces/baseInterface.ts
@@ -0,0 +1,76 @@
+import extend = require("extend");
+import { logger } from "../../logger/Logger";
+import { sys } from "../../controller/Equipment";
+import { state } from "../../controller/State";
+import { webApp } from '../Server';
+
+export class BaseInterfaceBindings {
+ constructor(cfg) {
+ this.cfg = cfg;
+ }
+ public context: InterfaceContext;
+ public cfg;
+ public events: InterfaceEvent[];
+ public bindEvent(evt: string, ...data: any) { };
+ protected buildTokens(input: string, eventName: string, toks: any, e: InterfaceEvent, data): any {
+ toks = toks || [];
+ let s = input;
+ let regx = /(?<=@bind\=\s*).*?(?=\;)/g;
+ let match;
+ let vars = extend(true, {}, this.cfg.vars, this.context.vars, typeof e !== 'undefined' && e.vars);
+ let sys1 = sys;
+ let state1 = state;
+ let webApp1 = webApp;
+ // Map all the returns to the token list. We are being very basic
+ // here an the object graph is simply based upon the first object occurrence.
+ // We simply want to eval against that object reference.
+
+ while (match = regx.exec(s)) {
+ let bind = match[0];
+ if (typeof toks[bind] !== 'undefined') continue;
+ let tok: any = {};
+ toks[bind] = tok;
+ try {
+ // we may error out if data can't be found (eg during init)
+ tok.reg = new RegExp("@bind=" + this.escapeRegex(bind) + ";", "g");
+ tok.value = eval(bind.replace(/sys./g, 'Equipment_1.sys.').replace(/state./g, 'State_1.state.'));
+ }
+ catch (err) {
+ // leave value undefined so it isn't sent to bindings
+ tok[bind] = null;
+ }
+ }
+ return toks;
+
+ }
+ protected escapeRegex(reg: string) {
+ return reg.replace(/[-[\]{}()*+?.,\\^$]/g, '\\$&');
+ }
+ protected replaceTokens(input: string, toks: any) {
+ let s = input;
+ for (let exp in toks) {
+ let tok = toks[exp];
+ if (typeof tok.reg === 'undefined') continue;
+ tok.reg.lastIndex = 0; // Start over if we used this before.
+ if (typeof tok.value === 'string') s = s.replace(tok.reg, tok.value);
+ else if (typeof tok.value === 'undefined') s = s.replace(tok.reg, 'null');
+ else s = s.replace(tok.reg, JSON.stringify(tok.value));
+ }
+ return s;
+ }
+}
+
+export class InterfaceEvent {
+ public name: string;
+ public enabled: boolean = true;
+ public options: any = {};
+ public body: any = {};
+ public vars: any = {};
+}
+export class InterfaceContext {
+ public name: string;
+ public mdnsDiscovery: any;
+ public upnpDevice: any;
+ public options: any = {};
+ public vars: any = {};
+}
diff --git a/web/interfaces/httpInterface.ts b/web/interfaces/httpInterface.ts
index cda32510..0fdaa361 100644
--- a/web/interfaces/httpInterface.ts
+++ b/web/interfaces/httpInterface.ts
@@ -22,16 +22,15 @@ import extend=require("extend");
import { logger } from "../../logger/Logger";
import { sys } from "../../controller/Equipment";
import { state } from "../../controller/State";
+import { InterfaceContext, InterfaceEvent, BaseInterfaceBindings } from "./baseInterface";
-export class HttpInterfaceBindings {
+export class HttpInterfaceBindings extends BaseInterfaceBindings {
constructor(cfg) {
- this.cfg = cfg;
+ super(cfg);
}
- public context: HttpInterfaceContext;
- public cfg;
- public events: HttpInterfaceEvent[];
public bindEvent(evt: string, ...data: any) {
- // Find the binding by first looking for the specific event name. If that doesn't exist then look for the "*" (all events).
+ // Find the binding by first looking for the specific event name.
+ // If that doesn't exist then look for the "*" (all events).
if (typeof this.events !== 'undefined') {
let evts = this.events.filter(elem => elem.name === evt);
// If we don't have an explicitly defined event then see if there is a default.
@@ -114,50 +113,5 @@ export class HttpInterfaceBindings {
}
}
}
- private buildTokens(input: string, eventName: string, toks: any, e: HttpInterfaceEvent, data): any {
- toks = toks || [];
- let s = input;
- let regx = /(?<=@bind\=\s*).*?(?=\;)/g;
- let match;
- let vars = extend(true, {}, this.cfg.vars, this.context.vars, e.vars);
- // Map all the returns to the token list. We are being very basic
- // here an the object graph is simply based upon the first object occurrence.
- // We simply want to eval against that object reference.
- while (match = regx.exec(s)) {
- let bind = match[0];
- if (typeof toks[bind] !== 'undefined') continue;
- let tok: any = {};
- toks[bind] = tok;
- tok.value = eval(bind);
- tok.reg = new RegExp("@bind=" + this.escapeRegex(bind) + ";", "g");
- }
- return toks;
- }
- private escapeRegex(reg: string) {
- return reg.replace(/[-[\]{}()*+?.,\\^$]/g, '\\$&');
- }
- private replaceTokens(input: string, toks: any) {
- let s = input;
- for (let exp in toks) {
- let tok = toks[exp];
- tok.reg.lastIndex = 0; // Start over if we used this before.
- if (typeof tok.value === 'string') s = s.replace(tok.reg, tok.value);
- else if (typeof tok.value === 'undefined') s = s.replace(tok.reg, 'null');
- else s = s.replace(tok.reg, JSON.stringify(tok.value));
- }
- return s;
- }
-}
-export class HttpInterfaceEvent {
- public name: string;
- public enabled: boolean=true;
- public options: any={};
- public body: any={};
- public vars: any={};
-}
-export class HttpInterfaceContext {
- public mdnsDiscovery: any;
- public upnpDevice: any;
- public options: any={};
- public vars: any={};
}
+
diff --git a/web/interfaces/influxInterface.ts b/web/interfaces/influxInterface.ts
new file mode 100644
index 00000000..43030f06
--- /dev/null
+++ b/web/interfaces/influxInterface.ts
@@ -0,0 +1,181 @@
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+import extend = require("extend");
+import { logger } from "../../logger/Logger";
+import { InfluxDB, Point, HttpError, WritePrecision, ClientOptions } from '@influxdata/influxdb-client';
+import { InterfaceEvent, InterfaceContext, BaseInterfaceBindings } from "./baseInterface";
+import { utils } from '../../controller/Constants';
+import { state } from '../../controller/State';
+import { sys } from "../../controller/Equipment";
+
+
+export class InfluxInterfaceBindings extends BaseInterfaceBindings {
+ constructor(cfg) {
+ super(cfg);
+ }
+ private writeApi;
+ public context: InterfaceContext;
+ public cfg;
+ public events: InfluxInterfaceEvent[];
+ private init = () => {
+ let baseOpts = extend(true, this.cfg.options, this.context.options);
+ if (typeof baseOpts.host === 'undefined' || !baseOpts.host) {
+ logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid host.`);
+ return;
+ }
+ if (typeof baseOpts.database === 'undefined' || !baseOpts.database) {
+ logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid database.`);
+ return;
+ }
+ // let opts = extend(true, baseOpts, e.options);
+ let url = 'http';
+ if (typeof baseOpts.protocol !== 'undefined' && baseOpts.protocol) url = baseOpts.protocol;
+ url = `${url}://${baseOpts.host}:${baseOpts.port}`;
+ // TODO: add username/password
+ const bucket = `${baseOpts.database}/${baseOpts.retentionPolicy}`;
+ const clientOptions:ClientOptions = {
+ url,
+ token: `${baseOpts.username}:${baseOpts.password}`,
+ }
+ const influxDB = new InfluxDB(clientOptions);
+ this.writeApi = influxDB.getWriteApi('', bucket, 'ms' as WritePrecision);
+
+ // set global tags from context
+ let baseTags = {}
+ baseOpts.tags.forEach(tag=> {
+ let toks = {};
+ let sname = tag.name;
+ this.buildTokens(sname, undefined, toks, undefined, {});
+ sname = this.replaceTokens(sname, toks);
+ let svalue = tag.value;
+ this.buildTokens(svalue, undefined, toks, {vars:{}} as any, {});
+ svalue = this.replaceTokens(svalue, toks);
+ if (typeof sname !== 'undefined' && typeof svalue !== 'undefined' && !sname.includes('@bind') && !svalue.includes('@bind'))
+ baseTags[sname] = svalue;
+ })
+ this.writeApi.useDefaultTags(baseTags);
+ }
+ public bindEvent(evt: string, ...data: any) {
+
+ // if (state.status.value !== sys.board.valueMaps.controllerStatus.getValue('ready')) return; // miss values? or show errors? or?
+ if (typeof this.events !== 'undefined') {
+ if (typeof this.writeApi === 'undefined') this.init();
+ let evts = this.events.filter(elem => elem.name === evt);
+ if (evts.length > 0) {
+ let toks = {};
+ for (let i = 0; i < evts.length; i++) {
+ let e = evts[i];
+ if (typeof e.enabled !== 'undefined' && !e.enabled) continue;
+ e.points.forEach(_point => {
+ // iterate through points array
+ let point = new Point(_point.measurement)
+ let point2 = new Point(_point.measurement);
+ _point.tags.forEach(_tag => {
+ let sname = _tag.name;
+ this.buildTokens(sname, evt, toks, e, data[0]);
+ sname = this.replaceTokens(sname, toks);
+ let svalue = _tag.value;
+ this.buildTokens(svalue, evt, toks, e, data[0]);
+ svalue = this.replaceTokens(svalue, toks);
+ if (typeof sname !== 'undefined' && typeof svalue !== 'undefined' && !sname.includes('@bind') && !svalue.includes('@bind') && svalue !== null){
+ point.tag(sname, svalue);
+ if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.tag(sname, svalue);
+ }
+ else {
+ console.log(`failed on ${_tag.name}/${_tag.value}`);
+ }
+ })
+ _point.fields.forEach(_field => {
+ let sname = _field.name;
+ this.buildTokens(sname, evt, toks, e, data[0]);
+ sname = this.replaceTokens(sname, toks);
+ let svalue = _field.value;
+ this.buildTokens(svalue, evt, toks, e, data[0]);
+ svalue = this.replaceTokens(svalue, toks);
+ if (typeof sname !== 'undefined' && typeof svalue !== 'undefined' && !sname.includes('@bind') && !svalue.includes('@bind') && svalue !== null)
+ switch (_field.type) {
+ case 'int':
+ case 'integer':
+ let int = parseInt(svalue, 10);
+ if (!isNaN(int)) point.intField(sname, int);
+ // if (!isNaN(int) && typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.intField(sname, int);
+ break;
+ case 'string':
+ point.stringField(sname, svalue);
+ // if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.stringField(sname, svalue);
+ break;
+ case 'boolean':
+ point.booleanField(sname, utils.makeBool(svalue));
+ if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.booleanField(sname, !utils.makeBool(svalue));
+ break;
+ case 'float':
+ let float = parseFloat(svalue);
+ if (!isNaN(float)) point.floatField(sname, float);
+ // if (!isNaN(float) && typeof _point.storePrevState !== 'undefined' && _point.storePrevState) point2.intField(sname, int);
+ break;
+ }
+ else {
+ console.log(`failed on ${_field.name}/${_field.value}`);
+
+ }
+ })
+ point.timestamp(new Date());
+ if (typeof _point.storePrevState !== 'undefined' && _point.storePrevState){
+ // copy the point and subtract a second and keep inverse value
+ let ts = new Date();
+ let sec = ts.getSeconds() - 1;
+ ts.setSeconds(sec);
+ point2.timestamp(ts);
+ this.writeApi.writePoint(point2);
+ }
+ if (typeof point.toLineProtocol() !== 'undefined'){
+ this.writeApi.writePoint(point);
+ logger.info(`INFLUX: ${point.toLineProtocol()}`)
+ }
+ else {
+ logger.silly(`Skipping INFLUX write because some data is missing with ${e.name} event on measurement ${_point.measurement}.`)
+ }
+ })
+ }
+ }
+ }
+ }
+ public close = () => {
+
+ }
+}
+
+class InfluxInterfaceEvent extends InterfaceEvent {
+ public points: IPoint[];
+}
+
+export interface IPoint {
+ measurement: string;
+ tags: ITag[];
+ fields: IFields[];
+ storePrevState?: boolean;
+}
+export interface ITag {
+ name: string;
+ value: string;
+}
+export interface IFields {
+ name: string;
+ value: string;
+ type: string;
+}
diff --git a/web/services/state/State.ts b/web/services/state/State.ts
index b72f95fa..c4d1b425 100755
--- a/web/services/state/State.ts
+++ b/web/services/state/State.ts
@@ -1,19 +1,19 @@
-/* nodejs-poolController. An application to control pool equipment.
-Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .
-*/
+/* nodejs-poolController. An application to control pool equipment.
+Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
import * as express from "express";
import { state, ICircuitState, LightGroupState, ICircuitGroupState } from "../../../controller/State";
import { sys } from "../../../controller/Equipment";
@@ -28,8 +28,8 @@ export class StateRoute {
});
app.put('/state/chemController', async (req, res, next) => {
try {
- let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
- return res.status(200).send(state.chemControllers.getItemById(chem.id).getExtended());
+ let schem = await sys.board.chemControllers.setChemControllerAsync(req.body);
+ return res.status(200).send(state.chemControllers.getItemById(schem.id).getExtended());
}
catch (err) { next(err); }
});
@@ -46,47 +46,46 @@ export class StateRoute {
});
app.put('/state/circuit/setState', async (req, res, next) => {
try {
- console.log(`request: ${JSON.stringify(req.body)}... id: ${req.body.id} state: ${req.body.state} isOn: ${req.body.isOn}`);
// Do some work to allow the legacy state calls to work. For some reason the state value is generic while all of the
// circuits are actually binary states. While this may need to change in the future it seems like a distant plan
// that circuits would have more than 2 states. Not true for other equipment but certainly true for individual circuits/features/groups.
let isOn = utils.makeBool(typeof req.body.isOn !== 'undefined' ? req.body.isOn : req.body.state);
//state.circuits.setCircuitState(parseInt(req.body.id, 10), utils.makeBool(req.body.state));
- let circuit = await sys.board.circuits.setCircuitStateAsync(parseInt(req.body.id, 10), isOn);
- return res.status(200).send((circuit as ICircuitState).get(true));
+ let cstate = await sys.board.circuits.setCircuitStateAsync(parseInt(req.body.id, 10), isOn);
+ return res.status(200).send(cstate.get(true));
}
catch (err) { next(err); }
});
app.put('/state/circuitGroup/setState', async (req, res, next) => {
console.log(`request: ${JSON.stringify(req.body)}... id: ${req.body.id} state: ${req.body.state} isOn: ${req.body.isOn}`);
let isOn = utils.makeBool(typeof req.body.isOn !== 'undefined' ? req.body.isOn : req.body.state);
- let circuit = await sys.board.circuits.setCircuitGroupStateAsync(parseInt(req.body.id, 10), isOn);
- return res.status(200).send((circuit as ICircuitGroupState).get(true));
+ let cstate = await sys.board.circuits.setCircuitGroupStateAsync(parseInt(req.body.id, 10), isOn);
+ return res.status(200).send(cstate.get(true));
});
app.put('/state/lightGroup/setState', async (req, res, next) => {
console.log(`request: ${JSON.stringify(req.body)}... id: ${req.body.id} state: ${req.body.state} isOn: ${req.body.isOn}`);
let isOn = utils.makeBool(typeof req.body.isOn !== 'undefined' ? req.body.isOn : req.body.state);
- let circuit = await sys.board.circuits.setLightGroupStateAsync(parseInt(req.body.id, 10), isOn);
- return res.status(200).send((circuit as ICircuitGroupState).get(true));
+ let cstate = await sys.board.circuits.setLightGroupStateAsync(parseInt(req.body.id, 10), isOn);
+ return res.status(200).send(cstate.get(true));
});
app.put('/state/circuit/toggleState', async (req, res, next) => {
try {
let cstate = await sys.board.circuits.toggleCircuitStateAsync(parseInt(req.body.id, 10));
- return res.status(200).send(cstate);
+ return res.status(200).send(cstate.get(true));
}
catch (err) {next(err);}
});
app.put('/state/feature/toggleState', async (req, res, next) => {
try {
let fstate = await sys.board.features.toggleFeatureStateAsync(parseInt(req.body.id, 10));
- return res.status(200).send(fstate);
+ return res.status(200).send(fstate.get(true));
}
catch (err) {next(err);}
});
app.put('/state/circuit/setTheme', async (req, res, next) => {
try {
let theme = await state.circuits.setLightThemeAsync(parseInt(req.body.id, 10), parseInt(req.body.theme, 10));
- return res.status(200).send(theme);
+ return res.status(200).send(theme.get(true));
}
catch (err) { next(err); }
});
@@ -105,8 +104,8 @@ export class StateRoute {
});
app.put('/state/circuit/setDimmerLevel', async (req, res, next) => {
try {
- let circuit = await sys.board.circuits.setDimmerLevelAsync(parseInt(req.body.id, 10), parseInt(req.body.level, 10));
- return res.status(200).send(circuit);
+ let cstate = await sys.board.circuits.setDimmerLevelAsync(parseInt(req.body.id, 10), parseInt(req.body.level, 10));
+ return res.status(200).send(cstate.get(true));
}
catch (err) { next(err); }
});
@@ -114,7 +113,7 @@ export class StateRoute {
try {
let isOn = utils.makeBool(typeof req.body.isOn !== 'undefined' ? req.body.isOn : req.body.state);
let fstate = await state.features.setFeatureStateAsync(req.body.id, isOn);
- return res.status(200).send(fstate);
+ return res.status(200).send(fstate.get(true));
}
catch (err){ next(err); }
});
@@ -134,7 +133,7 @@ export class StateRoute {
let body = sys.bodies.findByObject(req.body);
if (typeof body === 'undefined') return next(new ServiceParameterError(`Cannot set body heatMode. You must supply a valid id, circuit, name, or type for the body`, 'body', 'id', req.body.id));
let tbody = await sys.board.bodies.setHeatModeAsync(body, mode);
- return res.status(200).send(tbody);
+ return res.status(200).send(tbody.get(true));
} catch (err) { next(err); }
});
app.put('/state/body/setPoint', async (req, res, next) => {
@@ -143,51 +142,51 @@ export class StateRoute {
let body = sys.bodies.findByObject(req.body);
if (typeof body === 'undefined') return next(new ServiceParameterError(`Cannot set body setPoint. You must supply a valid id, circuit, name, or type for the body`, 'body', 'id', req.body.id));
let tbody = await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.setPoint, 10));
- return res.status(200).send(tbody);
+ return res.status(200).send(tbody.get(true));
} catch (err) { next(err); }
});
app.put('/state/chlorinator', async (req, res, next) => {
try {
- let chlor = await sys.board.chlorinator.setChlorAsync(req.body);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(req.body);
+ return res.status(200).send(schlor.get(true));
} catch (err) { next(err); }
});
// this ../setChlor should really be EOL for PUT /state/chlorinator above
app.put('/state/chlorinator/setChlor', async (req, res, next) => {
try {
- let chlor = await sys.board.chlorinator.setChlorAsync(req.body);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(req.body);
+ return res.status(200).send(schlor.get(true));
} catch (err) { next(err); }
});
app.put('/state/chlorinator/poolSetpoint', async (req, res, next) => {
try {
let obj = { id: req.body.id, poolSetpoint: parseInt(req.body.setPoint, 10) }
- let chlor = await sys.board.chlorinator.setChlorAsync(obj);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(obj);
+ return res.status(200).send(schlor.get(true));
}
catch (err) { next(err); }
});
app.put('/state/chlorinator/spaSetpoint', async (req, res, next) => {
try {
let obj = { id: req.body.id, spaSetpoint: parseInt(req.body.setPoint, 10) }
- let chlor = await sys.board.chlorinator.setChlorAsync(obj);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(obj);
+ return res.status(200).send(schlor.get(true));
}
catch (err) { next(err); }
});
app.put('/state/chlorinator/superChlorHours', async (req, res, next) => {
try {
let obj = { id: req.body.id, superChlorHours: parseInt(req.body.hours, 10) }
- let chlor = await sys.board.chlorinator.setChlorAsync(obj);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(obj);
+ return res.status(200).send(schlor.get(true));
}
catch (err) { next(err); }
});
app.put('/state/chlorinator/superChlorinate', async (req, res, next) => {
try {
let obj = { id: req.body.id, superChlorinate: utils.makeBool(req.body.superChlorinate) }
- let chlor = await sys.board.chlorinator.setChlorAsync(obj);
- return res.status(200).send(chlor);
+ let schlor = await sys.board.chlorinator.setChlorAsync(obj);
+ return res.status(200).send(schlor.get(true));
}
catch (err) { next(err); }
});