diff --git a/.snyk b/.snyk index 6a931b1e..8db49efd 100644 --- a/.snyk +++ b/.snyk @@ -118,20 +118,23 @@ ignore: 'npm:bootstrap:20160627': - bootstrap: reason: None given - expires: '2018-10-05T03:45:41.397Z' + expires: '2018-12-26T15:26:16.181Z' 'npm:lodash:20180130': - snyk-go-plugin > graphlib > lodash: reason: None given expires: '2018-05-27T15:05:54.491Z' - request-promise > request-promise-core > lodash: + node-ssdp > async > lodash: reason: None given - expires: '2018-11-13T23:00:17.087Z' + expires: '2019-02-03T00:16:15.673Z' - node-ssdp > async > lodash: reason: None given - expires: '2018-04-01T03:18:10.568Z' + expires: '2018-12-26T15:26:16.181Z' + request-promise > request-promise-core > lodash: + reason: None given + expires: '2019-02-03T00:16:15.674Z' - request-promise > request-promise-core > lodash: reason: None given - expires: '2018-04-01T03:18:10.569Z' + expires: '2018-12-26T15:26:16.181Z' - node-ip > node-localip > wmic > async > lodash: reason: None given expires: '2018-04-01T03:18:10.568Z' @@ -150,6 +153,12 @@ ignore: - request-promise > request-promise-core > lodash: reason: None given expires: '2018-08-16T06:24:46.574Z' + - request-promise > request-promise-core > lodash: + reason: None given + expires: '2018-11-13T23:00:17.087Z' + - node-ip > node-localip > wmic > async > lodash: + reason: None given + expires: '2019-01-28T22:22:52.646Z' 'npm:hoek:20180212': - request > hawk > hoek: reason: None given @@ -173,7 +182,7 @@ ignore: 'npm:extend:20180424': - node-ssdp > extend: reason: None given - expires: '2018-11-13T23:00:17.087Z' + expires: '2019-01-28T22:22:52.645Z' - request > extend: reason: None given expires: '2018-08-23T22:44:40.819Z' @@ -199,7 +208,7 @@ ignore: 'npm:chownr:20180731': - serialport > prebuild-install > tar-fs > chownr: reason: None given - expires: '2018-10-05T03:45:29.603Z' + expires: '2019-01-28T22:18:22.043Z' - chokidar > fsevents > node-pre-gyp > tar > chownr: reason: None given expires: '2018-09-01T14:44:44.769Z' diff --git a/README.md b/README.md index 8eba71a3..44b6b742 100755 --- a/README.md +++ b/README.md @@ -1,21 +1,20 @@ -# nodejs-poolController - Version 5.2.0 + +# nodejs-poolController - Version 5.3.0 + [![Join the chat at https://gitter.im/nodejs-poolController/Lobby](https://badges.gitter.im/nodejs-poolController/Lobby.svg)](https://gitter.im/nodejs-poolController/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/tagyoureit/nodejs-poolController.svg?branch=master)](https://travis-ci.org/tagyoureit/nodejs-poolController) [![Coverage Status](https://coveralls.io/repos/github/tagyoureit/nodejs-poolController/badge.svg?branch=master)](https://coveralls.io/github/tagyoureit/nodejs-poolController?branch=master) [![Known Vulnerabilities](https://snyk.io/test/github/tagyoureit/nodejs-poolcontroller/badge.svg)](https://snyk.io/test/github/tagyoureit/nodejs-poolcontroller) [Full Changelog](#module_nodejs-poolController--changelog) -### 5.2.0 -1. Node 6+ is supported. This app no longer supports Node 4. -1. Update of modules. Make sure to run `npm i` or `npm upgrade` to get the latest. -1. Much better support of multiple Intellibrite controllers. We can read both controllers now. There are still some issues with sending changes and help is needed to debug these. -1. Chlorinator API calls (and UI) will now make changes through Intellitouch when available, or directly to the Intellichlor if it is standalone (aka using the virtual controller) -1. Decoupled serial port and processing of packets. Should help recovery upon packet errors. -1. Implementation of #89. Expansion boards are now (better) supported by setting variables in your config.json. See the [config.json](#module_nodejs-poolController--config) section below. -1. Fix for #95 -1. Fix for #99 -1. Fix for #100 + +### 5.3.0 +1. Fix for #106 +1. Fix for "Error 60" messages +1. Improved caching of files on browsers. Thanks @arrmo! Now files will be loaded once in the browser and kept in cache instead of reloaded each time. +1. Improved handling of sessions and graceful closing of the HTTP(s) servers. + # License @@ -198,10 +197,14 @@ for discussions, designs, and clarifications, we recommend you join our [Gitter #### Chlorinator and Intellichem - +(Note: As of 5.3 the Chlorinator API's will route the commands either through the Intellitouch/Intellicom or directly to the chlorinator depending upon your setup) | Direction | Socket | API | Description | | --- | --- | --- | --- | -| To app | setchlorinator(level) | /chlorinator/{level}|sets the level of output for chlorinator (pool only) +| To app | setchlorinator(poolLevel, spaLevel, superChlorinateHours) | /chlorinator/{level}/spa/{level}/superChlorinateHours/{hours}|sets the level of output for chlorinator (spa/superchlorinate can be omitted) +| To app | | /chlorinator/pool/{level}|sets the pool output % +| To app | | /chlorinator/spa/{level}|sets the spa output % +| To app | | /chlorinator/pool/{level}/spa/{level}|sets the pool & spa output % +| To app | | /chlorinator/superChlorinateHours/{hours}|sets the hours for super chlorination | To client | chlorinator | outputs an object with the chlorinator information | To app | | /chlorinator | outputs an object with the chlorinator information | To app | intellichem | /intellichem |outputs an object with the intellichem information @@ -947,7 +950,16 @@ See the constants.js file and the sections: 1. Started to move some of the inter-communications to emitter events for better micro-services and shorter call stacks (easier debugging; loosely coupled code). 1. Changed some Influx tags/queries. - +### 5.2.0 +1. Node 6+ is supported. This app no longer supports Node 4. +1. Update of modules. Make sure to run `npm i` or `npm upgrade` to get the latest. +1. Much better support of multiple Intellibrite controllers. We can read both controllers now. There are still some issues with sending changes and help is needed to debug these. +1. Chlorinator API calls (and UI) will now make changes through Intellitouch when available, or directly to the Intellichlor if it is standalone (aka using the virtual controller) +1. Decoupled serial port and processing of packets. Should help recovery upon packet errors. +1. Implementation of #89. Expansion boards are now (better) supported by setting variables in your config.json. See the [config.json](#module_nodejs-poolController--config) section below. +1. Fix for #95 +1. Fix for #99 +1. Fix for #100 diff --git a/package.json b/package.json index c888ef38..16c77b42 100644 --- a/package.json +++ b/package.json @@ -41,16 +41,17 @@ }, "homepage": "https://github.com/tagyoureit/nodejs-poolController#readme", "dependencies": { - "bluebird": "^3.5.0", - "bootstrap": "^3.3.7", + "bluebird": "^3.5.3", + "bootstrap": "^3.4.0", "bottlejs": "^1.7.1", "dateformat": "^3.0.3", "deep-diff": "^1.0.1", "dequeue": "^1.0.5", "events": "^3.0.0", - "express": "^4.16.3", + "express": "^4.16.4", "getmac": "^1.4.3", "http-auth": "^3.1.3", + "http-shutdown": "^1.2.0", "influx": "^5.0.7", "ip": "^1.1.5", "jquery": "^3.2.1", @@ -65,24 +66,24 @@ "request": "^2.87.0", "request-promise": "^4.2.2", "serialport": "^6.2.2", - "socket.io": "^2.1.1", - "socket.io-client": "^2.1.1", + "socket.io": "^2.2.0", + "socket.io-client": "^2.2.0", "underscore": "^1.9.1", "winston": "^2.4.3", - "winston-transport": "^4.2.0", + "winston-transport": "^4.3.0", "yargs-parser": "^10.1.0" }, "devDependencies": { - "chai": "^4.0.1", - "coveralls": "^3.0.1", + "chai": "^4.2.0", + "coveralls": "^3.0.2", "grunt": "^1.0.3", "grunt-banner": "^0.6.0", "istanbul": "^0.4.5", - "jshint": "^2.9.4", + "jshint": "^2.9.7", "mocha": "^5.2.0", "nock": "^9.3.3", "sinon": "^6.1.4", - "snyk": "^1.83.0" + "snyk": "^1.120.1" }, "snyk": true } diff --git a/specs/assets/config/config.json b/specs/assets/config/config.json index bbf0b517..1d2b424b 100644 --- a/specs/assets/config/config.json +++ b/specs/assets/config/config.json @@ -1,5 +1,16 @@ { - "version": "5.2.0", + "meta": { + "notifications": { + "version": { + "remote": { + "version": "4.1.200", + "tag_name": "v4.1.200", + "dismissUntilNextRemoteVersionBump": false + } + } + } + }, + "version": "5.3.0", "equipment": { "controller": { "intellicom": { @@ -141,16 +152,5 @@ } } }, - "meta": { - "notifications": { - "version": { - "remote": { - "version": "4.0.0", - "tag_name": "v4.0.0", - "dismissUntilNextRemoteVersionBump": false - } - } - } - }, "integrations": {} } \ No newline at end of file diff --git a/specs/assets/replays/1/config.json b/specs/assets/replays/1/config.json index 6fb0978f..3e8a7dfb 100644 --- a/specs/assets/replays/1/config.json +++ b/specs/assets/replays/1/config.json @@ -1,5 +1,5 @@ { - "version": "5.2.0", + "version": "5.3.01", "equipment": { "controller": { "intellicom": { @@ -292,4 +292,4 @@ "integrations": { "outputToNodeRED": 1 } -} +} \ No newline at end of file diff --git a/specs/assets/replays/1/packetCapture.json b/specs/assets/replays/1/packetCapture.json index 031a0604..79199a03 100644 --- a/specs/assets/replays/1/packetCapture.json +++ b/specs/assets/replays/1/packetCapture.json @@ -606,4 +606,4 @@ {"type":"packet","packet":[255,255,255,255,255,255,255,255,0,255,165,1,15,16,2,29,15,10,2,0,0,0,0,0,0,4,64,0,0,0,26,26],"direction":"inbound","level":"info","message":"","timestamp":"2018-08-01T19:11:22.818Z"} {"type":"packet","packet":[0,0,27,0,0,0,0,4,0,0,0,1,3,1,154],"direction":"inbound","level":"info","message":"","timestamp":"2018-08-01T19:11:22.840Z"} {"type":"packet","packet":[255,255,255,255,255,255,255,255,0,255,165,1,15,16,2,29,15,10,2,0,0,0,0,0,0,4,64,0,0,0,26,26],"direction":"inbound","level":"info","message":"","timestamp":"2018-08-01T19:11:24.774Z"} -{"type":"packet","packet":[0,0,27,0,0,0,0,4,0,0,0,1,3,1,154],"direction":"inbound","level":"info","message":"","timestamp":"2018-08-01T19:11:24.796Z"} +{"type":"packet","packet":[0,0,27,0,0,0,0,4,0,0,0,1,3,1,154],"direction":"inbound","level":"info","message":"","timestamp":"2018-08-01T19:11:24.796Z"} \ No newline at end of file diff --git a/specs/assets/replays/1/packetCapture.log b/specs/assets/replays/1/packetCapture.log index efa6177c..59a97a35 100644 --- a/specs/assets/replays/1/packetCapture.log +++ b/specs/assets/replays/1/packetCapture.log @@ -9,11 +9,11 @@ 15:09:47.871 INFO Settings: ******************************* - Version: 5.2.0 + Version: 5.3.01 Configuration file name: config.json *******************************{ - "version": "5.2.0", + "version": "5.3.01", "equipment": { "controller": { "intellicom": { @@ -326,7 +326,7 @@ Configuration file name: config.json 15:09:47.974 SILLY New SOCKET.IO Client connected, rN_W8cd9yhLJ_2KjAAAA 15:09:47.975 SILLY Outputting socket all 15:09:47.975 SILLY Socket.IO checking if we need to output updateAvail: false (will send if false) -15:09:47.979 SILLY Socket.IO NOT outputting updateAvail because it is missing the result string: {"local":{"version":"5.2.0"}} +15:09:47.979 SILLY Socket.IO NOT outputting updateAvail because it is missing the result string: {"local":{"version":"5.3.01"}} 15:09:47.987 SILLY New https server connection ::ffff:127.0.0.1 15:09:47.988 SILLY New https server connection ::ffff:127.0.0.1 15:09:48.000 VERBOSE Sending NR POST @@ -343,12 +343,12 @@ Configuration file name: config.json 15:09:48.134 SILLY updateAvail.compareLocaltoSavedLocal: (current) published release (5.1.1) to cached/last published config.json version (5.1.1) 15:09:48.134 SILLY updateAvail: no change in current remote version compared to local cached config.json version of app 15:09:48.136 SILLY updateAvail: versions discovered: - {"local":{"version":"5.2.0"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"}} -15:09:48.137 SILLY updateAvail: local ver: 5.2.0 latest published release ver: 5.1.1 -15:09:48.139 INFO You are running a newer release (5.2.0) than the published release (5.1.1) + {"local":{"version":"5.3.01"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"}} +15:09:48.137 SILLY updateAvail: local ver: 5.3.01 latest published release ver: 5.1.1 +15:09:48.139 INFO You are running a newer release (5.3.01) than the published release (5.1.1) 15:09:48.140 SILLY Outputting socket updateAvailable 15:09:48.141 SILLY Socket.IO checking if we need to output updateAvail: false (will send if false) -15:09:48.141 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.2.0"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.2.0) than the published release (5.1.1)"} +15:09:48.141 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.3.01"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.3.01) than the published release (5.1.1)"} 15:09:48.142 SILLY updateAvail: finished successfully 15:09:48.163 VERBOSE ISY: Response from ISY: [object Object] @@ -1860,7 +1860,7 @@ Full queue: [[255,0,255,165,1,16,33,232,1,0,1,192], [255,0,255,165,1,16,33,219,1,1,1,180]] 15:09:49.849 SILLY Outputting socket all 15:09:49.849 SILLY Socket.IO checking if we need to output updateAvail: false (will send if false) -15:09:49.850 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.2.0"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.2.0) than the published release (5.1.1)"} +15:09:49.850 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.3.01"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.3.01) than the published release (5.1.1)"} 15:09:49.851 DEBUG postWritePacketHelper: First time writing packet. {"counter":1,"packetWrittenAt":5,"msgWrote":[255,0,255,165,1,16,33,232,1,0,1,192]} 15:09:49.851 DEBUG writePacketHelper: Setting timeout to write next packet (will call preWritePacketHelper()) @@ -25698,7 +25698,7 @@ container.packetBuffer.length()(0) === 0 && bufferToProcess.length(0) > 0: false 15:10:49.881 SILLY New http server connection ::ffff:192.168.1.12 15:10:49.885 SILLY Outputting socket all 15:10:49.885 SILLY Socket.IO checking if we need to output updateAvail: false (will send if false) -15:10:49.897 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.2.0"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.2.0) than the published release (5.1.1)"} +15:10:49.897 SILLY Socket.IO outputting updateAvail: {"local":{"version":"5.3.01"},"remote":{"tag_name":"v5.1.1","version":"5.1.1"},"result":"newer","resultStr":"You are running a newer release (5.3.01) than the published release (5.1.1)"} 15:10:49.902 DEBUG NR Socket: Will not send circuit2status because the value has not changed (1) 15:10:49.902 DEBUG NR Socket: Will not send circuit3status because the value has not changed (0) 15:10:49.902 DEBUG NR Socket: Will not send circuit4status because the value has not changed (0) diff --git a/specs/index.spec.js b/specs/index.spec.js index 708a5e22..98faf2c1 100755 --- a/specs/index.spec.js +++ b/specs/index.spec.js @@ -10,12 +10,23 @@ describe('nodejs-poolController', function () { describe('Loads/checks for a valid configuration file', function () { before(function () { - + // initialize winston once with defaults + return Promise.resolve() + .then(function(){ + bottle.container.logger.init() + }) + .delay(50) + .then(function(){ + console.log("done") + bottle.container.logger.info("test logger") + bottle.container.logger.warn("test warn") + bottle.container.logger.error("test error") + }) }) beforeEach(function () { - + updateAvailStub = sinon.stub(bottle.container.updateAvailable, 'getResultsAsync').returns(Promise.resolve({})) if (global.logInitAndStop) { loggerInfoStub = sinon.spy(bottle.container.logger, 'info') diff --git a/specs/lib/comms/controllers/chlorinator-controller.spec.js b/specs/lib/comms/controllers/chlorinator-controller.spec.js index 96784d37..32e9b01e 100755 --- a/specs/lib/comms/controllers/chlorinator-controller.spec.js +++ b/specs/lib/comms/controllers/chlorinator-controller.spec.js @@ -1,9 +1,9 @@ -describe('chlorinator controller', function () { +describe('chlorinator controller - Virtual', function () { describe('#startChlorinatorController starts the timer for 1 or 2 chlorinators', function () { before(function () { - return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor.json'}) + return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor_virtual.json'}) }); beforeEach(function () { diff --git a/specs/lib/comms/inbound/packetBufferToDecode-chlorinator.spec.js b/specs/lib/comms/inbound/packetBufferToDecode-chlorinator.spec.js index d4b4e4a4..48d65171 100644 --- a/specs/lib/comms/inbound/packetBufferToDecode-chlorinator.spec.js +++ b/specs/lib/comms/inbound/packetBufferToDecode-chlorinator.spec.js @@ -10,7 +10,7 @@ describe('chlorinator packets: receives packets from buffer and follows them to }); beforeEach(function () { - return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor.json'}) + return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor_virtual.json'}) .then(function () { sinon = sinon.sinon.create() loggers = setupLoggerStubOrSpy('stub', 'spy') diff --git a/specs/lib/comms/server-chlorinator-api.spec.js b/specs/lib/comms/server-chlorinator-api.spec.js index ad47a316..20bc0394 100644 --- a/specs/lib/comms/server-chlorinator-api.spec.js +++ b/specs/lib/comms/server-chlorinator-api.spec.js @@ -32,45 +32,32 @@ describe('#set functions', function() { }) - it('should send a message if chlorinator is not installed', function (done) { - global.requestPoolDataWithURLAsync('chlorinator/0') + it('should send a message if chlorinator is not installed', function () { + return global.requestPoolDataWithURLAsync('chlorinator/0') .then(function (result) { result.text.should.contain('FAIL') queuePacketStub.callCount.should.eq(0) }) - .then(done) - .catch(function (err) { - bottle.container.logger.error('Error with chlor not installed.', err) - console.error(err) - }) + }) }) }) describe('#sends chlorinator commands', function() { - context('with a REST API', function() { + context('with the VIRTUAL chlorinator with a REST API', function() { before(function() { - - // return Promise.resolve() - // .then(function(){ - // bottle.container.settings.set('virtual.chlorinatorController', 1) - // bottle.container.settings.set('chlorinator.installed', 1) - // }) - // .then(global.initAllAsync) - return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor.json'}) + return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellichlor_virtual.json'}) }); beforeEach(function() { - loggers = setupLoggerStubOrSpy('stub','spy') + loggers = setupLoggerStubOrSpy('stub','stub') queuePacketStub = sinon.stub(bottle.container.queuePacket, 'queuePacket') settingsStub = sinon.stub(bottle.container.settings, 'updateChlorinatorDesiredOutputAsync') - //getVersionNotificationStub = sinon.stub(bottle.container.settings, 'get').withArgs('notifications.version.remote').returns({'dismissUntilNextRemoteVersionBump': true}) - }) afterEach(function() { - bottle.container.chlorinator.setChlorinatorLevelAsync(0) + bottle.container.chlorinator.setChlorinatorLevelAsync(0,0,0) .then(function(){ bottle.container.chlorinatorController.clearTimer() // Clear out the write queues @@ -100,62 +87,212 @@ describe('#set functions', function() { queuePacketStub.callCount.should.eq(1) queuePacketStub.args[0][0].should.deep.equal([16, 2, 80, 17, 50]) }) - .catch(function(err){ - bottle.container.logger.error(err.toString()) - }) }) - it('starts chlorinator at 100%', function(done) { + it('starts chlorinator at 100%', function() { - global.requestPoolDataWithURLAsync('chlorinator/100') + return global.requestPoolDataWithURLAsync('chlorinator/100') .then(function(result) { result.status.should.eq('on') result.value.should.eq(100) queuePacketStub.callCount.should.eq(1) queuePacketStub.args[0][0].should.deep.equal([16, 2, 80, 17, 100]) }) - .then(done,done) + }) - it('starts chlorinator at 101% (super chlorinate)', function(done) { + it('starts chlorinator at 101% (super chlorinate)', function() { - global.requestPoolDataWithURLAsync('chlorinator/101') + return global.requestPoolDataWithURLAsync('chlorinator/101') .then(function(result) { result.status.should.eq('on') result.value.should.eq(101) queuePacketStub.callCount.should.eq(1) queuePacketStub.args[0][0].should.deep.equal([16, 2, 80, 17, 101]) }) - .then(done,done) + }) - it('starts chlorinator at -1% (should fail)', function(done) { + it('starts chlorinator at -1% (should fail)', function() { - global.requestPoolDataWithURLAsync('chlorinator/-1') + return global.requestPoolDataWithURLAsync('chlorinator/-1') .then(function(result) { result.text.should.contain('FAIL') queuePacketStub.callCount.should.eq(0) }) - .then(done,done) + }) - it('starts chlorinator at 150% (should fail)', function(done) { + it('starts chlorinator at 150% (should fail)', function() { - global.requestPoolDataWithURLAsync('chlorinator/150') + return global.requestPoolDataWithURLAsync('chlorinator/150') .then(function(result){ result.text.should.contain('FAIL') queuePacketStub.callCount.should.eq(0) }) - .then(done,done) + }) - it('starts chlorinator at 0%', function(done) { + it('starts chlorinator at 0%', function() { //do this one last so - global.requestPoolDataWithURLAsync('chlorinator/0') + return global.requestPoolDataWithURLAsync('chlorinator/0') .then(function(result) { result.status.should.eq('off') result.value.should.eq(0) queuePacketStub.callCount.should.eq(1) queuePacketStub.args[0][0].should.deep.equal([16, 2, 80, 17, 0]) }) - .then(done,done) + + }) + }); + }); + + + describe('#sends chlorinator commands', function() { + + context('with a Intellitouch chlorinator with a REST API', function() { + + before(function() { + + return global.initAllAsync({'configLocation': '/specs/assets/config/templates/config_intellitouch_intellichlor.json'}) + }); + + beforeEach(function() { + loggers = setupLoggerStubOrSpy('stub','stub') + queuePacketStub = sinon.stub(bottle.container.queuePacket, 'queuePacket') + settingsStub = sinon.stub(bottle.container.settings, 'updateChlorinatorDesiredOutputAsync') + preambleStub = sinon.stub(bottle.container.intellitouch, 'getPreambleByte').returns(99) + }) + + afterEach(function() { + bottle.container.chlorinator.setChlorinatorLevelAsync(0,0,0) + .then(function(){ + bottle.container.chlorinatorController.clearTimer() + // Clear out the write queues + bottle.container.queuePacket.init() + bottle.container.writePacket.init() + sinon.restore() + }) + + }) + + after(function() { + return Promise.resolve() + .then(function(){ + bottle.container.settings.set('virtual.chlorinatorController', 0) + bottle.container.settings.set('chlorinator.installed', 0) + }) + .then(global.stopAllAsync) + }) + + it('starts chlorinator at 50%', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/50') + .delay(50) + .then(function(result) { + result.status.should.eq('on') + result.value.should.eq(50) + queuePacketStub.callCount.should.eq(1) + // NOTE: this spa setting should be 22 (11%) because that is what is saved in the config file + // all others should then be 0 because we reset the value after each test + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,22,50,0,0,0,0,0,0,0,0]) + }) + + }) + it('starts chlorinator at 100%', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/100') + .delay(50) + .then(function(result) { + result.status.should.eq('on') + result.value.should.eq(100) + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,0,100,0,0,0,0,0,0,0,0]) + }) + + }) + it('starts chlorinator at 101% (super chlorinate)', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/101') + .then(function(result) { + result.status.should.eq('on') + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,0,0,129,0,0,0,0,0,0,0]) + }) + + }) + it('starts chlorinator at -2% (should fail)', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/-2') + .then(function(result) { + result.text.should.contain('FAIL') + queuePacketStub.callCount.should.eq(0) + }) + + }) + it('starts chlorinator at 150% (should fail)', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/150') + .then(function(result){ + result.text.should.contain('FAIL') + queuePacketStub.callCount.should.eq(0) + }) + + }) + + it('tests /pool API at 75%', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/pool/75') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,0,75,0,0,0,0,0,0,0,0]) + }) + }) + + + it('tests /spa API at 75%', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/spa/75') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,150,0,0,0,0,0,0,0,0,0]) + }) + }) + + it('tests /pool/x/spa/y API at 85%/25%', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/pool/85/spa/25') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,50,85,0,0,0,0,0,0,0,0]) + }) + }) + + + it('tests /superChlorinateHours/x API at 2', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/superChlorinateHours/2') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,0,0,130,0,0,0,0,0,0,0]) + }) + }) + + it('tests /pool/x/spa/y/superChlorinateHours/z API at 2', function() { + + return global.requestPoolDataWithURLAsync('chlorinator/pool/1/spa/2/superChlorinateHours/3') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,4,1,131,0,0,0,0,0,0,0]) + }) + }) + + it('sets chlorinator at 0%', function() { + return global.requestPoolDataWithURLAsync('chlorinator/0') + .then(function(result) { + queuePacketStub.callCount.should.eq(1) + queuePacketStub.args[0][0].should.deep.equal([165,99,16,33,153,10,0,0,0,0,0,0,0,0,0,0]) + }) + }) }); }); + + }) diff --git a/specs/lib/equipment/intellibright.spec.js b/specs/lib/equipment/intellibrite.spec.js similarity index 98% rename from specs/lib/equipment/intellibright.spec.js rename to specs/lib/equipment/intellibrite.spec.js index 4c395d58..ca73967b 100644 --- a/specs/lib/equipment/intellibright.spec.js +++ b/specs/lib/equipment/intellibrite.spec.js @@ -419,7 +419,8 @@ describe('processes Intellibrite packets',function () { .delay(500) .then(function (obj) { queue = bottle.container.queuePacket.entireQueue() - queue[2].should.deep.equal(intellibriteCyan) + //console.log('queue: ', queue) + queue[1].should.deep.equal(intellibriteCyan) }) .then(done,done) }); @@ -476,7 +477,7 @@ describe('processes Intellibrite packets',function () { beforeEach(function () { // sinon = sinon.sinon.create() - loggers = setupLoggerStubOrSpy('stub','spy') + loggers = setupLoggerStubOrSpy('spy','spy') checkIfNeedControllerConfigurationStub = sinon.stub(bottle.container.intellitouch,'checkIfNeedControllerConfiguration') writeSPPacketStub = sinon.stub(bottle.container.sp,'writeSP') @@ -539,7 +540,8 @@ describe('processes Intellibrite packets',function () { .delay(100) .then(function () { queue = bottle.container.queuePacket.entireQueue() - queue[2].should.deep.equal(intellibriteCyan) + //console.log('Queue: ', JSON.stringify(queue,null,2)) + queue[1].should.deep.equal(intellibriteCyan) }) @@ -563,7 +565,7 @@ describe('processes Intellibrite packets',function () { .then(function () { queue = bottle.container.queuePacket.entireQueue() queue[0].should.deep.equal(intellibritePosition1) - queue[2].should.deep.equal(intellibriteCyan) + queue[1].should.deep.equal(intellibriteCyan) }) @@ -583,7 +585,7 @@ describe('processes Intellibrite packets',function () { return Promise.resolve() .delay(100) .then(function () { - // console.log('writeSP: ',writeSPPacketStub.args) + //console.log('writeSP: ',writeSPPacketStub.args) writeSPPacketStub.args[0][0].should.deep.equal(intellibriteSwimDelay10) }) diff --git a/specs/lib/helpers/bootrstrap-config-editor.spec.js b/specs/lib/helpers/bootrstrap-config-editor.spec.js index 68233ba6..2a501576 100644 --- a/specs/lib/helpers/bootrstrap-config-editor.spec.js +++ b/specs/lib/helpers/bootrstrap-config-editor.spec.js @@ -15,7 +15,7 @@ describe('updates/resets bootstrap configClient.json', function() { beforeEach(function () { // sinon = sinon.sinon.create() - loggers = setupLoggerStubOrSpy('spy', 'spy') + loggers = setupLoggerStubOrSpy('stub', 'spy') updateAvailStub = sinon.stub(bottle.container.updateAvailable, 'getResultsAsync').returns(Promise.resolve({})) var origFile = '/specs/assets/bootstrapConfig/configClient.json' @@ -65,7 +65,7 @@ describe('updates/resets bootstrap configClient.json', function() { return fs.readFileAsync(path.join(process.cwd(), '/specs/assets/bootstrapConfig/_configClient.json'), 'utf-8') .then(function(data){ data = JSON.parse(data) - console.log(data) + //console.log(data) for (var key in data.panelState) { if (data.panelState[key].state !== "visible"){ myReject(new Error('resetConfigClient did not reset all value.')) diff --git a/src/etc/settings.js b/src/etc/settings.js index 257b1146..c7660ada 100755 --- a/src/etc/settings.js +++ b/src/etc/settings.js @@ -24,7 +24,8 @@ module.exports = function (container) { pfs = Promise.promisifyAll(container.fs), diff = container.deepdiff.diff, observableDiff = container.deepdiff.observableDiff, - applyChange = container.deepdiff.applyChange; + applyChange = container.deepdiff.applyChange, + ready = false var argv = require('yargs-parser')(process.argv.slice(2), opts = {boolean: ['capturePackets', 'suppressWrite']}) @@ -605,6 +606,7 @@ module.exports = function (container) { }) .finally(function () { container.logger.debug('Finished settings.loadAsync()') + ready = true; }) } @@ -870,12 +872,16 @@ module.exports = function (container) { }) } + var isReady = function(){ + return ready; + } /* istanbul ignore next */ if (container.logModuleLoading) console.log('Loaded: settings.js') return { + isReady: isReady, loadAsync: loadAsync, has: has, get: get, diff --git a/src/lib/comms/outbound/queue-packet.js b/src/lib/comms/outbound/queue-packet.js index 9f88c959..5c1af845 100755 --- a/src/lib/comms/outbound/queue-packet.js +++ b/src/lib/comms/outbound/queue-packet.js @@ -62,8 +62,8 @@ module.exports = function (container) { packet = [255, 0, 255]; Array.prototype.push.apply(packet, message); - //if we request to "SET" a variable on the HEAT STATUS & TIME, or Intellibrite - if ((packet[7] === 136 || packet[7] === 133 || packet[7] === 167 || packet[7]===153) && container.settings.get('intellitouch.installed')) { + //if we request to "SET" a variable on outgoing packets. 96 is Intellibrite and there seems to be no "Get" (#106) + if (((packet[7] >= 131 && packet[7] === 168) || packet[7] === 96) && container.settings.get('intellitouch.installed')) { requestGet = 1; } } @@ -131,14 +131,16 @@ module.exports = function (container) { //-------End Internally validate checksum if (requestGet) { - //request the GET version of the SET packet - var getPacket = [165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), packet[container.constants.packetFields.ACTION + 3] + 64, 1, 0] - if (logPacketWrites) container.logger.debug('Queueing message %s to retrieve \'%s\'', getPacket, container.constants.strControllerActions[getPacket[container.constants.packetFields.ACTION]]) - queuePacket(getPacket); - - //var statusPacket = [165, preambleByte, 16, 34, 194, 1, 0] - //container.logger.debug('Queueing messages to retrieve \'%s\'', container.constants.strControllerActions[statusPacket[container.constants.packetFields.ACTION]]) - //queuePacket(statusPacket); + if (packet[7] === 96) { + bottle.container.packetBuffer.push(Buffer.from(packet)) + } + else { + //request the GET version of the SET packet + var getPacket = [165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), packet[container.constants.packetFields.ACTION + 3] + 64, 1, 0] + if (logPacketWrites) container.logger.debug('Queueing message %s to retrieve \'%s\'', getPacket, container.constants.strControllerActions[getPacket[container.constants.packetFields.ACTION]]) + queuePacket(getPacket); + } + } if (logPacketWrites) container.logger.silly('queuePacket: Message: %s now has checksum added: %s', message, packet) diff --git a/src/lib/comms/server.js b/src/lib/comms/server.js index fa1517ba..e83bfe5c 100755 --- a/src/lib/comms/server.js +++ b/src/lib/comms/server.js @@ -22,21 +22,22 @@ module.exports = function (container) { if (container.logModuleLoading) container.logger.info('Loading: server.js'); - var express = container.express, servers = {http: {}, https: {}, ssdp: {}, mdns: {}} + var express = container.express, servers = { http: {}, https: {}, ssdp: {}, mdns: {} } var path = require('path').posix; - var defaultPort = {http: 3000, https: 3001} + var defaultPort = { http: 3000, https: 3001 } var mdnsEmitter = new container.events.EventEmitter(); - var mdns = {query: [], answers: []} + var mdns = { query: [], answers: [] } var emitter = new container.events.EventEmitter(); + var httpShutdown = require('http-shutdown') function startServerAsync(type) { return new Promise(function (resolve, reject) { if (container.settings.get(type + 'Enabled')) { - // srvDesired += container.settings.get(type + 'Enabled'); servers[type].app = express(); + servers[type].port = container.settings.get(type + 'ExpressPort') || defaultPort[type]; servers[type].server = undefined; @@ -79,25 +80,19 @@ module.exports = function (container) { else configExpressServer(servers[type].app, express); + // And Start Listening - servers[type].server.listen(servers[type].port, function () { + servers[type].server = httpShutdown(servers[type].server.listen(servers[type].port, function () { container.logger.verbose('Express Server ' + type + ' listening at port %d', servers[type].port); container.io.init(servers[type].server, type) resolve('Server ' + type + ' started.'); - }); + })); servers[type].server.on('error', function (e) { container.logger.error('error from ' + type + ':', e) console.error(e) reject(e) }); - - // based on https://stackoverflow.com/questions/43003870/how-do-i-shut-down-my-express-server-gracefully-when-its-process-is-killed - servers[type].connections = [] - servers[type].server.on('connection', function (connection) { - container.logger.silly('New %s server connection', type, connection.remoteAddress) - servers[type].connections.push(connection) - }) } else { var res = 'Not starting ' + type + ' server.' @@ -179,11 +174,11 @@ module.exports = function (container) { servers['mdns'].isRunning = 1 servers['mdns'].query({ - questions: [{ - name: 'myserver.local', - type: 'A' - }] - }, + questions: [{ + name: 'myserver.local', + type: 'A' + }] + }, resolve('Server MDNS started.')) @@ -244,17 +239,14 @@ module.exports = function (container) { // advertise shutting down and stop listening } else if (servers[type].server !== undefined) { - container.logger.silly('%s has %s connections in it: ', type, servers[type].connections.length) container.io.stop(type) servers[type].server.close(function () { container.logger.verbose('Express Server ' + type + ' closed'); resolve(); }); - servers[type].connections.forEach(function (conn) { - container.logger.silly('Destroying %s connection from %s', type, conn.remoteAddress) - conn.end() - conn.destroy() - }) + + // graceful shutdown thanks to http-shutdown + servers[type].server.shutdown() } else { container.logger.debug('Trying to close ' + type + ', but it is not running.'); resolve(); //it's ok if it isn't running, so resolve the promise. @@ -289,9 +281,9 @@ module.exports = function (container) { var customRoutes = require(path.join(process.cwd(), 'src/integrations/customExpressRoutes')); customRoutes.init(app); - // Middleware to capture requests to log + // Middleware app.use(function (req, res, next) { - + // Middleware to capture requests to log var reqType = req.originalUrl.split('/') if (!['bootstrap', 'assets', 'poolController', 'public'].includes(reqType[1])) { @@ -305,15 +297,35 @@ module.exports = function (container) { }) } } + + /* console.log('looking at session: ', req.sessionID) + //store session in memory store + if (req.session.views) { + req.session.views++ + res.setHeader('Content-Type', 'text/html') + res.write('

views: ' + req.session.views + '

') + res.write('

expires in: ' + (req.session.cookie.maxAge / 1000) + 's

') + res.write('

json session:
' + JSON.stringify(req.session) ) + res.end() + } else { + req.session.views = 1 + res.end('welcome to the session demo. refresh!') + } + + //output request variables + console.log(`Request session: ${req}`) + */ next() }) // Routing - app.use(express.static(path.join(process.cwd(), 'src/www'))); - app.use('/bootstrap', express.static(path.join(process.cwd(), '/node_modules/bootstrap/dist/'))); - app.use('/jquery', express.static(path.join(process.cwd(), '/node_modules/jquery/'))); - app.use('/jquery-ui', express.static(path.join(process.cwd(), '/node_modules/jquery-ui-dist/'))); - app.use('/jquery-clockpicker', express.static(path.join(process.cwd(), '/node_modules/jquery-clockpicker/dist/'))); + app.use(express.static(path.join(process.cwd(), 'src/www'), { maxAge: '14d' })); + app.use('/bootstrap', express.static(path.join(process.cwd(), '/node_modules/bootstrap/dist/'), { maxAge: '60d' })); + app.use('/jquery', express.static(path.join(process.cwd(), '/node_modules/jquery/'), { maxAge: '60d' })); + app.use('/jquery-ui', express.static(path.join(process.cwd(), '/node_modules/jquery-ui-dist/'), { maxAge: '60d' })); + app.use('/jquery-clockpicker', express.static(path.join(process.cwd(), '/node_modules/jquery-clockpicker/dist/'), { maxAge: '60d' })); + app.use('/socket.io-client', express.static(path.join(process.cwd(), '/node_modules/socket.io-client/dist/'), { maxAge: '60d' })); + // disable for security app.disable('x-powered-by') @@ -322,6 +334,8 @@ module.exports = function (container) { res.send(container.status.getCurrentStatus()) })*/ + + app.get('/all', function (req, res) { res.send(container.helpers.allEquipmentInOneJSON()); container.io.emitToClients('all'); @@ -471,7 +485,7 @@ module.exports = function (container) { }) -//TODO: do we need DOW in these??? + //TODO: do we need DOW in these??? app.get('/datetime/set/time/:hh/:mm/date/:dow/:dd/:mon/:yy/:dst', function (req, res) { var hour = parseInt(req.params.hh) var min = parseInt(req.params.mm) @@ -537,6 +551,7 @@ module.exports = function (container) { res.send(container.intellichem.getCurrentIntellichem()) }) + // This should be deprecated app.get('/chlorinator/:chlorinateLevel', function (req, res) { container.chlorinator.setChlorinatorLevelAsync(parseInt(req.params.chlorinateLevel)) .then(function (response) { @@ -544,6 +559,43 @@ module.exports = function (container) { }) }) + app.get('/chlorinator/pool/:poolChlorinateLevel', function (req, res) { + container.chlorinator.setChlorinatorLevelAsync(parseInt(req.params.poolChlorinateLevel)) + .then(function (response) { + res.send(response) + }) + }) + + app.get('/chlorinator/spa/:spaChlorinateLevel', function (req, res) { + container.chlorinator.setChlorinatorLevelAsync(-1, parseInt(req.params.spaChlorinateLevel)) + .then(function (response) { + res.send(response) + }) + }) + + app.get('/chlorinator/pool/:poolChlorinateLevel/spa/:spaChlorinateLevel', function (req, res) { + container.chlorinator.setChlorinatorLevelAsync(parseInt(req.params.poolChlorinateLevel), parseInt(req.params.spaChlorinateLevel)) + .then(function (response) { + res.send(response) + }) + }) + + + app.get('/chlorinator/superChlorinateHours/:hours', function (req, res) { + container.chlorinator.setChlorinatorLevelAsync(-1, -1, parseInt(req.params.hours)) + .then(function (response) { + res.send(response) + }) + }) + + app.get('/chlorinator/pool/:poolChlorinateLevel/spa/:spaChlorinateLevel/superChlorinateHours/:hours', function (req, res) { + container.chlorinator.setChlorinatorLevelAsync(parseInt(req.params.poolChlorinateLevel), parseInt(req.params.spaChlorinateLevel), parseInt(req.params.hours)) + .then(function (response) { + res.send(response) + }) + }) + + app.get('/light/mode/:mode', function (req, res) { if (parseInt(req.params.mode) >= 0 && parseInt(req.params.mode) <= 256) { res.send(container.circuit.setLightMode(parseInt(req.params.mode))) @@ -862,7 +914,7 @@ module.exports = function (container) { res.send(response) }) -//#11 Run pump at GPM for an indefinite duration + //#11 Run pump at GPM for an indefinite duration app.get('/pumpCommand/run/pump/:pump/gpm/:gpm', function (req, res) { var pump = parseInt(req.params.pump) var gpm = parseInt(req.params.gpm) @@ -876,7 +928,7 @@ module.exports = function (container) { res.send(response) }) -//#12 Run pump at GPM for specified duration + //#12 Run pump at GPM for specified duration app.get('/pumpCommand/run/pump/:pump/gpm/:gpm/duration/:duration', function (req, res) { var pump = parseInt(req.params.pump) var gpm = parseInt(req.params.gpm) @@ -890,7 +942,7 @@ module.exports = function (container) { res.send(response) }) -//#13 Save program to pump + //#13 Save program to pump app.get('/pumpCommand/save/pump/:pump/program/:program/gpm/:speed', function (req, res) { var pump = parseInt(req.params.pump) var program = parseInt(req.params.program) @@ -911,7 +963,7 @@ module.exports = function (container) { } }) -//#14 Save and run program for indefinite duration + //#14 Save and run program for indefinite duration app.get('/pumpCommand/saverun/pump/:pump/program/:program/gpm/:speed', function (req, res) { var pump = parseInt(req.params.pump) var program = parseInt(req.params.program) @@ -930,7 +982,7 @@ module.exports = function (container) { } }) -//#15 Save and run program for specified duration + //#15 Save and run program for specified duration app.get('/pumpCommand/saverun/pump/:pump/program/:program/gpm/:speed/duration/:duration', function (req, res) { var pump = parseInt(req.params.pump) diff --git a/src/lib/comms/socketio-helper.js b/src/lib/comms/socketio-helper.js index 70278cb9..40bcadcb 100755 --- a/src/lib/comms/socketio-helper.js +++ b/src/lib/comms/socketio-helper.js @@ -197,7 +197,7 @@ module.exports = function (container) { } else { try { - container.logger.debug('Stopping Socket IO Server') + container.logger.debug(`Stopping Socket IO ${type} Server`) while (socketList[type].length !== 0) { container.logger.silly('total sockets in list: ', socketList[type].length) @@ -212,7 +212,7 @@ module.exports = function (container) { if (typeof io[type].close === 'function') { io[type].close(); io[type + 'Enabled'] = 0 - container.logger.debug('Socket IO Server closed') + container.logger.debug(`Socket IO ${type} Server closed`) } else { container.logger.silly('Trying to close IO server, but already closed.') diff --git a/src/lib/equipment/chlorinator.js b/src/lib/equipment/chlorinator.js index 5518b88d..a006e1cd 100755 --- a/src/lib/equipment/chlorinator.js +++ b/src/lib/equipment/chlorinator.js @@ -225,10 +225,61 @@ module.exports = function (container) { container.chlorinatorController.startChlorinatorController() } + let response = {} + if (currentChlorinatorStatus.controlledBy === 'virtual') { + // check for valid settings to be sent to Chlorinator directly + if (chlorPoolLvl >= 0 && chlorPoolLvl <= 101) { + currentChlorinatorStatus.outputPoolPercent = chlorPoolLvl + } + else { + + response.text = 'FAIL: Request for invalid value for chlorinator (' + chlorPoolLvl + '). Chlorinator will continue to run at previous level (' + currentChlorinatorStatus.outputPoolPercent + ')' + response.status = this.status + response.value = currentChlorinatorStatus.outputPoolPercent + if (container.settings.get('logChlorinator')) { + container.logger.warn(response) + } + return Promise.resolve(response) + } + } + else { + // check for valid values with Intellicom/Intellitouch + if (chlorPoolLvl === 101) { + // assume we will set the superchlorinate for 1 hour + chlorSuperChlorinateHours = 1 + } + else if (chlorPoolLvl >= 0 && chlorPoolLvl <= 100) { + currentChlorinatorStatus.outputPoolPercent = chlorPoolLvl + } + else { + if (!chlorPoolLvl || chlorPoolLvl < -1 || chlorPoolLvl > 101) { + // -1 is valid if we don't want to change the setting. Anything else is invalid and should trigger a fail. + currentChlorinatorStatus.outputPoolPercent = 0; + response.text = 'FAIL: Request for invalid value for chlorinator (' + chlorPoolLvl + '). Chlorinator will continue to run at previous level (' + currentChlorinatorStatus.outputPoolPercent + ')' + response.status = this.status + response.value = currentChlorinatorStatus.outputPoolPercent + response.chlorinator = currentChlorinatorStatus + if (container.settings.get('logChlorinator')) { + container.logger.warn(response) + } + return Promise.resolve(response) + } + + } + } + + if (chlorSpaLvl >= 0 & chlorSpaLvl <= 100) { currentChlorinatorStatus.outputSpaPercent = chlorSpaLvl } - if (chlorSuperChlorinateHours > 0 & chlorSuperChlorinateHours <= 96) { + else { + if (!currentChlorinatorStatus.outputSpaPercent || currentChlorinatorStatus.outputSpaPercent < 0) { + // just in case it isn't set. otherwise we don't want to touch it + currentChlorinatorStatus.outputSpaPercent = 0; + } + } + + if ((chlorSuperChlorinateHours > 0 & chlorSuperChlorinateHours <= 96) || currentChlorinatorStatus.superChlorinateHours>0) { currentChlorinatorStatus.superChlorinate = 1 currentChlorinatorStatus.superChlorinateHours = chlorSuperChlorinateHours } @@ -236,112 +287,94 @@ module.exports = function (container) { currentChlorinatorStatus.superChlorinate = 0 currentChlorinatorStatus.superChlorinateHours = 0 } - currentChlorinatorStatus.outputPoolPercent = chlorPoolLvl - - let response = {} - return Promise.resolve() - .then(function () { - if (container.settings.get('chlorinator.installed')) { - if (currentChlorinatorStatus.controlledBy === 'virtual') { - // chlorinator only has pool setting; send commands directly to chlorinator - if (chlorPoolLvl >= 0 && chlorPoolLvl <= 101) { - return Promise.resolve() - .then(function () { - - if (currentChlorinatorStatus.outputPoolPercent === 0) { - response.text = 'Chlorinator set to off. Chlorinator will be queried every 30 mins for PPM' - response.status = 'off' - response.value = 0 - } else if (currentChlorinatorStatus.outputPoolPercent >= 1 && currentChlorinatorStatus.outputPoolPercent <= 100) { - response.text = 'Chlorinator set to ' + currentChlorinatorStatus.outputPoolPercent + '%.' - response.status = 'on' - response.value = currentChlorinatorStatus.outputPoolPercent - } else if (currentChlorinatorStatus.outputPoolPercent === 101) { - response.text = 'Chlorinator set to super chlorinate' - response.status = 'on' - response.value = currentChlorinatorStatus.outputPoolPercent - } - }) - .then(container.settings.updateChlorinatorDesiredOutputAsync(chlorPoolLvl, currentChlorinatorStatus.outputSpaPercent)) - .then(function () { - container.io.emitToClients('chlorinator') - if (container.chlorinatorController.isChlorinatorTimerRunning()) - container.chlorinatorController.chlorinatorStatusCheck() //This is causing problems if the chlorinator is offline (repeated calls to send status packet.) - else - container.queuePacket.queuePacket([16, 2, 80, 17, chlorPoolLvl]) - if (container.settings.get('logChlorinator')) { - container.logger.info(response) - } - return response - }) - } - else { - response.text = 'FAIL: Request for invalid value for chlorinator (' + chlorPoolLvl + '). Chlorinator will continue to run at previous level (' + currentChlorinatorStatus.outputPoolPercent + ')' - response.status = this.status + if (container.settings.get('chlorinator.installed')) { + if (currentChlorinatorStatus.controlledBy === 'virtual') { + // chlorinator only has one setting; it doesn't know the difference between pool/spa + return Promise.resolve() + .then(function () { + response.chlorinator = currentChlorinatorStatus + if (currentChlorinatorStatus.outputPoolPercent === 0) { + response.text = 'Chlorinator set to off. Chlorinator will be queried every 30 mins for PPM' + response.status = 'off' + response.value = 0 + } else if (currentChlorinatorStatus.outputPoolPercent >= 1 && currentChlorinatorStatus.outputPoolPercent <= 100) { + response.text = 'Chlorinator set to ' + currentChlorinatorStatus.outputPoolPercent + '%.' + response.status = 'on' + response.value = currentChlorinatorStatus.outputPoolPercent + } else if (currentChlorinatorStatus.outputPoolPercent === 101) { + response.text = 'Chlorinator set to super chlorinate' + response.status = 'on' response.value = currentChlorinatorStatus.outputPoolPercent - if (container.settings.get('logChlorinator')) { - container.logger.warn(response) - } - return Promise.resolve(response) } - } - else if (currentChlorinatorStatus.controlledBy === 'intellicom') { - // chlorinator controlled by intellicom; it only has the pool setting + }) + .then(container.settings.updateChlorinatorDesiredOutputAsync(currentChlorinatorStatus.outputPoolPercent, currentChlorinatorStatus.outputSpaPercent)) + .then(function () { + container.io.emitToClients('chlorinator') + if (container.chlorinatorController.isChlorinatorTimerRunning()) + container.chlorinatorController.chlorinatorStatusCheck() //This is causing problems if the chlorinator is offline (repeated calls to send status packet.) + else + container.queuePacket.queuePacket([16, 2, 80, 17, currentChlorinatorStatus.outputPoolPercent]) + if (container.settings.get('logChlorinator')) { + container.logger.info(response) + } + return response + }) + } + + else if (currentChlorinatorStatus.controlledBy === 'intellicom') { + // chlorinator controlled by intellicom; it only has the pool setting - return Promise.resolve() - .then(function () { + return Promise.resolve() + .then(function () { + response.chlorinator = currentChlorinatorStatus + response.text = `Chlorinator set to ${currentChlorinatorStatus.outputPoolPercent} and SuperChlorinate is ${currentChlorinatorStatus.superChlorinate} for ${currentChlorinatorStatus.superChlorinateHours} hours.` + response.status = 'on' + response.value = currentChlorinatorStatus.outputPoolPercent - if (chlorPoolLvl >= 0 && chlorPoolLvl <= 100) { - currentChlorinatorStatus.outputPoolPercent = chlorPoolLvl - response.text = `Chlorinator set to ${currentChlorinatorStatus.outputPoolPercent} and SuperChlorinate is ${currentChlorinatorStatus.superChlorinate} for ${currentChlorinatorStatus.superChlorinateHours} hours.` - response.status = 'on' - response.value = currentChlorinatorStatus.outputPoolPercent - } - }) - .then(container.settings.updateChlorinatorDesiredOutputAsync(currentChlorinatorStatus.outputPoolPercent, currentChlorinatorStatus.outputSpaPercent)) - .then(function () { - container.queuePacket.queuePacket([165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), 153, 10, outputSpaByte(), currentChlorinatorStatus.outputPoolPercent, 0, superChlorinateByte(), 0, 0, 0, 0, 0, 0, 0]) + }) + .then(container.settings.updateChlorinatorDesiredOutputAsync(currentChlorinatorStatus.outputPoolPercent, currentChlorinatorStatus.outputSpaPercent)) + .then(function () { + // TODO: Check if the packet is the same on Intellicom (sans Spa setting)... currently it is the same as Intellichlor but the response is formatted differently. + container.queuePacket.queuePacket([165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), 153, 10, outputSpaByte(), currentChlorinatorStatus.outputPoolPercent, 0, superChlorinateByte(), 0, 0, 0, 0, 0, 0, 0]) - }) + }) + + if (container.settings.get('logChlorinator')) { + container.logger.info(response) + } + } + else { + // controlled by Intellitouch. We should set both pool and spa levels at the controller + + return Promise.resolve() + .then(function () { + response.chlorinator = currentChlorinatorStatus + response.text = `Chlorinator pool set to ${currentChlorinatorStatus.outputPoolPercent}, spa set to ${currentChlorinatorStatus.outputSpaPercent} and SuperChlorinate is ${currentChlorinatorStatus.superChlorinate} for ${currentChlorinatorStatus.superChlorinateHours} hours.` + response.status = 'on' + response.value = currentChlorinatorStatus.outputPoolPercent + + }) + .then(container.settings.updateChlorinatorDesiredOutputAsync(currentChlorinatorStatus.outputPoolPercent, currentChlorinatorStatus.outputSpaPercent)) + .then(function () { + container.queuePacket.queuePacket([165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), 153, 10, outputSpaByte(), currentChlorinatorStatus.outputPoolPercent, superChlorinateByte(), 0, 0, 0, 0, 0, 0, 0]) if (container.settings.get('logChlorinator')) { container.logger.info(response) } - } + return response + }) - else { - // controlled by Intellitouch. We should set both pool and spa levels at the controller - - return Promise.resolve() - .then(function () { - - if (chlorPoolLvl >= 0 && chlorPoolLvl <= 100) { - currentChlorinatorStatus.outputPoolPercent = chlorPoolLvl - response.text = `Chlorinator pool set to ${currentChlorinatorStatus.outputPoolPercent}, spa set to ${currentChlorinatorStatus.outputSpaPercent} and SuperChlorinate is ${currentChlorinatorStatus.superChlorinate} for ${currentChlorinatorStatus.superChlorinateHours} hours.` - response.status = 'on' - response.value = currentChlorinatorStatus.outputPoolPercent - } - }) - .then(container.settings.updateChlorinatorDesiredOutputAsync(currentChlorinatorStatus.outputPoolPercent, currentChlorinatorStatus.outputSpaPercent)) - .then(function () { - container.queuePacket.queuePacket([165, container.intellitouch.getPreambleByte(), 16, container.settings.get('appAddress'), 153, 10, outputSpaByte(), currentChlorinatorStatus.outputPoolPercent, superChlorinateByte(), 0, 0, 0, 0, 0, 0, 0]) - if (container.settings.get('logChlorinator')) { - container.logger.info(response) - } - return Promise.resolve(response) - }) + } + container.io.emitToClients('chlorinator') + } - } - container.io.emitToClients('chlorinator') - } - else { - // chlor NOT installed - response.text = 'FAIL: Chlorinator not enabled. Set Chlorinator=1 in config.json' - return Promise.resolve(response) - } - }) + else { + // chlor NOT installed + response.text = 'FAIL: Chlorinator not enabled. Set Chlorinator=1 in config.json' + return Promise.resolve(response) + } } diff --git a/src/lib/equipment/circuit.js b/src/lib/equipment/circuit.js index a763c191..67b450dd 100755 --- a/src/lib/equipment/circuit.js +++ b/src/lib/equipment/circuit.js @@ -949,6 +949,9 @@ module.exports = function (container) { container.logger.info(retStr) } + // assign color to circuit object + assignControllerLightColor(mode,0,'API') + return retStr } diff --git a/src/lib/logger/winston-3.js b/src/lib/logger/winston-3.js index a27559ed..863ab368 100644 --- a/src/lib/logger/winston-3.js +++ b/src/lib/logger/winston-3.js @@ -174,13 +174,13 @@ module.exports = function (container) { packetLogger.info(msg) } - function error(msg) { - if (logger === undefined) { - console.log('Error ', arguments) - } - else - logger.error.apply(this, arguments) - } + // function error(msg) { + // if (logger === undefined) { + // console.log('Error ', arguments) + // } + // else + // logger.error.apply(this, arguments) + // } function warn(msg) { if (logger === undefined) { @@ -251,7 +251,7 @@ module.exports = function (container) { return { init: init, - error: error, + // error: error, warn: warn, silly: silly, debug: debug, diff --git a/src/lib/logger/winston-helper.js b/src/lib/logger/winston-helper.js index bf4a27cc..56dc3947 100755 --- a/src/lib/logger/winston-helper.js +++ b/src/lib/logger/winston-helper.js @@ -19,7 +19,7 @@ // dateFormat = require('dateformat'), // util = require('util') -module.exports = function(container) { +module.exports = function (container) { /*istanbul ignore next */ if (container.logModuleLoading) console.log('Loading: winston-helper.js') @@ -31,129 +31,145 @@ module.exports = function(container) { var logger, packetLogger - var init = function(){ + var init = function () { - logger = new (winston.Logger)({ - transports: [ - new (winston.transports.Console)({ + if (container.settings.isReady()) { + console.log('Winstion initializing with customized settings.') + + logger = new (winston.Logger)({ + transports: [ + new (winston.transports.Console)({ + timestamp: function () { + return dateFormat(Date.now(), "HH:MM:ss.l"); + }, + formatter: function (options) { + // Return string will be passed to logger. + return options.timestamp() + ' ' + winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + (undefined !== options.message ? options.message : '') + + (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : ''); + }, + colorize: true, + level: container.settings.get('logLevel') || 'info', + stderrLevels: [], + handleExceptions: true, + humanReadableUnhandledException: true + }) + ] + }); + + var fileLogEnable = 0 + if (container.settings.has('fileLog.enable')) { + fileLogEnable = container.settings.get('fileLog.enable') + } + if (fileLogEnable) { + var _level = container.settings.get('fileLog.fileLogLevel') + var file = path.join(process.cwd(), container.settings.get('fileLog.fileName')) + + var options = { timestamp: function () { return dateFormat(Date.now(), "HH:MM:ss.l"); }, + level: _level, formatter: function (options) { // Return string will be passed to logger. - return options.timestamp() + ' ' + winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + (undefined !== options.message ? options.message : '') + + return options.timestamp() + ' ' + options.level.toUpperCase() + ' ' + (undefined !== options.message ? options.message : '') + (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : ''); }, - colorize: true, - level: container.settings.get('logLevel') || 'info', - stderrLevels: [], - handleExceptions: true, - humanReadableUnhandledException: true - }) - ] - }); - var fileLogEnable = 0 - if (container.settings.has('fileLog.enable')){ - fileLogEnable = container.settings.get('fileLog.enable') - } - if (fileLogEnable) { - var _level = container.settings.get('fileLog.fileLogLevel') - var file = path.join(process.cwd(), container.settings.get('fileLog.fileName')) - - var options = { - timestamp: function () { - return dateFormat(Date.now(), "HH:MM:ss.l"); - }, - level: _level, - formatter: function (options) { - // Return string will be passed to logger. - return options.timestamp() + ' ' + options.level.toUpperCase() + ' ' + (undefined !== options.message ? options.message : '') + - (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : ''); - }, - filename: file, - colorize: false, - json: false + filename: file, + colorize: false, + json: false + } + logger.add(winston.transports.File, options) + } - logger.add(winston.transports.File, options) - } + + if (container.settings.get('capturePackets')) { + packetLogger = new (winston.Logger)({ + transports: [ + new (winston.transports.File)({ + formatter: function (options) { + // Return string will be passed to logger. + return JSON.stringify(options.message); + }, + colorize: false, + level: 'info', + handleExceptions: true, + humanReadableUnhandledException: false, + json: true, + filename: path.join(process.cwd(), 'replay/packetCapture.json') + }) + ] + }); + } - if (container.settings.get('capturePackets')) - { - packetLogger = new (winston.Logger)({ + } + else { + console.log('Winstion initializing with default settings.') + // initialize winston with defaults + logger = new (winston.Logger)({ transports: [ - new (winston.transports.File)({ + new (winston.transports.Console)({ + timestamp: function () { + return dateFormat(Date.now(), "HH:MM:ss.l"); + }, formatter: function (options) { // Return string will be passed to logger. - return JSON.stringify(options.message); + return options.timestamp() + ' ' + winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + (undefined !== options.message ? options.message : '') + + (options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : ''); }, - colorize: false, + colorize: true, level: 'info', + stderrLevels: [], handleExceptions: true, - humanReadableUnhandledException: false, - json: true, - filename: path.join(process.cwd(), 'replay/packetCapture.json') + humanReadableUnhandledException: true }) ] }); } - - } - function error(msg, ...args){ - // console.log('error msg: ', msg) - // console.log('error args... ', args) + function error(msg){ + if (logger===undefined){ + init() + } + logger.error.apply(this, arguments) + } + + function warn(msg){ if (logger===undefined){ - console.log('Error ', args) - } - else - // is this right? - logger.error.apply(this, args) - } - - function warn(msg){ - if (logger===undefined){ - console.log('Warn ',arguments) + init() } - else - logger.warn.apply(this, arguments) - } - function silly(msg){ + logger.warn.apply(this, arguments) + } + function silly(msg){ if (logger===undefined){ init() - - logger.silly.apply(this, arguments) } - else - logger.silly.apply(this, arguments) - } - function debug(msg){ + logger.silly.apply(this, arguments) + } + function debug(msg){ if (logger===undefined){ - console.log('Debug ',arguments) + init() } - else - logger.debug.apply(this, arguments) - } - function verbose(msg){ + logger.debug.apply(this, arguments) + } + function verbose(msg){ if (logger===undefined){ - console.log('Verbose ',arguments) + init() } - else - logger.verbose.apply(this, arguments) - } - function info(msg){ + logger.verbose.apply(this, arguments) + } + function info(msg){ if (logger===undefined){ - console.log('Info ',arguments) + init() } - else - logger.info.apply(this, arguments) - } + logger.info.apply(this, arguments) + } - function changeLevel(transport, lvl){ + function changeLevel(transport, lvl) { //when testing, we may call this first - if (logger===undefined) { + if (logger === undefined) { //init() //calling init here may lead to retrieving settings which we don't have yet... so print a message and move on console.log('Error trying to call changeLevel when winston is not yet initialized') @@ -162,12 +178,12 @@ module.exports = function(container) { logger.transports[transport].level = lvl; } } - function add(transport, options){ + function add(transport, options) { logger.add(transport, options) } - function packet(msg){ - packetLogger.info.apply(this, arguments) + function packet(msg) { + packetLogger.info.apply(this, arguments) } /*istanbul ignore next */ @@ -175,13 +191,13 @@ module.exports = function(container) { logger.info('Loaded: winston-helper.js') return { - init:init, + init: init, error: error, warn: warn, silly: silly, debug: debug, verbose: verbose, - info: info, + info: info, changeLevel: changeLevel, add: add, packet: packet diff --git a/src/www/bootstrap/configClient.json b/src/www/bootstrap/configClient.json index 5977afcf..51d35662 100755 --- a/src/www/bootstrap/configClient.json +++ b/src/www/bootstrap/configClient.json @@ -28,7 +28,7 @@ "state": "visible" }, "intellichem": { - "state": "visible" + "state": "hidden" }, "release": { "state": "visible" diff --git a/src/www/bootstrap/index.html b/src/www/bootstrap/index.html index eb069a39..fa4ba945 100755 --- a/src/www/bootstrap/index.html +++ b/src/www/bootstrap/index.html @@ -14,7 +14,7 @@ - + @@ -73,34 +73,35 @@ - + - + - + - + - + diff --git a/src/www/bootstrap/main.js b/src/www/bootstrap/main.js index ee3c080c..c59a3ba8 100755 --- a/src/www/bootstrap/main.js +++ b/src/www/bootstrap/main.js @@ -2182,7 +2182,7 @@ function lastUpdate(reset) { } var loadAppSettings = function () { - $.getJSON('/config', function (appConfig) { + $.getJSON('../config', function (appConfig) { appParams = appConfig.config console.log(appParams) if (appParams.systemReady) { diff --git a/src/www/public/replay.html b/src/www/public/replay.html index fee6ef6d..6046b623 100755 --- a/src/www/public/replay.html +++ b/src/www/public/replay.html @@ -17,7 +17,7 @@ - +
5.2.0 release notes5.3.0 release notes
Node 6+Intellibrite - This app no longer supports Node 4. + #106 fixed. API updates are now reflected in the /circuit object.
Update of modules.Browser caching - Make sure to run `npm i` or `npm upgrade` to get the latest. + Much thanks to @arrmo for implementing browser side caching of web pages. Big speed improvements on loading the HTML pages.
Decoupled serial port and processing of packets.Server side session management - Should help recovery upon packet errors. + Better management of sessions on the server.
Bug FixesNPM I - #95, #99, #100 + Re-run 'npm i' to update dependencies. +