nodejs-poolcontroller 7.7.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +224 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +45 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -15,11 +16,11 @@ You should have received a copy of the GNU Affero General Public License
15
16
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
  */
17
18
  import * as fs from "fs";
19
+ import * as path from "path";
18
20
  import * as express from "express";
19
21
  import * as extend from 'extend';
20
22
  import * as multer from 'multer';
21
- import * as path from "path";
22
- import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater } from "../../../controller/Equipment";
23
+ import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater, Screenlogic } from "../../../controller/Equipment";
23
24
  import { config } from "../../../config/Config";
24
25
  import { logger } from "../../../logger/Logger";
25
26
  import { utils } from "../../../controller/Constants";
@@ -29,6 +30,8 @@ import { stopPacketCaptureAsync, startPacketCapture } from '../../../app';
29
30
  import { conn } from "../../../controller/comms/Comms";
30
31
  import { webApp, BackupFile, RestoreFile } from "../../Server";
31
32
  import { release } from "os";
33
+ import { ScreenLogicComms, sl } from "../../../controller/comms/ScreenLogic";
34
+ import { screenlogic } from "node-screenlogic";
32
35
 
33
36
  export class ConfigRoute {
34
37
  public static initRoutes(app: express.Application) {
@@ -64,20 +67,48 @@ export class ConfigRoute {
64
67
  });
65
68
  app.get('/config/options/rs485', async (req, res, next) => {
66
69
  try {
67
- let opts = { ports: [], local: [] }
70
+ let opts = { ports: [], local: [], screenlogic: {} }
68
71
  let cfg = config.getSection('controller');
69
72
  for (let section in cfg) {
70
73
  if (section.startsWith('comms')) {
71
- let cport = extend(true, { enabled: false, netConnect: false }, cfg[section]);
74
+ let cport = extend(true, { enabled: false, netConnect: false, mock: false }, cfg[section]);
72
75
  let port = conn.findPortById(cport.portId || 0);
76
+ if (typeof cport.type === 'undefined'){
77
+ cport.type = cport.netConnect ? 'netConnect' : cport.mockPort || cport.mock ? 'mock' : 'local'
78
+ }
73
79
  if (typeof port !== 'undefined') cport.stats = port.stats;
80
+ if (port.portId === 0 && port.type === 'screenlogic') {
81
+ cport.screenlogic.stats = sl.stats;
82
+ }
74
83
  opts.ports.push(cport);
75
84
  }
85
+ // if (section.startsWith('screenlogic')){
86
+ // let screenlogic = cfg[section];
87
+ // screenlogic.types = [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }];
88
+ // screenlogic.stats = sl.stats;
89
+ // opts.screenlogic = screenlogic;
90
+ // }
76
91
  }
77
92
  opts.local = await conn.getLocalPortsAsync() || [];
78
93
  return res.status(200).send(opts);
79
94
  } catch (err) { next(err); }
80
95
  });
96
+ // app.get('/config/options/screenlogic', async (req, res, next) => {
97
+ // try {
98
+ // let cfg = config.getSection('controller.screenlogic');
99
+ // let data = {
100
+ // cfg,
101
+ // types: [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }]
102
+ // }
103
+ // return res.status(200).send(data);
104
+ // } catch (err) { next(err); }
105
+ // });
106
+ app.get('/config/options/screenlogic/search', async (req, res, next) => {
107
+ try {
108
+ let localUnits = await ScreenLogicComms.searchAsync();
109
+ return res.status(200).send(localUnits);
110
+ } catch (err) { next(err); }
111
+ });
81
112
  app.get('/config/options/circuits', async (req, res, next) => {
82
113
  try {
83
114
  let opts = {
@@ -269,12 +300,34 @@ export class ConfigRoute {
269
300
  warnings,
270
301
  // waterFlow: sys.board.valueMaps.chemControllerWaterFlow.toArray(), // remove
271
302
  controllers: sys.chemControllers.get(),
272
- maxChemControllers: sys.equipment.maxChemControllers
303
+ maxChemControllers: sys.equipment.maxChemControllers,
304
+ doserTypes: sys.board.valueMaps.chemDoserTypes.toArray()
273
305
  };
274
306
  return res.status(200).send(opts);
275
307
  }
276
308
  catch (err) { next(err); }
277
309
  });
310
+ app.get('/config/options/chemDosers', async (req, res, next) => {
311
+ try {
312
+ let remServers = await sys.ncp.getREMServers();
313
+ let opts = {
314
+ bodies: sys.board.bodies.getBodyAssociations(),
315
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
316
+ status: sys.board.valueMaps.chemDoserStatus.toArray(),
317
+ pumpTypes: sys.board.valueMaps.chemPumpTypes.toArray(),
318
+ volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
319
+ flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
320
+ remServers,
321
+ dosingStatus: sys.board.valueMaps.chemDoserDosingStatus.toArray(),
322
+ dosers: sys.chemDosers.get(),
323
+ doserTypes: sys.board.valueMaps.chemDoserTypes.toArray(),
324
+ maxChemDosers: sys.equipment.maxChemDosers
325
+ };
326
+ return res.status(200).send(opts);
327
+ }
328
+ catch (err) { next(err); }
329
+ });
330
+
278
331
  app.get('/config/options/rem', async (req, res, next) => {
279
332
  try {
280
333
  let opts = {
@@ -294,6 +347,19 @@ export class ConfigRoute {
294
347
  return res.status(200).send(opts);
295
348
  } catch (err) { next(err); }
296
349
  });
350
+ app.get('/config/options/anslq25ControllerType', async (req, res, next) => {
351
+ try {
352
+ let opts = {
353
+ // controllerType: typeof sys.anslq25.controllerType === 'undefined' ? '' : sys.anslq25.controllerType,
354
+ // model: typeof sys.anslq25.model === 'undefined' ? '' : sys.anslq25.model,
355
+ // equipment: sys.equipment.get(),
356
+ ...sys.anslq25.get(true),
357
+ controllerTypes: sys.getAvailableControllerTypes(['easytouch', 'intellitouch', 'intellicenter']),
358
+ rs485ports: await conn.listInstalledPorts()
359
+ }
360
+ return res.status(200).send(opts);
361
+ } catch (err) { next(err); }
362
+ });
297
363
  app.get('/config/options/chlorinators', async (req, res, next) => {
298
364
  try {
299
365
  let opts = {
@@ -324,25 +390,6 @@ export class ConfigRoute {
324
390
  let opts = config.getSection();
325
391
  return res.status(200).send(opts);
326
392
  });
327
- app.get('/app/options/interfaces', (req, res) => {
328
- // todo: move bytevaluemaps out to a proper location; add additional definitions
329
- let opts = {
330
- interfaces: config.getSection('web.interfaces'),
331
- types: [
332
- { name: 'rest', desc: 'Rest' },
333
- { name: 'http', desc: 'Http' },
334
- { name: 'rem', desc: 'Relay Equipment Manager' },
335
- { name: 'mqtt', desc: 'MQTT' },
336
- { name: 'influx', desc: 'InfluxDB' }
337
- ],
338
- protocols: [
339
- { val: 0, name: 'http://', desc: 'http://' },
340
- { val: 1, name: 'https://', desc: 'https://' },
341
- { val: 2, name: 'mqtt://', desc: 'mqtt://' }
342
- ]
343
- }
344
- return res.status(200).send(opts);
345
- });
346
393
  app.get('/config/options/tempSensors', (req, res) => {
347
394
  let opts = {
348
395
  tempUnits: sys.board.valueMaps.tempUnits.toArray(),
@@ -400,6 +447,13 @@ export class ConfigRoute {
400
447
  return res.status(200).send(controller.get(true));
401
448
  } catch (err) { next(err); }
402
449
  });
450
+ app.put('/config/anslq25ControllerType', async (req, res, next) => {
451
+ try {
452
+ // sys.anslq25ControllerType
453
+ await sys.anslq25Board.setAnslq25Async(req.body);
454
+ return res.status(200).send(sys.anslq25.get(true));
455
+ } catch (err) { next(err); }
456
+ });
403
457
  app.delete('/config/filter', async (req, res, next) => {
404
458
  try {
405
459
  let sfilter = await sys.board.filters.deleteFilterAsync(req.body);
@@ -521,6 +575,18 @@ export class ConfigRoute {
521
575
  }
522
576
  catch (err) { next(err); }
523
577
  });
578
+ // RKS: 05-20-22 This is a remnant of the old web ui. It is not called and the setType method needed to go away.
579
+ //app.delete('/config/pump/:pumpId', async (req, res, next) => {
580
+ // try {
581
+ // let pump = sys.pumps.getItemById(parseInt(req.params.pumpId, 10));
582
+ // await sys.board.pumps.deletePumpAsync()
583
+ // if (pump.type === 0) {
584
+ // return res.status(500).send(`Pump ${pump.id} not active`);
585
+ // }
586
+ // pump.setType(0);
587
+ // return res.status(200).send('OK');
588
+ // } catch (err) { next(err); }
589
+ //});
524
590
  app.delete('/config/pump', async (req, res, next) => {
525
591
  try {
526
592
  let pump = await sys.board.pumps.deletePumpAsync(req.body);
@@ -638,14 +704,6 @@ export class ConfigRoute {
638
704
  next(err);
639
705
  }
640
706
  });
641
- app.delete('/config/pump/:pumpId', (req, res) => {
642
- let pump = sys.pumps.getItemById(parseInt(req.params.pumpId, 10));
643
- if (pump.type === 0) {
644
- return res.status(500).send(`Pump ${pump.id} not active`);
645
- }
646
- pump.setType(0);
647
- return res.status(200).send('OK');
648
- });
649
707
  app.put('/config/dateTime', async (req, res, next) => {
650
708
  try {
651
709
  let time = await sys.updateControllerDateTimeAsync(req.body);
@@ -706,6 +764,28 @@ export class ConfigRoute {
706
764
  }
707
765
  catch (err) { next(err); }
708
766
  });
767
+ app.put('/config/chemDoser', async (req, res, next) => {
768
+ try {
769
+ let doser = await sys.board.chemDosers.setChemDoserAsync(req.body);
770
+ return res.status(200).send(doser.get());
771
+ }
772
+ catch (err) { next(err); }
773
+
774
+ });
775
+ app.put('/config/chemController/calibrateDose', async (req, res, next) => {
776
+ try {
777
+ let schem = await sys.board.chemControllers.calibrateDoseAsync(req.body);
778
+ return res.status(200).send(schem.getExtended());
779
+ }
780
+ catch (err) { next(err); }
781
+ });
782
+ app.put('/config/chemDoser/calibrateDose', async (req, res, next) => {
783
+ try {
784
+ let schem = await sys.board.chemDosers.calibrateDoseAsync(req.body);
785
+ return res.status(200).send(schem.getExtended());
786
+ }
787
+ catch (err) { next(err); }
788
+ });
709
789
  app.put('/config/chemController/feed', async (req, res, next) => {
710
790
  try {
711
791
  let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
@@ -719,8 +799,16 @@ export class ConfigRoute {
719
799
  return res.status(200).send(chem.get());
720
800
  }
721
801
  catch (err) { next(err); }
802
+ });
803
+ app.delete('/config/chemDoser', async (req, res, next) => {
804
+ try {
805
+ let doser = await sys.board.chemDosers.deleteChemDoserAsync(req.body);
806
+ return res.status(200).send(doser.get());
807
+ }
808
+ catch (err) { next(err); }
722
809
 
723
810
  });
811
+
724
812
  /* app.get('/config/intellibrite', (req, res) => {
725
813
  return res.status(200).send(sys.intellibrite.getExtended());
726
814
  });
@@ -770,6 +858,13 @@ export class ConfigRoute {
770
858
  }
771
859
  catch (err) { next(err); }
772
860
  });
861
+ // app.put('/app/screenlogic', async (req, res, next) => {
862
+ // try {
863
+ // let screenlogic = await sl.setScreenlogicAsync(req.body);
864
+ // return res.status(200).send(screenlogic);
865
+ // }
866
+ // catch (err) { next(err); }
867
+ // });
773
868
  app.delete('/app/rs485Port', async (req, res, next) => {
774
869
  try {
775
870
  let port = await conn.deleteAuxPort(req.body);
@@ -909,5 +1004,17 @@ export class ConfigRoute {
909
1004
  return res.status(200).send(results);
910
1005
  } catch (err) { next(err); }
911
1006
  });
1007
+ app.put('/app/anslq25', async(req, res, next) => {
1008
+ try {
1009
+ await sys.anslq25Board.setAnslq25Async(req.body);
1010
+ return res.status(200).send(sys.anslq25.get(true));
1011
+ } catch (err) { next(err); }
1012
+ });
1013
+ app.delete('/app/anslq25', async(req, res, next) => {
1014
+ try {
1015
+ await sys.anslq25Board.deleteAnslq25Async(req.body);
1016
+ return res.status(200).send(sys.anslq25.get(true));
1017
+ } catch (err) { next(err); }
1018
+ });
912
1019
  }
913
1020
  }
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -22,14 +23,64 @@ import { sys } from "../../../controller/Equipment";
22
23
  import { utils } from '../../../controller/Constants';
23
24
  import { logger } from "../../../logger/Logger";
24
25
  import { DataLogger } from "../../../logger/DataLogger";
26
+ import { conn } from "../../../controller/comms/Comms";
27
+ import { config } from "../../../config/Config";
25
28
 
26
29
  import { ServiceParameterError } from "../../../controller/Errors";
27
30
 
28
31
  export class StateRoute {
29
32
  public static initRoutes(app: express.Application) {
33
+ app.get('/state/rs485Port/:id', async (req, res, next) => {
34
+ try {
35
+ let portId = parseInt(req.params.id, 10);
36
+ if (isNaN(portId)) throw new ServiceParameterError(`RS485 port id not supplied`, '/state/rs485Port/:id', 'portId', req.params.id);
37
+ let cfg = config.getSection(portId === 0 ? 'controller.comms' : `controller.comms${portId}`);
38
+ if (typeof cfg === 'undefined') throw new ServiceParameterError(`RS485 port id not found`, '/state/rs485Port/:id', 'portId', req.params.id);
39
+ let port = conn.findPortById(portId);
40
+ let sport: any = {
41
+ portId: portId,
42
+ enabled: cfg.enabled || false,
43
+ netConnect: cfg.netConnect,
44
+ reconnects: 0,
45
+ inactivityRetry: cfg.inactivityRetry,
46
+ isOpen: false,
47
+ mock: cfg.mock || false
48
+ }
49
+ if (cfg.netConnect) sport.netConnect = { host: cfg.netHost, port: cfg.netPort }
50
+ else if (typeof cfg.type !== 'undefined' && cfg.type === 'screenlogic'){
51
+ sport.screenlogic = cfg.screenlogic;
52
+ }
53
+ else sport.settings = extend(true, { name: cfg.rs485Port }, cfg.portSettings);
54
+ if (typeof port !== 'undefined' && port.type !== 'screenlogic') {
55
+ let stats = port.stats;
56
+ sport.reconnects = port.reconnects;
57
+ sport.isOpen = port.isOpen;
58
+ sport.received = {
59
+ bytes: stats.bytesReceived,
60
+ success: stats.recSuccess,
61
+ failed: stats.recFailed,
62
+ collisions: stats.recCollisions,
63
+ rewinds: stats.recFRewinds,
64
+ failureRate: stats.recFailureRate
65
+ };
66
+ sport.sent = {
67
+ bytes: stats.bytesSent,
68
+ success: stats.sndSuccess,
69
+ aborted: stats.sndAborted,
70
+ retries: stats.sndRetries,
71
+ failureRate: stats.sndFailureRate
72
+ }
73
+ }
74
+ res.status(200).send(sport);
75
+ }
76
+ catch (err) { next(err); }
77
+ });
30
78
  app.get('/state/chemController/:id', (req, res) => {
31
79
  res.status(200).send(state.chemControllers.getItemById(parseInt(req.params.id, 10)).getExtended());
32
80
  });
81
+ app.get('/state/chemDoser/:id', (req, res) => {
82
+ res.status(200).send(state.chemDosers.getItemById(parseInt(req.params.id, 10)).getExtended());
83
+ });
33
84
  app.put('/state/chemController', async (req, res, next) => {
34
85
  try {
35
86
  let schem = await sys.board.chemControllers.setChemControllerStateAsync(req.body);
@@ -37,6 +88,13 @@ export class StateRoute {
37
88
  }
38
89
  catch (err) { next(err); }
39
90
  });
91
+ app.put('/state/chemDoser', async (req, res, next) => {
92
+ try {
93
+ let schem = await sys.board.chemDosers.setChemDoserStateAsync(req.body);
94
+ return res.status(200).send(schem.getExtended());
95
+ }
96
+ catch (err) { next(err); }
97
+ });
40
98
  app.put('/state/chemController/manualDose', async (req, res, next) => {
41
99
  try {
42
100
  let schem = await sys.board.chemControllers.manualDoseAsync(req.body);
@@ -44,6 +102,14 @@ export class StateRoute {
44
102
  }
45
103
  catch (err) { next(err); }
46
104
  });
105
+ app.put('/state/chemDoser/manualDose', async (req, res, next) => {
106
+ try {
107
+ let schem = await sys.board.chemDosers.manualDoseAsync(req.body);
108
+ return res.status(200).send(schem.getExtended());
109
+ }
110
+ catch (err) { next(err); }
111
+ });
112
+
47
113
  app.put('/state/chemController/manualMix', async (req, res, next) => {
48
114
  try {
49
115
  logger.debug(`Starting manual mix`);
@@ -53,6 +119,13 @@ export class StateRoute {
53
119
  }
54
120
  catch (err) { next(err); }
55
121
  });
122
+ app.put('/state/chemDoser/manualMix', async (req, res, next) => {
123
+ try {
124
+ let schem = await sys.board.chemDosers.manualMixAsync(req.body);
125
+ return res.status(200).send(schem.getExtended());
126
+ }
127
+ catch (err) { next(err); }
128
+ });
56
129
  app.get('/state/chemController/:id/doseHistory', (req, res) => {
57
130
  let schem = state.chemControllers.getItemById(parseInt(req.params.id));
58
131
  let hist = { ph: [], orp: [] };
@@ -62,6 +135,11 @@ export class StateRoute {
62
135
  hist.orp.push(schem.orp.doseHistory[i]);
63
136
  return res.status(200).send(hist);
64
137
  });
138
+ app.get('/state/chemDoser/:id/doseHistory', (req, res) => {
139
+ let schem = state.chemDosers.getItemById(parseInt(req.params.id));
140
+ return res.status(200).send(schem.doseHistory);
141
+ });
142
+
65
143
  app.put('/state/chemController/:id/doseHistory/orp/clear', async (req, res, next) => {
66
144
  try {
67
145
  let schem = state.chemControllers.getItemById(parseInt(req.params.id));
@@ -71,6 +149,16 @@ export class StateRoute {
71
149
  }
72
150
  catch (err) { next(err); }
73
151
  });
152
+ app.put('/state/chemDoser/:id/doseHistory/clear', async (req, res, next) => {
153
+ try {
154
+ let schem = state.chemDosers.getItemById(parseInt(req.params.id));
155
+ schem.doseHistory = [];
156
+ schem.calcDoseHistory();
157
+ return res.status(200).send(schem.doseHistory);
158
+ }
159
+ catch (err) { next(err); }
160
+ });
161
+
74
162
  app.put('/state/chemController/:id/doseHistory/ph/clear', async (req, res, next) => {
75
163
  try {
76
164
  let schem = state.chemControllers.getItemById(parseInt(req.params.id));
@@ -95,6 +183,21 @@ export class StateRoute {
95
183
  }
96
184
  catch (err) { next(err); }
97
185
  });
186
+ app.get('/state/chemDoser/:id/doseLog', async (req, res, next) => {
187
+ try {
188
+ let schem = state.chemDosers.getItemById(parseInt(req.params.id));
189
+ let filter = req.body || {};
190
+ let dh = await DataLogger.readFromEndAsync(`chemDosage_Peristalic.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState, arr: ChemicalDoseState[]): boolean => {
191
+ if (entry.id !== schem.id) return false;
192
+ if (typeof filter.lines !== 'undefined' && filter.lines <= arr.length) return false;
193
+ if (typeof filter.date !== 'undefined' && entry.end < filter.date) return false;
194
+ return true;
195
+ });
196
+ return res.status(200).send(dh);
197
+ }
198
+ catch (err) { next(err); }
199
+ });
200
+
98
201
  app.search('/state/chemController/:id/doseLog/ph', async (req, res, next) => {
99
202
  try {
100
203
  let schem = state.chemControllers.getItemById(parseInt(req.params.id));
@@ -144,6 +247,13 @@ export class StateRoute {
144
247
  }
145
248
  catch (err) { next(err); }
146
249
  });
250
+ app.put('/state/chemDoser/cancelDosing', async (req, res, next) => {
251
+ try {
252
+ let schem = await sys.board.chemDosers.cancelDosingAsync(req.body);
253
+ return res.status(200).send(schem.getExtended());
254
+ }
255
+ catch (err) { next(err); }
256
+ });
147
257
  app.put('/state/chemController/cancelMixing', async (req, res, next) => {
148
258
  try {
149
259
  let schem = await sys.board.chemControllers.cancelMixingAsync(req.body);
@@ -151,6 +261,13 @@ export class StateRoute {
151
261
  }
152
262
  catch (err) { next(err); }
153
263
  });
264
+ app.put('/state/chemDoser/cancelMixing', async (req, res, next) => {
265
+ try {
266
+ let schem = await sys.board.chemDosers.cancelMixingAsync(req.body);
267
+ return res.status(200).send(schem.getExtended());
268
+ }
269
+ catch (err) { next(err); }
270
+ });
154
271
 
155
272
  app.get('/state/chlorinator/:id', (req, res) => {
156
273
  res.status(200).send(state.chlorinators.getItemById(parseInt(req.params.id, 10), false).getExtended());
@@ -233,6 +350,13 @@ export class StateRoute {
233
350
  }
234
351
  catch (err) { next(err); }
235
352
  });
353
+ app.put('/state/light/:id/colorSync', async (req, res, next) => {
354
+ try {
355
+ let slight = await sys.board.circuits.runLightCommandAsync({ id: parseInt(req.params.id, 10), command: 'colorsync' });
356
+ return res.status(200).send(slight.get(true));
357
+ }
358
+ catch (err) { next(err); }
359
+ });
236
360
  app.put('/state/light/:id/colorHold', async (req, res, next) => {
237
361
  try {
238
362
  let slight = await sys.board.circuits.runLightCommandAsync({ id: parseInt(req.params.id, 10), command: 'colorhold' });
@@ -306,6 +430,7 @@ export class StateRoute {
306
430
  // RKS: 06-24-20 -- Changed this so that users can send in the body id, circuit id, or the name.
307
431
  // RKS: 05-14-21 -- Added cooling setpoints for the body.
308
432
  try {
433
+
309
434
  let body = sys.bodies.findByObject(req.body);
310
435
  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));
311
436
  if (typeof req.body.coolSetpoint !== 'undefined' && !isNaN(parseInt(req.body.coolSetpoint, 10)))
@@ -426,8 +551,24 @@ export class StateRoute {
426
551
  }
427
552
  catch (err) { next(err); }
428
553
  });
429
-
430
-
554
+ app.put('/state/panelMode', async (req, res, next) => {
555
+ try {
556
+ await sys.board.system.setPanelModeAsync(req.body);
557
+ return res.status(200).send(state.controllerState);
558
+ } catch (err) { next(err); }
559
+ });
560
+ app.put('/state/toggleServiceMode', async (req, res, next) => {
561
+ try {
562
+ let data = extend({}, req.body);
563
+ if (state.mode === 0) {
564
+ if (typeof data.timeout !== 'undefined' && !isNaN(data.timeout)) data.mode = 'timeout';
565
+ else data.mode = 'service';
566
+ await sys.board.system.setPanelModeAsync(req.body);
567
+ }
568
+ else sys.board.system.setPanelModeAsync({ mode: 'auto' });
569
+ return res.status(200).send(state.controllerState);
570
+ } catch (err) { next(err); }
571
+ });
431
572
  app.get('/state/emitAll', (req, res) => {
432
573
  res.status(200).send(state.emitAllEquipmentChanges());
433
574
  });