nodejs-poolcontroller 8.0.5 → 8.1.1

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.
package/Changelog CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.1.0
4
+ 1. Support for dual chlorinators with REM chem controllers. It is now possible to have two separate chlorinators controlled in 'dynamic' mode by two separate REM chems. Note: In order for REM chem to control each chlorinator, each needs to be on a dedicated RS-485 port (not shared with an OCP or any other chlorinator).
5
+
6
+ ## 8.0.1-8.0.5
7
+ 1. Bug fixes including:
8
+ a. schedule end time errors
9
+ b. manual priority
10
+ c. screenlogic recurring schedules
11
+ d. VF pump message sequences
12
+ e. intellibrite themes
13
+ f. schedules are evaluated by bodies first and then everything else
14
+ g. solar stop/start delta logic
15
+ 2. Jandy WaterColors support
16
+ 3. Initial docker support (github docker actions)
17
+
3
18
  ## 8.0.0
4
19
  1. Refactor comms code to Async
5
20
  2. Update dependencies and Node >16
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ```diff
2
2
  - INTELLICENTER USERS: Do not upgrade Intellicenter to 2.006. Rollback to 1.064 to use this application.
3
3
  ```
4
- # nodejs-poolController - Version 8.0
4
+ # nodejs-poolController - Version 8.1
5
5
 
6
6
  ## What is nodejs-poolController
7
7
 
@@ -26,6 +26,10 @@ Equipment supported
26
26
  ## Latest Changes
27
27
  See [Changelog](https://github.com/tagyoureit/nodejs-poolController/blob/master/Changelog)
28
28
 
29
+ ## What's new in 8.1?
30
+
31
+ Support for dual chlorinators with REM chem controllers. It is now possible to have two separate chlorinators controlled in 'dynamic' mode by two separate REM chems. Note: In order for REM chem to control each chlorinator, each needs to be on a dedicated RS-485 port (not shared with an OCP or any other chlorinator).
32
+
29
33
  ## What's new in 8.0?
30
34
 
31
35
  Screenlogic can now be used as a direct connection point. If you feel that integrating an RS-485 adapter is a bit too much, then this is an option for you. The preferred method is still RS-485 as it is more fully featured.
@@ -62,8 +66,6 @@ If you don't know anything about NodeJS, these directions might be helpful.
62
66
  1. Run the app with `npm start`.
63
67
  * `npm start` will compile the Typescript code. You should use this every time you download/clone/pull the latest code.
64
68
  * `npm run start:cached` will run the app without compiling the code which can be much faster.
65
- 1. Running `npm start` will also create a `config.json` file for your installation. If you need to modify any properties (e.g. the path to your serialport adapter, enabling socat, etc) then stop the app, edit the `config.json` per the [instructions](module_nodejs-poolController--config.json) below, and start the app again.
66
- 1. Verify your pool equipment is correctly identified by inspecting the `/data/*.json` files.
67
69
  1. Install a [webclient](module_nodejs-poolController--clients) for a browser experience and/or a [binding](module_nodejs-poolController--bindings) to have two way control with Home Automation systems.
68
70
 
69
71
  For a very thorough walk-through, see [this](https://www.troublefreepool.com/threads/pentair-intellicenter-pool-control-dashboard-instructional-guide.218514/) great thread on Trouble Free Pool. Thanks @MyAZPool.
@@ -97,16 +99,13 @@ To do anything with this app, you need a client to connect to it. A client can
97
99
 
98
100
  ## Web Clients
99
101
  1. [nodejs-poolController-dashPanel](https://github.com/rstrouse/nodejs-poolController-dashPanel). Full compatibility with IntelliCenter, *Touch, REM (RelayEquipmentManager).
100
- 1. Deprecated - ~~[nodejs-poolController-webClient](http://github.com/tagyoureit/nodejs-poolController-webClient). Built primarily around EasyTouch/IntelliTouch but will work with other systems.~~
101
102
 
102
- * This app has the default to only listen to clients from localhost (127.0.0.1). If you need to have clients connect from other machines you will need to change the [ip](#module_nodejs-poolController--config.json) in `config.json`.
103
103
 
104
104
  <a name="module_nodejs-poolController--bindings"></a>
105
105
 
106
106
  ## Home Automation Bindings (previously Integrations)
107
- **NOTE: Existing integrations built of 5.3 or earlier WILL NOT WORK. They need to be upgraded to leverage 6.0. **
108
107
 
109
- Available for 6.x:
108
+ Available automations:
110
109
  * [Vera Home Automation Hub](https://github.com/rstrouse/nodejs-poolController-veraPlugin) - A plugin that integrates with nodejs-poolController. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#vera)
111
110
  * [Hubitat](https://github.com/bsileo/hubitat_poolcontroller) by @bsileo (prev help from @johnny2678, @donkarnag, @arrmo). [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#smartthingshubitat)
112
111
  * [Homebridge/Siri/EVE](https://github.com/gadget-monk/homebridge-poolcontroller) by @gadget-monk, adopted from @leftyflip
@@ -114,31 +113,25 @@ Available for 6.x:
114
113
  * [MQTT](https://github.com/crsherman/nodejs-poolController-mqtt) original release by @crsherman, re-write by @kkzonie, testing by @baudfather and others. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#mqtt)
115
114
  * [Homeseer](https://github.com/tagyoureit/nodejs-poolController/wiki/Homeseer-Setup-Instructions) - Integration directions by @miamijerry to integrate Homeseer through MQTT
116
115
 
117
- Need to be updated:
116
+ Outdated:
118
117
  * [Another SmartThings Controller](https://github.com/dhop90/pentair-pool-controller/blob/master/README.md) by @dhop90
119
118
  * [ISY](src/integrations/socketISY.js). Original credit to @blueman2, enhancements by @mayermd
120
119
  * [ISY Polyglot NodeServer](https://github.com/brianmtreese/nodejs-pool-controller-polyglotv2) created by @brianmtreese
121
120
 
122
121
  # Support
123
- 1. For discussions, recommendations, designs, and clarifications, we recommend you join the [Github discussions](https://github.com/tagyoureit/nodejs-poolController/discussions or [Gitter Chat room](https://gitter.im/pentair_pool/Lobby).
122
+ 1. For discussions, recommendations, designs, and clarifications, we recommend you join the [Github discussions](https://github.com/tagyoureit/nodejs-poolController/discussions.
124
123
  1. Check the [wiki](https://github.com/tagyoureit/nodejs-poolController/wiki) for tips, tricks and additional documentation.
125
124
  1. For bug reports you can open a [github issue](https://github.com/tagyoureit/nodejs-poolController/issues/new),
126
125
 
127
- ### Virtual Controller
128
- v6 adds all new configuration and support for virtual pumps, chlorinators (and soon, Intellichem)
129
-
130
- * [Virtual Pump Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Virtual-Pump-Controller---v6)
131
- * [Virtual Chlorinator Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Virtual-Chlorinator-Controller-v6)
132
- * Virtual Chem Controller
133
126
 
134
127
  # Changes
135
128
  See [Changelog](https://github.com/tagyoureit/nodejs-poolController/blob/master/Changelog)
136
129
 
137
-
138
130
  <a name="module_nodejs-poolController--config.json"></a>
139
131
  # Config.json changes
140
132
 
141
133
  ## Controller section - changes to the communications for the app
134
+ Most of these can be configured directly from the UI in dashPanel.
142
135
  * `rs485Port` - set to the name of you rs485 controller. See [wiki](https://github.com/tagyoureit/nodejs-poolController/wiki/RS-485-Adapter-Details) for details and testing.
143
136
  * `portSettings` - should not need to be changed for RS485
144
137
  * `mockPort` - opens a "fake" port for this app to communicate on. Can be used with [packet captures/replays](https://github.com/tagyoureit/nodejs-poolController/wiki/How-to-capture-all-packets-for-issue-resolution).
@@ -182,7 +175,7 @@ See [Changelog](https://github.com/tagyoureit/nodejs-poolController/blob/master/
182
175
  # License
183
176
 
184
177
  nodejs-poolController. An application to control pool equipment.
185
- Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
178
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025. Russell Goldin, tagyoureit. russ.goldin@gmail.com
186
179
 
187
180
  This program is free software: you can redistribute it and/or modify
188
181
  it under the terms of the GNU Affero General Public License as
@@ -196,7 +196,11 @@ export class MessagesMock {
196
196
  HeaterStateMessage.process(outboundMsg);
197
197
  break;*/
198
198
  case Protocol.Chlorinator:
199
- mockChlor.process(msg);
199
+ // Only process outbound messages (commands from OCP to chlorinator)
200
+ // Inbound messages (responses from chlorinator to OCP) should not be processed by mock
201
+ if (msg.dest >= 80) {
202
+ mockChlor.process(msg);
203
+ }
200
204
  /*
201
205
  case Protocol.Hayward:
202
206
  PumpStateMessage.processHayward(msg);
@@ -50,7 +50,7 @@ export class MockSystemBoard {
50
50
  return await msg.sendAsync();
51
51
  // is the controller on a real/physical port or a mock port?
52
52
  /* let port = conn.findPortById(sys.anslq25.portId);
53
- if (port.mockPort) {
53
+ if (port.mock) {
54
54
  let inbound = new Inbound();
55
55
  inbound.protocol = msg.protocol;
56
56
  inbound.header = msg.header;
@@ -15,13 +15,16 @@ export class MockChlorinator {
15
15
  switch (inbound.action){
16
16
  case 0: // Set control OCP->Chlorinator: [16,2,80,0][0][98,16,3]
17
17
  this.chlorSetControl(inbound, response);
18
+ break;
18
19
  case 17: // OCP->Chlorinator set output. [16,2,80,17][15][130,16,3]
19
20
  this.chlorSetOutput(inbound, response);
20
- case 19: // iChlor keep alive(?) [16, 2, 80, 19][117, 16, 3]
21
- // need response
21
+ break;
22
+ case 19: // iChlor keep alive(?) [16, 2, 80, 19][117, 16, 3]
23
+ this.chlorKeepAlive(inbound, response);
22
24
  break;
23
25
  case 20: // OCP->Chlorinator Get model [16,2,80,20][0][118,16,3]
24
26
  this.chlorGetModel(inbound, response);
27
+ break;
25
28
  default:
26
29
  logger.info(`No mock chlorinator response for ${inbound.toShortPacket()} `);
27
30
  }
@@ -45,13 +48,34 @@ export class MockChlorinator {
45
48
  /*
46
49
  {"port":0,"id":42639,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,17], [100],[215,16,3]],"ts":"2022-07-19T21:46:00.302-0700"}
47
50
  {"port":0,"id":42640,"valid":true,"dir":"in","proto":"chlorinator","for":[42639],"pkt":[[],[],[16,2,0,18],[78,128],[242,16,3]],"ts": "2022-07-19T21:46:00.341-0700"} */
48
- response.action = 18;
49
- response.appendPayloadBytes(0, 2);
50
- // ideal high = 4500 = 90 * 50; ideal low = 2800 = 56 * 50
51
- response.setPayloadByte(0, this.random(90-56, true)+56, 75)
52
- response.setPayloadByte(1, 128);
53
- conn.queueSendMessage(response);
51
+ // Simulate a response from the chlorinator (inbound message)
52
+ const payload = [Math.floor(this.random(90-56, true))+56, 128];
53
+ const header = [16, 2, 0, 18];
54
+ const term = [242, 16, 3];
55
+ let responseMsg = new Inbound();
56
+ responseMsg.protocol = inbound.protocol;
57
+ responseMsg.portId = inbound.portId;
58
+ responseMsg.header = header;
59
+ responseMsg.payload = payload;
60
+ responseMsg.term = term;
61
+ // The Inbound class will parse header/payload/action/source/dest automatically
62
+ setTimeout(() => {
63
+ let port = conn.findPortById(inbound.portId);
64
+ if (port) {
65
+ port.pushIn(Buffer.from(responseMsg.toPacket()));
66
+ }
67
+ }, 50);
54
68
  }
69
+ public chlorKeepAlive(inbound: Inbound, response: Outbound){
70
+ /*
71
+ {"port":0,"id":42647,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,19],[117,16,3]],"ts":"2022-07-19T21:46:00.645-0700"}
72
+ {"port":0,"id":42648,"valid":true,"dir":"in","proto":"chlorinator","for":[42647],"pkt":[[],[],[16,2,0,20],[0],[118,16,3]],"ts":"2022-07-19T21:46:00.700-0700"} */
73
+ response.action = 20;
74
+ response.appendPayloadBytes(0, 1);
75
+ response.setPayloadByte(0, 0);
76
+ conn.queueSendMessage(response);
77
+ }
78
+
55
79
  public chlorGetModel(inbound: Inbound, response: Outbound){
56
80
  /*
57
81
  {"port":0,"id":42645,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,20], [0],[118,16,3]],"ts":"2022-07-19T21:46:00.645-0700"}
package/app.ts CHANGED
@@ -25,7 +25,7 @@ import { conn } from "./controller/comms/Comms";
25
25
  import { sys } from "./controller/Equipment";
26
26
 
27
27
  import { state } from "./controller/State";
28
- import { webApp } from "./web/Server";
28
+ import { webApp, REMInterfaceServer } from "./web/Server";
29
29
  import * as readline from 'readline';
30
30
  import { sl } from './controller/comms/ScreenLogic'
31
31
 
@@ -52,6 +52,9 @@ export async function startPacketCapture(bResetLogs: boolean) {
52
52
  if (bResetLogs){
53
53
  sys.resetSystem();
54
54
  }
55
+
56
+ // Start packet capture on the REM server
57
+ await REMInterfaceServer.startPacketCaptureOnRemServer();
55
58
  }
56
59
  catch (err) {
57
60
  console.error(`Error starting replay: ${ err.message }`);
@@ -61,7 +64,12 @@ export async function stopPacketCaptureAsync() {
61
64
  let log = config.getSection('log');
62
65
  log.app.captureForReplay = false;
63
66
  config.setSection('log', log);
64
- return logger.stopCaptureForReplayAsync();
67
+
68
+ // Stop packet capture on the REM server and collect its logs
69
+ let remLogs = await REMInterfaceServer.stopPacketCaptureOnRemServer();
70
+
71
+ // Pass REM logs to the logger for inclusion in the backup
72
+ return logger.stopCaptureForReplayAsync(remLogs);
65
73
  }
66
74
  export async function stopAsync(): Promise<void> {
67
75
  try {
@@ -1678,6 +1678,10 @@ export class Chlorinator extends EqItem {
1678
1678
  public set ignoreSaltReading(val: boolean) { this.setDataVal('ignoreSaltReading', val); }
1679
1679
  public get model() { return this.data.model; }
1680
1680
  public set model(val: number | any) { this.setDataVal('model', sys.board.valueMaps.chlorinatorModel.encode(val)); }
1681
+ public get ratedLbs(): number {
1682
+ let model = sys.board.valueMaps.chlorinatorModel.get(this.model);
1683
+ return typeof model.chlorinePerSec !== 'undefined' ? model.chlorinePerSec : 0;
1684
+ }
1681
1685
  }
1682
1686
  export class ValveCollection extends EqItemCollection<Valve> {
1683
1687
  constructor(data: any, name?: string) { super(data, name || "valves"); }
@@ -2207,80 +2211,6 @@ export interface IChemController {
2207
2211
  }
2208
2212
  export class ChemController extends EqItem implements IChemController {
2209
2213
  public initData() {
2210
- //var chemController = {
2211
- // id: 'number', // Id of the controller
2212
- // name: 'string', // Name assigned to the controller
2213
- // type: 'valueMap', // intellichem, rem -- There is an unknown but that should probably go away.
2214
- // body: 'valueMap', // Body assigned to the chem controller.
2215
- // address: 'number', // Address for IntelliChem controller only.
2216
- // isActive: 'booean',
2217
- // isVirtual: 'boolean', // False if controlled by OCP.
2218
- // calciumHardness: 'number',
2219
- // cyanuricAcid: 'number',
2220
- // alkalinity: 'number',
2221
- // HMIAdvancedDisplay: 'boolean', // This is related to IntelliChem and determines what is displayed on the controller.
2222
- // ph: { // pH chemical structure
2223
- // chemType: 'string', // Constant ph
2224
- // enabled: 'boolean', // Allows disabling the functions without deleting the settings.
2225
- // dosingMethod: 'valueMap', // manual, volume, volumeTime.
2226
- // // manual = The dosing pump is not triggered.
2227
- // // volume = Time is not considered as a limit to the dosing.
2228
- // // time = The only limit to the dose is the amount of time.
2229
- // // volumeTime = Limit the dose by volume or time whichever is sooner.
2230
- // maxDosingTime: 'number', // The maximum amount of time a dose can occur before mixing.
2231
- // maxDosingVolume: 'number', // The maximum volume for a dose in mL.
2232
- // mixingTime: 'number', // Amount of time between in seconds doses that the pump must run before adding another dose.
2233
- // startDelay: 'number', // The number of seconds that the pump must be running prior to considering a dose.
2234
- // setpoint: 'number', // Target setpoint for pH
2235
- // phSupply: 'valueMap', // base or acid.
2236
- // pump: {
2237
- // type: 'valueMap', // none, relay, ezo-pmp
2238
- // connectionId: 'uuid', // Unique identifier for njspc external connections.
2239
- // deviceBinding: 'string', // Binding value for REM to tell it what device is involved.
2240
- // ratedFlow: 'number', // The standard flow rate for the pump in mL/min.
2241
- // },
2242
- // tank: {
2243
- // capacity: 'number', // Capacity of the tank in the units provided.
2244
- // units: 'valueMap' // gal, mL, cL, L, oz, pt, qt.
2245
- // },
2246
- // probe: {
2247
- // connectionId: 'uuid', // A unique identifier that has been generated for connections in njspc.
2248
- // deviceBinding: 'string', // A mapping value that is used by REM to determine which device is used.
2249
- // type: 'valueMap' // none, ezo-ph, other.
2250
- // }
2251
-
2252
- // },
2253
- // orp: { // ORP chemical structure
2254
- // chemType: 'string', // Constant orp
2255
- // enabled: 'boolean', // Allows disabling the functions without deleting the settings.
2256
- // dosingMethod: 'valueMap', // manual, volume, volumeTime.
2257
- // // manual = The dosing pump is not triggered.
2258
- // // volume = Time is not considered as a limit to the dosing.
2259
- // // time = The only limit to the dose is the amount of time.
2260
- // // volumeTime = Limit the dose by volume or time whichever is sooner.
2261
- // maxDosingTime: 'number', // The maximum amount of time a dose can occur before mixing.
2262
- // maxDosingVolume: 'number', // The maximum volume for a dose in mL.
2263
- // mixingTime: 'number', // Amount of time between in seconds doses that the pump must run before adding another dose.
2264
- // startDelay: 'number', // The number of seconds that the pump must be running prior to considering a dose.
2265
- // setpoint: 'number', // Target setpoint for ORP
2266
- // useChlorinator: 'boolean', // Indicates whether the chlorinator will be used for dosing.
2267
- // pump: {
2268
- // type: 'valueMap', // none, relay, ezo-pmp
2269
- // connectionId: 'uuid', // Unique identifier for njspc external connections.
2270
- // deviceBinding: 'string', // Binding value for REM to tell it what device is involved.
2271
- // ratedFlow: 'number', // The standard flow rate for the pump in mL/min.
2272
- // },
2273
- // tank: {
2274
- // capacity: 'number', // Capacity of the tank in the units provided.
2275
- // units: 'valueMap' // gal, mL, cL, L, oz, pt, qt.
2276
- // },
2277
- // probe: {
2278
- // connectionId: 'uuid', // A unique identifier that has been generated for connections in njspc.
2279
- // deviceBinding: 'string', // A mapping value that is used by REM to determine which device is used.
2280
- // type: 'valueMap' // none, ezo-orp, other.
2281
- // }
2282
- // }
2283
- //}
2284
2214
  if (typeof this.data.lsiRange === 'undefined') this.data.lsiRange = { low: -.5, high: .5, enabled: true };
2285
2215
  if (typeof this.data.borates === 'undefined') this.data.borates = 0;
2286
2216
  if (typeof this.data.siCalcType === 'undefined') this.data.siCalcType = 0;
@@ -2299,8 +2229,6 @@ export class ChemController extends EqItem implements IChemController {
2299
2229
  public set address(val: number) { this.setDataVal('address', val); }
2300
2230
  public get isActive(): boolean { return this.data.isActive; }
2301
2231
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
2302
- // public get isVirtual(): boolean { return this.data.isVirtual; }
2303
- // public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
2304
2232
  public get calciumHardness(): number { return this.data.calciumHardness; }
2305
2233
  public set calciumHardness(val: number) { this.setDataVal('calciumHardness', val); }
2306
2234
  public get cyanuricAcid(): number { return this.data.cyanuricAcid; }
@@ -2460,7 +2388,7 @@ export class Chemical extends ChildEqItem implements IChemical {
2460
2388
  public set startDelay(val: number) { this.setDataVal('startDelay', val); }
2461
2389
  public get pump(): ChemicalPump { return new ChemicalPump(this.data, 'pump', this); }
2462
2390
  public get tank(): ChemicalTank { return new ChemicalTank(this.data, 'tank', this); }
2463
- public get chlor(): ChemicalChlor { return new ChemicalChlor(this.data, 'chlor', this); }
2391
+ // public get chlor(): Chlorinator { return new ChemicalChlor(this.data, 'chlor', this); }
2464
2392
  public get setpoint(): number { return this.data.setpoint; }
2465
2393
  public set setpoint(val: number) { this.setDataVal('setpoint', val); }
2466
2394
  public get tolerance(): AlarmSetting { return new AlarmSetting(this.data, 'tolerance', this); }
@@ -2518,6 +2446,14 @@ export class ChemicalORP extends Chemical {
2518
2446
  }
2519
2447
  public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
2520
2448
  public set useChlorinator(val: boolean) { this.setDataVal('useChlorinator', val); }
2449
+ public get chlorId(): number {
2450
+ if (typeof this.data.chlorId === 'undefined'){
2451
+ // default to 1st chlorinator if not set; this is a backwards compatibility item when upgrading to 8.1
2452
+ return sys.chlorinators.getItemByIndex(0).id;
2453
+ }
2454
+ return this.data.chlorId;
2455
+ }
2456
+ public set chlorId(val: number) { this.setDataVal('chlorId', val); }
2521
2457
  public get phLockout(): number { return this.data.phLockout; }
2522
2458
  public set phLockout(val: number) { this.setDataVal('phLockout', val); }
2523
2459
  public get probe(): ChemicalORPProbe { return new ChemicalORPProbe(this.data, 'probe', this); }
@@ -2661,7 +2597,7 @@ export class ChemicalTank extends ChildEqItem {
2661
2597
  return tank;
2662
2598
  }
2663
2599
  }
2664
- export class ChemicalChlor extends ChildEqItem {
2600
+ /* export class ChemicalChlor extends ChildEqItem {
2665
2601
  // This whole class is a reference to the first chlorinator.
2666
2602
  // This may not follow a best practice
2667
2603
  // and certainly won't work for multiple chlors
@@ -2695,7 +2631,7 @@ export class ChemicalChlor extends ChildEqItem {
2695
2631
  chlor.model = sys.board.valueMaps.chlorinatorModel.transform(this.model);
2696
2632
  return chlor;
2697
2633
  }
2698
- }
2634
+ } */
2699
2635
  export class AlarmSetting extends ChildEqItem {
2700
2636
  public dataName = 'AlarmSettingConfig';
2701
2637
  public initData() {
@@ -1096,7 +1096,7 @@ export class ScheduleStateCollection extends EqStateCollection<ScheduleState> {
1096
1096
  }
1097
1097
  st.calcSchedule(state.time, sys.schedules.getItemById(ssched.id));
1098
1098
  if (typeof st.startTime === 'undefined') continue;
1099
- if (ssched.isOn || st.shouldBeOn || st.startTime.getTime() > new Date().getTime()) activeScheds.push(ssched);
1099
+ if (ssched.isOn || st.shouldBeOn || (st.startTime && st.startTime.getTime() > new Date().getTime())) activeScheds.push(ssched);
1100
1100
  }
1101
1101
  return activeScheds;
1102
1102
  }
@@ -1256,7 +1256,7 @@ export class ScheduleTime extends ChildEqState {
1256
1256
  let dtCalc = typeof this.calculatedDate !== 'undefined' && typeof this.calculatedDate.getTime === 'function' ? new Date(this.calculatedDate.getTime()).setHours(0, 0, 0, 0) : new Date(1970, 0, 1, 0, 0).getTime();
1257
1257
  let recalc = !this.calculated;
1258
1258
  if (!recalc && sod.getTime() !== dtCalc) recalc = true;
1259
- if (!recalc && (this.endTime.getTime() < new Date().getTime() && this.startTime.getTime() < dtCalc)) {
1259
+ if (!recalc && (this.endTime && this.endTime.getTime() < new Date().getTime() && this.startTime && this.startTime.getTime() < dtCalc)) {
1260
1260
  recalc = true;
1261
1261
  logger.info(`Recalculating expired schedule ${sched.id}`);
1262
1262
  }
@@ -2658,74 +2658,6 @@ export class ChemControllerState extends EqState implements IChemControllerState
2658
2658
  if (typeof this.data.siCalcType === 'undefined') {
2659
2659
  this.data.siCalcType = sys.board.valueMaps.siCalcTypes.transform(0);
2660
2660
  }
2661
- //var chemControllerState = {
2662
- // lastComm: 'number', // The unix time the chem controller sent its status.
2663
- // id: 'number', // Id of the chemController.
2664
- // type: 'valueMap', // intellichem, rem.
2665
- // address: 'number', // Assigned address if IntelliChem.
2666
- // name: 'string', // Name assigned to the controller.
2667
- // status: 'valueMap', // ok, nocomms, setupError
2668
- // body: 'valueMap', // Body that the chemController is assigned to.
2669
- // flowDetected: 'boolean', // True if there is currently sufficient flow to read and dose.
2670
- // flowDelay: 'boolean', // True of the controller is currently under a flow delay.
2671
- // firmware: 'string', // Firmware version from IntelliChem (this should be in config)
2672
- // saturationIndex: 'number', // Calculated LSI for the body.
2673
- // isActive: 'boolean',
2674
- // alarms: {}, // This has not changed although additional alarms will be added.
2675
- // warnings: {}, // This has not changed although additional warnings will be added.
2676
- // chemistryStatus: 'valueMap', // Current water quality status.
2677
- // ph: {
2678
- // chemType: 'string', // Constant ph.
2679
- // dosingTimeRemaining: 'number', // The number of seconds remaining for the current dose.
2680
- // dosingVolumeRemaining: 'number', // Remaining volume for the current dose in mL.
2681
- // mixTimeRemaining: 'number', // The number of seconds remaining in the current mix cycle.
2682
- // dosingStatus: 'valueMap', // dosing, monitoring, mixing.
2683
- // level: 'number', // The current pH level.
2684
- // lockout: 'boolean', // True if an attempt to dose was thwarted by error.
2685
- // manualDosing: 'boolean', // True if the pump is running outside of a dosing command.
2686
- // dailyLimitReached: 'boolean', // True if the calculated daily limit has been reached based upon body volume.
2687
- // pump: {
2688
- // type: 'valueMap', // The defined pump type.
2689
- // isDosing: 'boolean', // True if the pump is running.
2690
- // },
2691
- // tank: {
2692
- // level: 'number', // The current level for the tank.
2693
- // capacity: 'number', // Total capacity for the tank.
2694
- // units: 'valueMap', // nounits, gal, mL, cL, L, oz, pt, qt.
2695
- // },
2696
- // probe: {
2697
- // level: 'number', // Current ph level as measured by the probe.
2698
- // temperature: 'number', // The temperature used to calculate the adjusted probe level.
2699
- // tempUnits: 'valueMap' // Units for the temperature C or F.
2700
- // }
2701
- // },
2702
- // orp: {
2703
- // chemType: 'string', // Constant orp.
2704
- // dosingTimeRemaining: 'number', // The number of seconds remaining for the current dose.
2705
- // dosingVolumeRemaining: 'number', // Remaining volume for the current dose in mL.
2706
- // mixTimeRemaining: 'number', // The number of seconds remaining in the current mix cycle.
2707
- // dosingStatus: 'valueMap', // dosing, monitoring, mixing.
2708
- // level: 'number', // The current ORP level.
2709
- // lockout: 'boolean', // True if an attempt to dose was thwarted by error.
2710
- // manualDosing: 'boolean', // True if the pump is running outside of a dosing command.
2711
- // dailyLimitReached: 'boolean', // True if the calculated daily limit has been reached based upon body volume.
2712
- // pump: {
2713
- // type: 'valueMap', // The defined pump type.
2714
- // isDosing: 'boolean', // True if the pump is running.
2715
- // },
2716
- // tank: {
2717
- // level: 'number', // The current level for the tank.
2718
- // capacity: 'number', // Total capacity for the tank.
2719
- // units: 'valueMap', // nounits, gal, mL, cL, L, oz, pt, qt.
2720
- // },
2721
- // probe: {
2722
- // level: 'number', // Current ORP level as measured by the probe.
2723
- // temperature: 'number', // The temperature used to calculate the adjusted probe level.
2724
- // tempUnits: 'valueMap' // Units for the temperature C or F.
2725
- // }
2726
- // }
2727
- //}
2728
-
2729
2661
  }
2730
2662
  public dataName: string = 'chemController';
2731
2663
  public get lastComm(): number { return this.data.lastComm || 0; }
@@ -3175,8 +3107,8 @@ export class ChemicalORPState extends ChemicalState {
3175
3107
  public get chemType() { return 'orp'; }
3176
3108
  public set chemType(val) { this.setDataVal('chemType', val); }
3177
3109
  public get probe() { return new ChemicalProbeORPState(this.data, 'probe', this); }
3178
- public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
3179
- public set useChlorinator(val: boolean) { this.setDataVal('useChlorinator', val); }
3110
+ // public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
3111
+ // public set useChlorinator(val: boolean) { this.setDataVal('useChlorinator', val); }
3180
3112
  public get suspendDosing(): boolean {
3181
3113
  let cc = this.chemController;
3182
3114
  return cc.alarms.comms !== 0 || cc.alarms.orpProbeFault !== 0 || cc.alarms.orpPumpFault !== 0 || cc.alarms.bodyFault !== 0;
@@ -73,6 +73,7 @@ export class NixieBoard extends SystemBoard {
73
73
  [14, { name: 'colorlogic', desc: 'ColorLogic', isLight: true, theme: 'colorlogic' }],
74
74
  [15, { name: 'spadrain', desc: 'Spa Drain' }],
75
75
  [16, { name: 'pooltone', desc: 'Pool Tone', isLight: true, theme: 'pooltone' }],
76
+ [17, { name: 'watercolors', desc: 'WaterColors', isLight: true, theme: 'watercolors' }],
76
77
  ]);
77
78
  this.valueMaps.pumpTypes = new byteValueMap([
78
79
  [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
@@ -3656,8 +3656,12 @@ export class ScheduleCommands extends BoardCommands {
3656
3656
  ssched.display = sched.display = display;
3657
3657
  ssched.startTimeOffset = sched.startTimeOffset = startTimeOffset;
3658
3658
  ssched.endTimeOffset = sched.endTimeOffset = endTimeOffset;
3659
- if (typeof sched.startDate === 'undefined')
3659
+ // Nixie controller managing schedules (master = 1), physical OCP (master = 0)
3660
+ if (sys.controllerType === ControllerType.Nixie) {
3660
3661
  sched.master = 1;
3662
+ } else {
3663
+ sched.master = 0;
3664
+ }
3661
3665
  await ncp.schedules.setScheduleAsync(sched, data);
3662
3666
  // update end time in case sched is changed while circuit is on
3663
3667
  let cstate = state.circuits.getInterfaceById(sched.circuit);
@@ -198,7 +198,7 @@ export class Connection {
198
198
  let c = cfg[section];
199
199
  if (typeof c.type === 'undefined') {
200
200
  let type = 'local';
201
- if (c.mockPort) type = 'mock';
201
+ if (c.mock) type = 'mock';
202
202
  else if (c.netConnect) type = 'network';
203
203
  config.setSection(`controller.${section}`, c);
204
204
  console.log(section);
@@ -260,7 +260,7 @@ export class Connection {
260
260
  if (anslq25port >= 0) {
261
261
  let ports = this.rs485Ports;
262
262
  for (let i = 0; i < ports.length; i++) {
263
- // if (ports[i].mockPort) continue;
263
+ // if (ports[i].mock) continue;
264
264
  if (ports[i].portId === currPort.portId) continue;
265
265
  if (ports[i].portId === anslq25port) continue; // don't resend
266
266
  if (!ports[i].isOpen) continue;
@@ -380,7 +380,8 @@ export class Connection {
380
380
  let msgs = [];
381
381
  // conn.queueInboundToBroadcast(msg);
382
382
  conn.queueOutboundToBroadcast(msg);
383
- /* if (msgs.length > 0) {
383
+ /* if (msgs.le
384
+ ngth > 0) {
384
385
  msgs.push(msg);
385
386
  let promises: Promise<boolean>[] = [];
386
387
  for (let i = 0; i < msgs.length; i++) {
@@ -511,7 +512,7 @@ export class RS485Port {
511
512
  public reconnects: number = 0;
512
513
  public emitter: EventEmitter;
513
514
  public get portId() { return typeof this._cfg !== 'undefined' && typeof this._cfg.portId !== 'undefined' ? this._cfg.portId : 0; }
514
- public get type() { return typeof this._cfg.type !== 'undefined' ? this._cfg.type : this._cfg.netConnect ? 'netConnect' : this._cfg.mockPort || this._cfg.mock ? 'mock' : 'local' };
515
+ public get type() { return typeof this._cfg.type !== 'undefined' ? this._cfg.type : this._cfg.netConnect ? 'netConnect' : this._cfg.mock ? 'mock' : 'local' };
515
516
  public isOpen: boolean = false;
516
517
  public closing: boolean = false;
517
518
  private _cfg: any;
@@ -682,7 +683,7 @@ export class RS485Port {
682
683
  // be open if a hardware interface is used and this method returns.
683
684
  sp.open((err) => {
684
685
  if (err) {
685
- this.resetConnTimer();
686
+ if (!this.mock) this.resetConnTimer();
686
687
  this.isOpen = false;
687
688
  logger.error(`Error opening port ${this.portId}: ${err.message}. ${this._cfg.inactivityRetry > 0 && !this.mock ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; (fwiw, inactivityRetry set to ${this._cfg.inactivityRetry})`}`);
688
689
  resolve(false);
@@ -712,7 +713,7 @@ export class RS485Port {
712
713
  if (!this.mock && !this.isPaused) this.resetConnTimer();
713
714
  this.pushIn(data);
714
715
  });
715
- this.resetConnTimer();
716
+ if (!this.mock) this.resetConnTimer();
716
717
  this.emitPortStats();
717
718
  });
718
719
  sp.on('close', (err) => {
@@ -730,7 +731,7 @@ export class RS485Port {
730
731
  if (typeof this.writeTimer !== 'undefined') { clearTimeout(this.writeTimer); this.writeTimer = null; }
731
732
  this.isOpen = false;
732
733
  if (sp.isOpen) sp.close((err) => { }); // call this with the error callback so that it doesn't emit to the error again.
733
- this.resetConnTimer();
734
+ if (!this.mock) this.resetConnTimer();
734
735
  logger.error(`Serial Port ${this.portId}: An error occurred : ${this._cfg.rs485Port}: ${JSON.stringify(err)}`);
735
736
  this.emitPortStats();
736
737
 
@@ -823,7 +824,7 @@ export class RS485Port {
823
824
  protected resetConnTimer(...args) {
824
825
  //console.log(`resetting connection timer`);
825
826
  if (this.connTimer !== null) clearTimeout(this.connTimer);
826
- if (!this._cfg.mockPort && this._cfg.inactivityRetry > 0 && !this.closing) this.connTimer = setTimeout(async () => {
827
+ if (!this._cfg.mock && this._cfg.inactivityRetry > 0 && !this.closing) this.connTimer = setTimeout(async () => {
827
828
  try {
828
829
  if (this._cfg.netConnect)
829
830
  logger.warn(`Inactivity timeout for ${this.portId} serial port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port} after ${this._cfg.inactivityRetry} seconds`);
@@ -873,7 +873,7 @@ class OutboundCommon extends Message {
873
873
  case Protocol.Heater:
874
874
  case Protocol.Hayward:
875
875
  this.chkHi = Math.floor(sum / 256);
876
- this.chkLo = (sum - (super.chkHi * 256));
876
+ this.chkLo = (sum - (this.chkHi * 256));
877
877
  break;
878
878
  case Protocol.AquaLink:
879
879
  case Protocol.Chlorinator: