nodejs-poolcontroller 8.0.1 → 8.0.2

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 (47) hide show
  1. package/.docker/Dockerfile.armv6 +29 -0
  2. package/.docker/Dockerfile.armv7 +29 -0
  3. package/.docker/Dockerfile.linux +62 -0
  4. package/.docker/Dockerfile.windows +43 -0
  5. package/.docker/docker-compose.yml +47 -0
  6. package/.docker/ecosystem.config.js +35 -0
  7. package/.github/workflows/docker-publish-njsPC-linux.yml +81 -0
  8. package/.github/workflows/docker-publish-njsPC-windows.yml +41 -0
  9. package/Dockerfile +4 -3
  10. package/README.md +4 -1
  11. package/config/Config.ts +1 -1
  12. package/controller/Constants.ts +164 -67
  13. package/controller/Equipment.ts +78 -18
  14. package/controller/Lockouts.ts +15 -0
  15. package/controller/State.ts +280 -7
  16. package/controller/boards/EasyTouchBoard.ts +225 -101
  17. package/controller/boards/IntelliCenterBoard.ts +67 -18
  18. package/controller/boards/IntelliTouchBoard.ts +2 -4
  19. package/controller/boards/NixieBoard.ts +84 -27
  20. package/controller/boards/SunTouchBoard.ts +8 -2
  21. package/controller/boards/SystemBoard.ts +3259 -3242
  22. package/controller/comms/ScreenLogic.ts +47 -44
  23. package/controller/comms/messages/Messages.ts +4 -4
  24. package/controller/comms/messages/config/ChlorinatorMessage.ts +10 -3
  25. package/controller/comms/messages/config/ExternalMessage.ts +4 -1
  26. package/controller/comms/messages/config/PumpMessage.ts +8 -7
  27. package/controller/comms/messages/config/RemoteMessage.ts +48 -43
  28. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -2
  29. package/controller/comms/messages/status/EquipmentStateMessage.ts +9 -4
  30. package/controller/nixie/NixieEquipment.ts +1 -1
  31. package/controller/nixie/bodies/Body.ts +1 -1
  32. package/controller/nixie/chemistry/ChemController.ts +37 -28
  33. package/controller/nixie/circuits/Circuit.ts +36 -0
  34. package/controller/nixie/heaters/Heater.ts +24 -5
  35. package/controller/nixie/pumps/Pump.ts +155 -97
  36. package/controller/nixie/schedules/Schedule.ts +207 -126
  37. package/defaultConfig.json +3 -3
  38. package/logger/DataLogger.ts +7 -7
  39. package/package.json +2 -2
  40. package/sendSocket.js +32 -0
  41. package/web/Server.ts +17 -11
  42. package/web/bindings/homeassistant.json +2 -2
  43. package/web/interfaces/mqttInterface.ts +18 -18
  44. package/web/services/config/Config.ts +34 -1
  45. package/web/services/state/State.ts +10 -3
  46. package/web/services/state/StateSocket.ts +7 -3
  47. package/web/services/utilities/Utilities.ts +3 -3
@@ -21,7 +21,7 @@ import * as extend from 'extend';
21
21
  import { logger } from '../../logger/Logger';
22
22
  import { conn } from '../comms/Comms';
23
23
  import { Message, Outbound, Protocol, Response } from '../comms/messages/Messages';
24
- import { Timestamp, utils } from '../Constants';
24
+ import { ControllerType, Timestamp, utils } from '../Constants';
25
25
  import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, Options, PoolSystem, Pump, Schedule, sys, Valve } from '../Equipment';
26
26
  import { InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../Errors';
27
27
  import { ncp } from "../nixie/Nixie";
@@ -174,7 +174,7 @@ export class EasyTouchBoard extends SystemBoard {
174
174
  this.valueMaps.pumpTypes = new byteValueMap([
175
175
  [1, { name: 'vf', desc: 'Intelliflo VF', maxPrimingTime: 6, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
176
176
  [64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
177
- [65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 40, hasAddress: false, hasBody: true }],
177
+ [65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 4, hasAddress: false, hasBody: true }],
178
178
  [128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 10, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
179
179
  [169, { name: 'vssvrs', desc: 'IntelliFlo VS+SVRS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
180
180
  [257, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, equipmentMaster: 1, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }] }],
@@ -348,6 +348,49 @@ export class EasyTouchBoard extends SystemBoard {
348
348
  [131, { name: 'ET4P', part: 'ET-4P', desc: 'EasyTouch 4P', circuits: 4, single: true, shared: false }]
349
349
  ]);
350
350
  }
351
+ public initValves(eq) {
352
+ if (typeof sys.valves.find((v) => v.id === 1) === 'undefined') {
353
+ let valve = sys.valves.getItemById(1, true);
354
+ valve.isIntake = false;
355
+ valve.isReturn = false;
356
+ valve.type = 0;
357
+ valve.master = 0;
358
+ valve.isActive = true;
359
+ valve.name = 'Valve A';
360
+ logger.info(`Initializing EasyTouch Valve A`);
361
+
362
+ }
363
+ if (typeof sys.valves.find((v) => v.id === 2) === 'undefined') {
364
+ let valve = sys.valves.getItemById(2, true);
365
+ valve.isIntake = false;
366
+ valve.isReturn = false;
367
+ valve.type = 0;
368
+ valve.master = 0;
369
+ valve.isActive = true;
370
+ valve.name = 'Valve B';
371
+ logger.info(`Initializing EasyTouch Valve B`);
372
+ }
373
+ if (eq.intakeReturnValves) {
374
+ logger.info(`Initializing EasyTouch Intake/Return Valves`);
375
+ let valve = sys.valves.getItemById(3, true);
376
+ valve.isIntake = true;
377
+ valve.isReturn = false;
378
+ valve.circuit = 6;
379
+ valve.type = 0;
380
+ valve.master = 0;
381
+ valve.isActive = true;
382
+ valve.name = 'Intake';
383
+
384
+ valve = sys.valves.getItemById(4, true);
385
+ valve.isIntake = false;
386
+ valve.isReturn = true;
387
+ valve.circuit = 6;
388
+ valve.type = 0;
389
+ valve.master = 0;
390
+ valve.isActive = true;
391
+ valve.name = 'Return';
392
+ }
393
+ }
351
394
  public initHeaterDefaults() {
352
395
  sys.board.heaters.updateHeaterServices();
353
396
  // RKS: 03-03-22 This is not correct. As it turns out there is a case where the only heater installed is not
@@ -428,7 +471,7 @@ export class EasyTouchBoard extends SystemBoard {
428
471
  eq.maxChlorinators = md.chlorinators = 1;
429
472
  eq.maxChemControllers = md.chemControllers = 1;
430
473
  eq.maxCustomNames = 10;
431
- eq.intakeReturnValves = md.intakeReturnValves = typeof mt.intakeReturnValves !== 'undefined' ? mt.intakeReturnValves : false;
474
+ eq.intakeReturnValves = md.intakeReturnValves = typeof mt.intakeReturnValves !== 'undefined' ? mt.intakeReturnValves : mt.shared;
432
475
  // Calculate out the invalid ids.
433
476
  sys.board.equipmentIds.invalidIds.set([]);
434
477
  if (!eq.shared) sys.board.equipmentIds.invalidIds.merge([1]);
@@ -438,6 +481,7 @@ export class EasyTouchBoard extends SystemBoard {
438
481
  state.equipment.controllerType = 'easytouch';
439
482
  this.initBodyDefaults();
440
483
  this.initHeaterDefaults();
484
+ this.initValves(eq);
441
485
  sys.board.bodies.initFilters();
442
486
  sys.equipment.shared ? sys.board.equipmentIds.circuits.start = 1 : sys.board.equipmentIds.circuits.start = 2;
443
487
  (async () => {
@@ -677,7 +721,8 @@ export class TouchScheduleCommands extends ScheduleCommands {
677
721
  let schedDays = sys.board.schedules.transformDays(typeof data.scheduleDays !== 'undefined' ? data.scheduleDays : sched.scheduleDays || 255); // default to all days
678
722
  let changeHeatSetpoint = typeof (data.changeHeatSetpoint !== 'undefined') ? utils.makeBool(data.changeHeatSetpoint) : sched.changeHeatSetpoint;
679
723
  let display = typeof data.display !== 'undefined' ? data.display : sched.display || 0;
680
-
724
+ let endTimeOffset = typeof data.endTimeOffset !== 'undefined' ? data.endTimeOffset : sched.endTimeOffset;
725
+ let startTimeOffset = typeof data.startTimeOffset !== 'undefined' ? data.startTimeOffset : sched.startTimeOffset;
681
726
  // Ensure all the defaults.
682
727
  // if (isNaN(startDate.getTime())) startDate = new Date();
683
728
  if (typeof startTime === 'undefined') startTime = 480; // 8am
@@ -722,10 +767,10 @@ export class TouchScheduleCommands extends ScheduleCommands {
722
767
  if (state.heliotrope.isCalculated) {
723
768
  const sunrise = state.heliotrope.sunrise.getHours() * 60 + state.heliotrope.sunrise.getMinutes();
724
769
  const sunset = state.heliotrope.sunset.getHours() * 60 + state.heliotrope.sunset.getMinutes();
725
- if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) startTime = sunrise;
726
- else if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) startTime = sunset;
727
- if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) endTime = sunrise;
728
- else if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) endTime = sunset;
770
+ if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) startTime = (sunrise + startTimeOffset);
771
+ else if (startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) startTime = (sunset + startTimeOffset);
772
+ if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise')) endTime = (sunrise + endTimeOffset);
773
+ else if (endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset')) endTime = (sunset + endTimeOffset);
729
774
  }
730
775
 
731
776
 
@@ -769,6 +814,8 @@ export class TouchScheduleCommands extends ScheduleCommands {
769
814
  sched.endTimeType = ssched.endTimeType = endTimeType;
770
815
  sched.isActive = ssched.isActive = true;
771
816
  ssched.display = sched.display = display;
817
+ sched.startTimeOffset = ssched.startTimeOffset = startTimeOffset;
818
+ sched.endTimeOffset = ssched.endTimeOffset = endTimeOffset;
772
819
  ssched.emitEquipmentChange();
773
820
  // For good measure russ is sending out a config request for
774
821
  // the schedule in question. If there was a failure on the
@@ -925,19 +972,19 @@ export class TouchScheduleCommands extends ScheduleCommands {
925
972
  let sUpdated = false;
926
973
  let sched = sys.schedules.getItemByIndex(i);
927
974
  if (sched.startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise') && sched.startTime !== sunrise) {
928
- sched.startTime = sunrise;
975
+ sched.startTime = sunrise + (sched.startTimeOffset || 0);
929
976
  anyUpdated = sUpdated = true;
930
977
  }
931
978
  else if (sched.startTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset') && sched.startTime !== sunset) {
932
- sched.startTime = sunset;
979
+ sched.startTime = sunset + (sched.startTimeOffset || 0);
933
980
  anyUpdated = sUpdated = true;
934
981
  }
935
982
  if (sched.endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunrise') && sched.endTime !== sunrise) {
936
- sched.endTime = sunrise;
983
+ sched.endTime = sunrise + (sched.endTimeOffset || 0);
937
984
  anyUpdated = sUpdated = true;
938
985
  }
939
986
  else if (sched.endTimeType === sys.board.valueMaps.scheduleTimeTypes.getValue('sunset') && sched.endTime !== sunset) {
940
- sched.endTime = sunset;
987
+ sched.endTime = sunset + (sched.endTimeOffset || 0);
941
988
  anyUpdated = sUpdated = true;
942
989
  }
943
990
  if (sUpdated) {
@@ -1919,14 +1966,18 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1919
1966
  if (typeof superChlorinate === 'undefined') superChlorinate = utils.makeBool(chlor.superChlor);
1920
1967
  }
1921
1968
  if (typeof obj.disabled !== 'undefined') chlor.disabled = utils.makeBool(obj.disabled);
1969
+ if (typeof obj.body !== 'undefined') chlor.body = parseInt(obj.body, 10);
1922
1970
  if (typeof chlor.body === 'undefined') chlor.body = parseInt(obj.body, 10) || 32;
1923
1971
  // Verify the data.
1924
- let body = sys.board.bodies.mapBodyAssociation(chlor.body).val;
1925
- if (typeof body === 'undefined') {
1972
+ let bdy = sys.board.bodies.mapBodyAssociation(chlor.body || 0);
1973
+ let body;
1974
+ if (typeof bdy === 'undefined') {
1926
1975
  if (sys.equipment.shared) body = 32;
1927
1976
  else if (!sys.equipment.dual) body = 0;
1928
1977
  else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
1929
1978
  }
1979
+ else
1980
+ body = bdy.val;
1930
1981
  if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
1931
1982
  if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
1932
1983
  if (typeof obj.ignoreSaltReading !== 'undefined') chlor.ignoreSaltReading = utils.makeBool(obj.ignoreSaltReading);
@@ -1988,7 +2039,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
1988
2039
  let id = parseInt(obj.id, 10);
1989
2040
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
1990
2041
  let chlor = sys.chlorinators.getItemById(id);
1991
- if (chlor.master === 1) return await super.deleteChlorAsync(obj);
2042
+ if (chlor.master >= 1) return await super.deleteChlorAsync(obj);
1992
2043
  if (sl.enabled) {
1993
2044
  await sl.chlor.setChlorEnabledAsync(false);
1994
2045
  }
@@ -2026,11 +2077,25 @@ class TouchPumpCommands extends PumpCommands {
2026
2077
  //}
2027
2078
  // RKS: 05-20-22 This was moved out of systemBoard it does not belong there and probably should not
2028
2079
  // be called in any current form since it was not being called as part of a message result.
2029
- private setType(pump: Pump, pumpType: number) {
2080
+ private async setType(pump: Pump, pumpType: number) {
2030
2081
  // if we are changing pump types, need to clear out circuits
2031
2082
  // and props that aren't for this pump type
2032
2083
  let _id = pump.id;
2033
2084
  if (pump.type !== pumpType || pumpType === 0) {
2085
+ if (pump.type === 65) { // This used to be a two-speed pump. We need to remove the high speed circuits.
2086
+ let outc = Outbound.create({
2087
+ action: 158,
2088
+ retries: 2,
2089
+ response: Response.create({ action: 1, payload: [158] })
2090
+ });
2091
+ outc.appendPayloadBytes(0, 16);
2092
+ if (sys.controllerType !== ControllerType.IntelliTouch) {
2093
+ outc.setPayloadByte(4, 1);
2094
+ outc.setPayloadByte(5, 72);
2095
+ outc.setPayloadByte(13, 2);
2096
+ }
2097
+ await outc.sendAsync();
2098
+ }
2034
2099
  let _p = pump.get(true);
2035
2100
  sys.pumps.removeItemById(_id);
2036
2101
  pump = sys.pumps.getItemById(_id, true);
@@ -2054,7 +2119,6 @@ class TouchPumpCommands extends PumpCommands {
2054
2119
  spump.emitData('pumpExt', spump.getExtended());
2055
2120
  }
2056
2121
  }
2057
-
2058
2122
  public async setPumpAsync(data: any, send: boolean = true): Promise<Pump> {
2059
2123
  // Rules regarding Pumps in *Touch
2060
2124
  // In *Touch there are basically three classifications of pumps. These include those under control of RS485, Dual Speed, and Single Speed.
@@ -2083,28 +2147,26 @@ class TouchPumpCommands extends PumpCommands {
2083
2147
  if (type.equipmentMaster > 0 || data.master > 0) return await super.setPumpAsync(data);
2084
2148
  data.master = 0;
2085
2149
  if (typeof data.type === 'undefined' || isNaN(ntype) || typeof type.name === 'undefined') return Promise.reject(new InvalidEquipmentDataError('You must supply a pump type when creating a new pump', 'Pump', data));
2150
+ // Do not rely on the address for the id. This will not work overall since more than
2151
+ // one pump type that exists in the ET and IT realm.
2152
+ id = sys.pumps.getNextEquipmentId(new EquipmentIdRange(1, 255));
2086
2153
  if (type.name === 'ds') {
2087
- id = 9;
2088
2154
  if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
2089
2155
  }
2090
2156
  else if (type.name === 'ss') {
2091
- id = 10;
2092
2157
  if (sys.pumps.find(elem => elem.type === ntype)) return Promise.reject(new InvalidEquipmentDataError(`You may add only one ${type.desc} pump`, 'Pump', data));
2093
2158
  }
2094
- else if (type.name === 'none') return Promise.reject(new InvalidEquipmentDataError('You must supply a valid id when removing a pump.', 'Pump', data));
2159
+ else if (type.name === 'none') // This is a mis-guided relic. None should simply not exist.
2160
+ return Promise.reject(new InvalidEquipmentDataError('You must supply a valid type when adding a pump.', 'Pump', data));
2095
2161
  else {
2096
- // Under most circumstances the id will = the address minus 95.
2097
2162
  if (typeof data.address !== 'undefined') {
2098
2163
  data.address = parseInt(data.address, 10);
2099
2164
  if (isNaN(data.address)) return Promise.reject(new InvalidEquipmentDataError(`You must supply a valid pump address to add a ${type.desc} pump.`, 'Pump', data));
2100
- id = data.address - 95;
2101
2165
  // Make sure it doesn't already exist.
2102
2166
  if (sys.pumps.find(elem => elem.address === data.address)) return Promise.reject(new InvalidEquipmentDataError(`A pump already exists at address ${data.address - 95}`, 'Pump', data));
2103
2167
  }
2104
2168
  else {
2105
2169
  if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`You may not add another ${type.desc} pump. Max number of pumps exceeded.`, 'Pump', data));
2106
- id = sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps);
2107
- data.address = id + 95;
2108
2170
  }
2109
2171
  }
2110
2172
  isAdd = true;
@@ -2112,24 +2174,42 @@ class TouchPumpCommands extends PumpCommands {
2112
2174
  }
2113
2175
  else {
2114
2176
  pump = sys.pumps.getItemById(id, false);
2115
- if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
2116
2177
  data.master = 0;
2117
2178
  ntype = typeof data.type === 'undefined' ? pump.type : parseInt(data.type, 10);
2118
2179
  if (isNaN(ntype)) return Promise.reject(new InvalidEquipmentDataError(`Pump type ${data.type} is not valid`, 'Pump', data));
2119
2180
  type = sys.board.valueMaps.pumpTypes.transform(ntype);
2181
+ data.master = type.name in ['ss', 'hwvs', 'hwrly', 'sf'] ? 1 : 0;
2182
+ if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
2183
+
2120
2184
  // changing type? clear out all props and add as new
2121
2185
  if (ntype !== pump.type) {
2122
2186
  isAdd = true;
2123
- this.setType(pump, ntype);
2187
+ await this.setType(pump, ntype);
2124
2188
  pump = sys.pumps.getItemById(id, false); // refetch pump with new value
2125
2189
  }
2126
2190
  }
2127
2191
  // Validate all the ids since in *Touch the address is determined from the id.
2192
+ // RKS: 05-07-23 - The pump address is no longer determined by the id. This was such a twisted effort to try to match these to the id.
2193
+ // while the order of the pump coming out of the config messages determine the address the id is not relevant since *Touch panels should simply
2194
+ // search for the address using byte index + 96. All of this was becoming too much.
2128
2195
  if (!isAdd) isAdd = sys.pumps.find(elem => elem.id === id) === undefined;
2196
+ if (type.name === 'ss') {
2197
+ data.master = 1; // All single speed pumps are Nixie controlled.
2198
+ return await super.setPumpAsync(data);
2199
+ }
2200
+
2201
+ if (isAdd) {
2202
+ // The ids for dual speed and single speed pumps have been mixed up in the past. Originally, the code relied on ss id === 10 and ds === 9. However, when the screenLogic
2203
+ // code was added these got switched and essentially invalidated all ds and ss pumps. The way this works now is that the single ds pump for ET and IT will be determined by
2204
+ // type and the id will not matter.
2205
+ data.master = 0;
2206
+ if (type.name === 'ds' && typeof sys.pumps.find(x => x.type === 65) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Only one Two-speed pump may be added`, 'Pump', data));
2207
+ else if (sys.pumps.count(x => x.type in [1, 64, 128, 169]) >= sys.equipment.maxPumps) return Promise.reject(new InvalidEquipmentDataError(`The maximum number of variable pumps have been exceeded for this panel. ${type.desc}`, 'Pump', data));
2208
+ }
2129
2209
  // Now lets validate the ids related to the type.
2130
- if (id === 9 && type.name !== 'ds') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 9`, 'Pump', data));
2131
- else if (id === 10 && type.name !== 'ss') return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} pump must be 10`, 'Pump', data));
2132
- else if (id > sys.equipment.maxPumps) return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} must be less than ${sys.equipment.maxPumps}`, 'Pump', data));
2210
+ //if (id === 10 && type.name !== 'ds') return Promise.reject(new InvalidEquipmentDataError(`The id for a Two-speed pump must be 9`, 'Pump', data));
2211
+ //else if (id === 9 && type.name !== 'ss') return Promise.reject(new InvalidEquipmentDataError(`The id for a Single-Speed pump must be 10`, 'Pump', data));
2212
+ //else if (id > sys.equipment.maxPumps) return Promise.reject(new InvalidEquipmentDataError(`The id for a ${type.desc} must be less than ${sys.equipment.maxPumps}`, 'Pump', data));
2133
2213
 
2134
2214
  // Need to do a check here if we are clearing out the circuits; id data.circuits === []
2135
2215
  // extend will keep the original array
@@ -2180,34 +2260,60 @@ class TouchPumpCommands extends PumpCommands {
2180
2260
  // We will not be sending message for ss type pumps.
2181
2261
  if (type.name === 'ss') {
2182
2262
  // The OCP doesn't deal with single speed pumps. Simply add it to the config.
2183
- data.circuits = [];
2184
- pump.set(pump);
2263
+ pump.circuits.clear();
2185
2264
  let spump = state.pumps.getItemById(id, true);
2186
2265
  for (let prop in spump) {
2187
2266
  if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
2188
2267
  }
2189
- data.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpSSModels.encode(data.model) : pump.model || 0;
2268
+ spump.type = pump.type = type.val;
2269
+ pump.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpSSModels.encode(data.model) : pump.model || 0;
2190
2270
  spump.emitEquipmentChange();
2191
- return Promise.resolve(pump);
2271
+
2272
+ return pump;
2192
2273
  }
2193
2274
  else if (type.name === 'ds') {
2194
2275
  // We are going to set all the high speed circuits.
2195
2276
  // RSG: TODO I don't know what the message is to set the high speed circuits. The following should
2196
2277
  // be moved into the onComplete for the outbound message to set high speed circuits.
2197
- data.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpDSModels.encode(data.model) : pump.model || 0;
2198
- for (let prop in pump) {
2199
- if (typeof data[prop] !== 'undefined') pump[prop] = data[prop];
2278
+ // RKS: 05-06-23 - Send the message for high speed circuits.
2279
+ let outc = Outbound.create({
2280
+ action: 158,
2281
+ retries: 2,
2282
+ response: Response.create({ action: 1, payload: [158] })
2283
+ });
2284
+ outc.appendPayloadBytes(0, 16);
2285
+ if (sys.controllerType !== ControllerType.IntelliTouch) {
2286
+ outc.setPayloadByte(4, 1);
2287
+ outc.setPayloadByte(5, 72);
2288
+ outc.setPayloadByte(13, 2);
2200
2289
  }
2201
- let spump = state.pumps.getItemById(id, true);
2202
- for (let prop in spump) {
2203
- if (typeof data[prop] !== 'undefined') spump[prop] = data[prop];
2290
+ let maxCircuits = typeof data.circuits !== 'undefined' ? Math.min(data.circuits.length, sys.controllerType === ControllerType.IntelliTouch ? 8 : 4) : 0;
2291
+ for (let i = 0; i < maxCircuits; i++) {
2292
+ outc.setPayloadByte(i, data.circuits[i].circuit, 0);
2204
2293
  }
2205
- spump.emitEquipmentChange();
2206
- return Promise.resolve(pump);
2294
+ // So lets send the message for setting the high speed circuits.
2295
+ if (await outc.sendAsync()) {
2296
+ let spump = state.pumps.getItemById(id, true);
2297
+ pump.model = typeof data.model === 'undefined' ? sys.board.valueMaps.pumpDSModels.encode(data.model) : pump.model || 0;
2298
+ let circs = [];
2299
+ // Lets do this to rearrange cases where 0s are sent.
2300
+ for (let i = 0; i < maxCircuits; i++) {
2301
+ if (data.circuits[i].circuit > 0) {
2302
+ circs.push(data.circuits[i].circuit);
2303
+ }
2304
+ }
2305
+ pump.circuits.clear();
2306
+ for (let i = 0; i < circs.length; i++) {
2307
+ pump.circuits.getItemById(i + 1, true, { id: i + 1, circuit: circs[i] });
2308
+ }
2309
+ spump.emitEquipmentChange();
2310
+ }
2311
+ return pump;
2207
2312
  }
2208
2313
  else {
2209
2314
  let arrCircuits = [];
2210
- data.address = id + 95;
2315
+ // Look to see if there is another pump at the same address.
2316
+ if (typeof sys.pumps.find(x => x.id !== id && x.address === data.address) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`A pump already exists at address ${data.address - 95}`, 'Pump', data));
2211
2317
  if (isVersion1) {
2212
2318
  if (data.address !== 96) return Promise.reject(new InvalidEquipmentDataError(`EasyTouch Version 1 controllers only support VS pumps at the first address`, 'Pump', data));
2213
2319
  if (type.name !== 'vs') return Promise.reject(new InvalidEquipmentDataError(`EasyTouch Version 1 controllers only support VS pump types. ${type.desc} pumps are not supported`, 'Pump', data));
@@ -2250,7 +2356,7 @@ class TouchPumpCommands extends PumpCommands {
2250
2356
  data.circuits = arrCircuits;
2251
2357
 
2252
2358
  if (send) {
2253
- return new Promise<Pump>(async (resolve, reject) => {
2359
+ return await new Promise<Pump>(async (resolve, reject) => {
2254
2360
  outc.onComplete = (err, msg) => {
2255
2361
  if (err) reject(err);
2256
2362
  else {
@@ -2268,10 +2374,12 @@ class TouchPumpCommands extends PumpCommands {
2268
2374
  retries: 2,
2269
2375
  response: true
2270
2376
  });
2271
- conn.queueSendMessage(pumpConfigRequest);
2377
+ (async () => { pumpConfigRequest.sendAsync(); })();
2378
+ //conn.queueSendMessage(pumpConfigRequest);
2272
2379
  }
2273
2380
  };
2274
2381
  await outc.sendAsync();
2382
+ return pump;
2275
2383
  });
2276
2384
  }
2277
2385
  }
@@ -2428,32 +2536,26 @@ class TouchPumpCommands extends PumpCommands {
2428
2536
  return Promise.resolve(pump);
2429
2537
  }
2430
2538
  else if (send) {
2431
- return new Promise<Pump>(async (resolve, reject) => {
2432
- outc.onComplete = (err, msg) => {
2433
- if (err) reject(err);
2434
- else {
2435
- pump = sys.pumps.getItemById(id, true);
2436
- // RKS: 05-20-22 Boooh to this if the payload does not include its
2437
- // circuits we have just destroyed the pump definition. So I added code to
2438
- // make sure that the data is complete.
2439
- pump.set(data); // Sets all the data back to the pump.
2440
- let spump = state.pumps.getItemById(id, true);
2441
- spump.isActive = pump.isActive = true;
2442
- spump.name = pump.name;
2443
- spump.type = pump.type;
2444
- spump.emitEquipmentChange();
2445
- resolve(pump);
2446
- const pumpConfigRequest = Outbound.create({
2447
- action: 216,
2448
- payload: [pump.id],
2449
- retries: 2,
2450
- response: true
2451
- });
2452
- conn.queueSendMessage(pumpConfigRequest);
2453
- }
2454
- };
2455
- await outc.sendAsync();
2456
- });
2539
+ if (await outc.sendAsync()) {
2540
+ pump = sys.pumps.getItemById(id, true);
2541
+ // RKS: 05-20-22 Boooh to this if the payload does not include its
2542
+ // circuits we have just destroyed the pump definition. So I added code to
2543
+ // make sure that the data is complete.
2544
+ pump.set(data); // Sets all the data back to the pump.
2545
+ let spump = state.pumps.getItemById(id, true);
2546
+ spump.isActive = pump.isActive = true;
2547
+ spump.name = pump.name;
2548
+ spump.type = pump.type;
2549
+ spump.emitEquipmentChange();
2550
+ const pumpConfigRequest = Outbound.create({
2551
+ action: 216,
2552
+ payload: [pump.id],
2553
+ retries: 2,
2554
+ response: true
2555
+ });
2556
+ await pumpConfigRequest.sendAsync();
2557
+ return pump;
2558
+ }
2457
2559
  }
2458
2560
  }
2459
2561
  }
@@ -2548,30 +2650,50 @@ class TouchPumpCommands extends PumpCommands {
2548
2650
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`deletePumpAsync: Pump ${id} is not valid.`, 0, `pump`));
2549
2651
  let pump = sys.pumps.getItemById(id, false);
2550
2652
  if (pump.master === 1) return super.deletePumpAsync(data);
2551
- const outc = Outbound.create({
2552
- action: 155,
2553
- payload: [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2554
- retries: 2,
2555
- response: true
2556
- });
2557
- return new Promise<Pump>(async (resolve, reject) => {
2558
- outc.onComplete = (err, msg) => {
2559
- if (err) reject(err);
2560
- else {
2561
- sys.pumps.removeItemById(id);
2562
- state.pumps.removeItemById(id);
2563
- resolve(sys.pumps.getItemById(id, false));
2564
- const pumpConfigRequest = Outbound.create({
2565
- action: 216,
2566
- payload: [id],
2567
- retries: 2,
2568
- response: true
2569
- });
2570
- conn.queueSendMessage(pumpConfigRequest);
2571
- }
2572
- };
2653
+ else if (pump.type === 65) { // Dual speed pump.
2654
+ let outc = Outbound.create({
2655
+ action: 158,
2656
+ retries: 2,
2657
+ response: Response.create({ action: 1, payload: [158] })
2658
+ });
2659
+ outc.appendPayloadBytes(0, 16);
2660
+ if (sys.controllerType !== ControllerType.IntelliTouch) {
2661
+ outc.setPayloadByte(4, 1);
2662
+ outc.setPayloadByte(5, 72);
2663
+ outc.setPayloadByte(13, 2);
2664
+ }
2573
2665
  await outc.sendAsync();
2574
- });
2666
+ pump.isActive = false;
2667
+ sys.pumps.removeItemById(id);
2668
+ state.pumps.removeItemById(id);
2669
+ return pump;
2670
+ }
2671
+ else {
2672
+ const outc = Outbound.create({
2673
+ action: 155,
2674
+ payload: [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
2675
+ retries: 2,
2676
+ response: true
2677
+ });
2678
+ return new Promise<Pump>(async (resolve, reject) => {
2679
+ outc.onComplete = (err, msg) => {
2680
+ if (err) reject(err);
2681
+ else {
2682
+ sys.pumps.removeItemById(id);
2683
+ state.pumps.removeItemById(id);
2684
+ resolve(sys.pumps.getItemById(id, false));
2685
+ const pumpConfigRequest = Outbound.create({
2686
+ action: 216,
2687
+ payload: [id],
2688
+ retries: 2,
2689
+ response: true
2690
+ });
2691
+ (async () => { await pumpConfigRequest.sendAsync(); });
2692
+ }
2693
+ };
2694
+ await outc.sendAsync();
2695
+ });
2696
+ }
2575
2697
  }
2576
2698
  }
2577
2699
  class TouchHeaterCommands extends HeaterCommands {
@@ -2807,15 +2929,15 @@ class TouchHeaterCommands extends HeaterCommands {
2807
2929
  // 2 = Gas Heater
2808
2930
  // 3 = Hybrid
2809
2931
  // 16 = Dual
2810
- sys.board.valueMaps.heatModes.set(1, { name: 'heatpump', desc: 'Heat Pump' });
2811
- sys.board.valueMaps.heatModes.set(2, { name: 'heater', desc: 'Gas Heat' });
2812
- sys.board.valueMaps.heatModes.set(3, { name: 'heatpumppref', desc: 'Hybrid' });
2813
- sys.board.valueMaps.heatModes.set(16, { name: 'dual', desc: 'Dual Heat' });
2932
+ sys.board.valueMaps.heatModes.set(1, { name: 'hybheatpump', desc: 'Heat Pump' });
2933
+ sys.board.valueMaps.heatModes.set(2, { name: 'hybheat', desc: 'Gas Heat' });
2934
+ sys.board.valueMaps.heatModes.set(3, { name: 'hybhybrid', desc: 'Hybrid' });
2935
+ sys.board.valueMaps.heatModes.set(16, { name: 'hybdual', desc: 'Dual Heat' });
2814
2936
 
2815
- sys.board.valueMaps.heatSources.set(2, { name: 'heater', desc: 'Gas Heat' });
2816
- sys.board.valueMaps.heatSources.set(5, { name: 'heatpumppref', desc: 'Hybrid' });
2817
- sys.board.valueMaps.heatSources.set(20, { name: 'dual', desc: 'Dual Heat' });
2818
- sys.board.valueMaps.heatSources.set(21, { name: 'heatpump', desc: 'Heat Pump' });
2937
+ sys.board.valueMaps.heatSources.set(2, { name: 'hybheat', desc: 'Gas Heat' });
2938
+ sys.board.valueMaps.heatSources.set(5, { name: 'hybhybrid', desc: 'Hybrid' });
2939
+ sys.board.valueMaps.heatSources.set(20, { name: 'hybdual', desc: 'Dual Heat' });
2940
+ sys.board.valueMaps.heatSources.set(21, { name: 'hybheatpump', desc: 'Heat Pump' });
2819
2941
  }
2820
2942
  else {
2821
2943
  if (gasHeaterInstalled) {
@@ -2863,6 +2985,8 @@ class TouchHeaterCommands extends HeaterCommands {
2863
2985
  let body = sys.bodies.getItemByIndex(i);
2864
2986
  let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
2865
2987
  let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
2988
+ btemp.data.heatMode = sys.board.valueMaps.heatModes.transform(btemp.heatMode);
2989
+ btemp.data.heatStatus = sys.board.valueMaps.heatStatus.transform(btemp.heatStatus);
2866
2990
  btemp.heaterOptions = opts;
2867
2991
  }
2868
2992
  this.setActiveTempSensors();