nodejs-poolcontroller 8.0.2 → 8.0.5

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.
@@ -4,30 +4,37 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - master
7
- - docker
7
+ tags:
8
+ - "v*.*.*"
8
9
  workflow_dispatch:
9
10
 
10
11
  jobs:
11
12
  build-and-push:
12
13
  runs-on: ubuntu-latest
13
14
  steps:
14
- - name: Checkout code
15
- uses: actions/checkout@v3
16
-
17
- - name: Checkout code from tagyoureit/nodejs-poolcontroller
18
- uses: actions/checkout@v3
19
- with:
20
- repository: tagyoureit/nodejs-poolcontroller
21
- ref: master # or specify the branch or tag to pull from
22
- path: nodejs-poolcontroller
23
-
24
- - name: Checkout code from rstrouse/nodejs-poolcontroller-dashpanel
25
- uses: actions/checkout@v3
15
+ - name: Docker meta
16
+ id: meta
17
+ uses: docker/metadata-action@v5
26
18
  with:
27
- repository: rstrouse/nodejs-poolcontroller-dashpanel #
28
- ref: master # Specify the branch or tag to pull from
29
- path: nodejs-poolcontroller-dashpanel # Specify the directory to checkout the code into
30
-
19
+ # list of Docker images to use as base name for tags
20
+ images: |
21
+ tagyoureit/njspc
22
+ # generate Docker tags based on the following events/attributes
23
+ tags: |
24
+ type=schedule
25
+ type=ref,event=branch
26
+ type=ref,event=pr
27
+ type=semver,pattern={{version}}
28
+ type=semver,pattern={{major}}.{{minor}}
29
+ type=semver,pattern={{major}}
30
+ type=sha
31
+
32
+ - name: Set up QEMU
33
+ uses: docker/setup-qemu-action@v3
34
+
35
+ - name: Set up Docker Buildx
36
+ uses: docker/setup-buildx-action@v3
37
+
31
38
  - name: Login to Docker Hub
32
39
  uses: docker/login-action@v3
33
40
  with:
@@ -35,47 +42,9 @@ jobs:
35
42
  password: ${{ secrets.DOCKERHUB_TOKEN }}
36
43
 
37
44
  - name: Build and push combined Docker image
38
- uses: docker/build-push-action@v4
45
+ uses: docker/build-push-action@v6
39
46
  with:
40
- context: .
41
- file: ./.docker/Dockerfile.linux
42
47
  push: true
43
- tags: |
44
- tagyoureit/njspc-dp-combined-app:latest
45
-
46
-
47
- # - name: Build and push njsPC Linux Docker image (x86_64)
48
- # uses: docker/build-push-action@v4
49
- # with:
50
- # context: .
51
- # file: ./.docker/Dockerfile.linux
52
- # push: true
53
- # tags: |
54
- # tagyoureit/nodejs-poolcontroller:latest
55
-
56
- # - name: Build and push njsPC-dP Linux Docker image (x86_64)
57
- # uses: docker/build-push-action@v4
58
- # with:
59
- # context: nodejs-poolcontroller-dashpanel
60
- # file: ./.docker/Dockerfile.linux
61
- # push: true
62
- # tags: |
63
- # rstrouse/nodejs-poolcontroller-dashpanel:latest
64
-
65
- # - name: Build and push ARMv7 Docker image
66
- # uses: docker/build-push-action@v4
67
- # with:
68
- # context: .
69
- # file: ./.docker/Dockerfile.armv7 # Adjust the path to your ARMv7 Dockerfile
70
- # push: true
71
- # tags: |
72
- # tagyoureit/nodejs-poolcontroller-armv7:latest
73
-
74
- # - name: Build and push ARMv6 Docker image
75
- # uses: docker/build-push-action@v4
76
- # with:
77
- # context: .
78
- # file: ./.docker/Dockerfile.armv6 # Adjust the path to your ARMv6 Dockerfile
79
- # push: true
80
- # tags: |
81
- # tagyoureit/nodejs-poolcontroller-armv6:latest
48
+ platforms: linux/amd64,linux/arm64,linux/arm/v7
49
+ tags: ${{ steps.meta.outputs.tags }}
50
+ labels: ${{ steps.meta.outputs.labels }}
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM node:lts-alpine AS build
1
+ FROM node:18-alpine AS build
2
2
  RUN apk add --no-cache make gcc g++ python3 linux-headers udev tzdata
3
3
  WORKDIR /app
4
4
  COPY package*.json ./
@@ -8,11 +8,12 @@ COPY . .
8
8
  RUN npm run build
9
9
  RUN npm ci --omit=dev
10
10
 
11
- FROM node:lts-alpine as prod
11
+ FROM node:18-alpine as prod
12
12
  RUN apk add git
13
13
  RUN mkdir /app && chown node:node /app
14
14
  WORKDIR /app
15
15
  COPY --chown=node:node --from=build /app .
16
16
  USER node
17
17
  ENV NODE_ENV=production
18
+ EXPOSE 5150
18
19
  ENTRYPOINT ["node", "dist/app.js"]
@@ -1089,7 +1089,9 @@ export class ScheduleStateCollection extends EqStateCollection<ScheduleState> {
1089
1089
  let ssched = this.getItemByIndex(i);
1090
1090
  let st = ssched.scheduleTime;
1091
1091
  let sched = sys.schedules.getItemById(ssched.id);
1092
- if (!sched.isActive || ssched.disabled) {
1092
+ // rsg st.startTime is null when the schedule has No Days <-- WRONG. ssched.scheduleDays should be checked.
1093
+ // original fix #879; updated fix #1033
1094
+ if (!sched.isActive || ssched.disabled || ssched.scheduleDays === 0) {
1093
1095
  continue;
1094
1096
  }
1095
1097
  st.calcSchedule(state.time, sys.schedules.getItemById(ssched.id));
@@ -1837,6 +1839,7 @@ export class HeaterState extends EqState {
1837
1839
  public set prevHeaterOffTemp(val: number) {
1838
1840
  if (this.prevHeaterOffTemp !== val) {
1839
1841
  this.data.prevHeaterOffTemp = val;
1842
+ if (typeof val === 'undefined') delete this.data.prevHeaterOffTemp;
1840
1843
  }
1841
1844
  }
1842
1845
  public get startupDelay(): boolean { return this.data.startupDelay; }
@@ -2173,6 +2173,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2173
2173
  255, 255, 0, 0, 0, 0], // 30-35
2174
2174
  3);
2175
2175
 
2176
+
2176
2177
  // Circuits are always contiguous so we don't have to worry about
2177
2178
  // them having a strange offset like features and groups. However, in
2178
2179
  // single body systems they start with 2.
@@ -2180,13 +2181,14 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2180
2181
  // We are using the index and setting the circuits based upon
2181
2182
  // the index. This way it doesn't matter what the sort happens to
2182
2183
  // be and whether there are gaps in the ids or not. The ordinal is the bit number.
2183
- let circuit = state.circuits.getItemByIndex(i);
2184
- let ordinal = circuit.id - 1;
2184
+ let cstate = state.circuits.getItemByIndex(i);
2185
+ let ordinal = cstate.id - 1;
2186
+ if (ordinal >= 40) continue;
2185
2187
  let ndx = Math.floor(ordinal / 8);
2186
2188
  let byte = out.payload[ndx + 3];
2187
2189
  let bit = ordinal - (ndx * 8);
2188
- if (circuit.id === id) byte = isOn ? byte = byte | (1 << bit) : byte;
2189
- else if (circuit.isOn) byte = byte | (1 << bit);
2190
+ if (cstate.id === id) byte = isOn ? byte = byte | (1 << bit) : byte;
2191
+ else if (cstate.isOn) byte = byte | (1 << bit);
2190
2192
  out.payload[ndx + 3] = byte;
2191
2193
  }
2192
2194
  // Set the bits for the features.
@@ -2196,6 +2198,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2196
2198
  // be and whether there are gaps in the ids or not. The ordinal is the bit number.
2197
2199
  let feature = state.features.getItemByIndex(i);
2198
2200
  let ordinal = feature.id - sys.board.equipmentIds.features.start;
2201
+ if (ordinal >= 32) continue;
2199
2202
  let ndx = Math.floor(ordinal / 8);
2200
2203
  let byte = out.payload[ndx + 9];
2201
2204
  let bit = ordinal - (ndx * 8);
@@ -2207,6 +2210,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2207
2210
  for (let i = 0; i < state.data.circuitGroups.length; i++) {
2208
2211
  let group = state.circuitGroups.getItemByIndex(i);
2209
2212
  let ordinal = group.id - sys.board.equipmentIds.circuitGroups.start;
2213
+ if (ordinal >= 16) continue;
2210
2214
  let ndx = Math.floor(ordinal / 8);
2211
2215
  let byte = out.payload[ndx + 13];
2212
2216
  let bit = ordinal - (ndx * 8);
@@ -2218,6 +2222,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2218
2222
  for (let i = 0; i < state.data.lightGroups.length; i++) {
2219
2223
  let group = state.lightGroups.getItemByIndex(i);
2220
2224
  let ordinal = group.id - sys.board.equipmentIds.circuitGroups.start;
2225
+ if (ordinal >= 16) continue;
2221
2226
  let ndx = Math.floor(ordinal / 8);
2222
2227
  let byte = out.payload[ndx + 13];
2223
2228
  let bit = ordinal - (ndx * 8);
@@ -2253,6 +2258,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2253
2258
  for (let i = 0; i < state.data.schedules.length; i++) {
2254
2259
  let sched = state.schedules.getItemByIndex(i);
2255
2260
  let ordinal = sched.id - 1;
2261
+ if (ordinal >= 100) continue;
2256
2262
  let ndx = Math.floor(ordinal / 8);
2257
2263
  let byte = out.payload[ndx + 15];
2258
2264
  let bit = ordinal - (ndx * 8);
@@ -2822,6 +2828,7 @@ class IntelliCenterBodyCommands extends BodyCommands {
2822
2828
  body2: { heatMode: number, heatSetpoint: number, coolSetpoint: number }
2823
2829
  };
2824
2830
  private async queueBodyHeatSettings(bodyId?: number, byte?: number, data?: any): Promise<Boolean> {
2831
+ logger.debug(`queueBodyHeatSettings: ${JSON.stringify(this.bodyHeatSettings)}`); // remove this line if #848 is fixed
2825
2832
  if (typeof this.bodyHeatSettings === 'undefined') {
2826
2833
  let body1 = sys.bodies.getItemById(1);
2827
2834
  let body2 = sys.bodies.getItemById(2);
@@ -2884,10 +2891,12 @@ class IntelliCenterBodyCommands extends BodyCommands {
2884
2891
  state.emitEquipmentChanges();
2885
2892
  } catch (err) {
2886
2893
  bhs.processing = false;
2894
+ bhs.bytes = [];
2887
2895
  throw (err);
2888
2896
  }
2889
2897
  finally {
2890
2898
  bhs.processing = false;
2899
+ bhs.bytes = [];
2891
2900
  }
2892
2901
  return true;
2893
2902
  }
@@ -2897,7 +2906,10 @@ class IntelliCenterBodyCommands extends BodyCommands {
2897
2906
  setTimeout(async () => {
2898
2907
  try {
2899
2908
  await this.queueBodyHeatSettings();
2900
- } catch (err) { logger.error(`Error sending queued body setpoint message: ${err.message}`); }
2909
+ } catch (err) {
2910
+ logger.error(`Error sending queued body setpoint message: ${err.message}`);
2911
+ throw (err);
2912
+ }
2901
2913
  }, 3000);
2902
2914
  }
2903
2915
  else bhs.processing = false;
@@ -1762,6 +1762,7 @@ export class BodyCommands extends BoardCommands {
1762
1762
  let bdy = sys.bodies.getItemById(body.id);
1763
1763
  let bstate = state.temps.bodies.getItemById(body.id);
1764
1764
  bdy.heatMode = bstate.heatMode = mode;
1765
+ sys.board.heaters.clearPrevHeaterOffTemp();
1765
1766
  sys.board.heaters.syncHeaterStates();
1766
1767
  state.emitEquipmentChanges();
1767
1768
  return Promise.resolve(bstate);
@@ -1770,6 +1771,7 @@ export class BodyCommands extends BoardCommands {
1770
1771
  let bdy = sys.bodies.getItemById(body.id);
1771
1772
  let bstate = state.temps.bodies.getItemById(body.id);
1772
1773
  bdy.setPoint = bstate.setPoint = setPoint;
1774
+ sys.board.heaters.clearPrevHeaterOffTemp();
1773
1775
  state.emitEquipmentChanges();
1774
1776
  sys.board.heaters.syncHeaterStates();
1775
1777
  return Promise.resolve(bstate);
@@ -3629,7 +3631,7 @@ export class ScheduleCommands extends BoardCommands {
3629
3631
  if (heatSetpoint < 0 || heatSetpoint > 104) return Promise.reject(new InvalidEquipmentDataError(`Invalid heat setpoint: ${heatSetpoint}`, 'Schedule', heatSetpoint));
3630
3632
  if (sys.board.circuits.getCircuitReferences(true, true, false, true).find(elem => elem.id === circuit) === undefined)
3631
3633
  return Promise.reject(new InvalidEquipmentDataError(`Invalid circuit reference: ${circuit}`, 'Schedule', circuit));
3632
- if (schedType === 128 && schedDays === 0) return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule days: ${schedDays}. You must supply days that the schedule is to run.`, 'Schedule', schedDays));
3634
+ if (schedType === 128 && schedDays === 0) return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule days: ${schedDays}. You must supply days that the schedule is to run.`, 'Schedule', schedDays)); // rsg 2024.11.22 - some controllers allow no days.
3633
3635
 
3634
3636
  // If we made it to here we are valid and the schedula and it state should exist.
3635
3637
  sched = sys.schedules.getItemById(id, true);
@@ -4610,7 +4612,8 @@ export class ValveCommands extends BoardCommands {
4610
4612
  let drain = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' ||
4611
4613
  typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' : false;
4612
4614
  // Check to see if there is a spillway circuit or feature on. If it is on then the return will be diverted no mater what.
4613
- let spillway = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' ||
4615
+ let spillway = sys.equipment.shared ?
4616
+ typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' ||
4614
4617
  typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' : false;
4615
4618
  let spa = sys.equipment.shared ? state.circuits.getItemById(1).isOn : false;
4616
4619
  let pool = sys.equipment.shared ? state.circuits.getItemById(6).isOn : false;
@@ -668,6 +668,9 @@ export class RS485Port {
668
668
  let opts: SerialPortOpenOptions<AutoDetectTypes> = { path: portPath, autoOpen: false, baudRate: 9600 };
669
669
  sp = new SerialPortMock(opts);
670
670
  }
671
+ else if (this._cfg.type === 'screenlogic') {
672
+ return await sl.openAsync();
673
+ }
671
674
  else {
672
675
  this.mock = false;
673
676
  let opts: SerialPortOpenOptions<AutoDetectTypes> = extend(true, { path: this._cfg.rs485Port }, this._cfg.portSettings);
@@ -1,5 +1,5 @@
1
1
  import { ControllerType, Timestamp, Utils, utils } from '../../controller/Constants';
2
- import { LightGroup, LightGroupCircuit, sys, Valve, Body, Pump, PumpCircuit, Remote} from '../../controller/Equipment';
2
+ import { LightGroup, LightGroupCircuit, sys, Valve, Body, Pump, PumpCircuit, Remote } from '../../controller/Equipment';
3
3
  import { CircuitState, state, ValveState } from '../../controller/State';
4
4
  import { RemoteLogin, UnitConnection, FindUnits, SLEquipmentStateData, SLIntellichlorData, SLPumpStatusData, SLScheduleData, SLSystemTimeData, HeatModes, SLControllerConfigData, SLEquipmentConfigurationData, HeaterConfig, Valves, SLChemData, SLGetCustomNamesData } from 'node-screenlogic';
5
5
  import * as Screenlogic from 'node-screenlogic';
@@ -87,7 +87,7 @@ export class ScreenLogicComms {
87
87
  logger.screenlogic(msg);
88
88
  })
89
89
  let ver = await this._client.getVersionAsync();
90
- logger.info(`Screenlogic: connect to ${systemName} ${ver} at ${unit.ipAddr}:${unit.port}`);
90
+ logger.info(`Screenlogic: connect to ${systemName} ${ver.version} at ${unit.ipAddr}:${unit.port}`);
91
91
 
92
92
  let addClient = await this._client.addClientAsync();
93
93
  logger.silly(`Screenlogic:Add client result: ${addClient}`);
@@ -912,21 +912,21 @@ class Controller {
912
912
  }
913
913
  ], */
914
914
  }
915
- public static decodeHighSpeed(highSpeed: number[]) {
916
- let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
917
- let arrCircuits = [];
918
- let pump = sys.pumps.find(x => { return x.master !== 1 && x.type === 65 });
919
- for (let i = 0; i < maxCircuits && i < highSpeed.length; i++) {
920
- let val = highSpeed[i];
921
- if (val > 0) arrCircuits.push(val);
922
- else if (typeof pump !== 'undefined') pump.circuits.removeItemById(i);
923
- }
924
- if (arrCircuits.length > 0) {
925
- let pump = sys.pumps.getDualSpeed(true);
926
- for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j - 1];
927
- }
928
- else if (typeof pump !== 'undefined') sys.pumps.removeItemById(pump.id);
929
- }
915
+ public static decodeHighSpeed(highSpeed: number[]) {
916
+ let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
917
+ let arrCircuits = [];
918
+ let pump = sys.pumps.find(x => { return x.master !== 1 && x.type === 65 });
919
+ for (let i = 0; i < maxCircuits && i < highSpeed.length; i++) {
920
+ let val = highSpeed[i];
921
+ if (val > 0) arrCircuits.push(val);
922
+ else if (typeof pump !== 'undefined') pump.circuits.removeItemById(i);
923
+ }
924
+ if (arrCircuits.length > 0) {
925
+ let pump = sys.pumps.getDualSpeed(true);
926
+ for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j - 1];
927
+ }
928
+ else if (typeof pump !== 'undefined') sys.pumps.removeItemById(pump.id);
929
+ }
930
930
  public static decodeRemote(remoteDataArray) {
931
931
  if (sys.controllerType === ControllerType.EasyTouch) {
932
932
 
@@ -980,11 +980,11 @@ class Controller {
980
980
  remote6.button3 = remote.button8;
981
981
  remote6.button4 = remote.button9;
982
982
  if (!remote5.button1 && !remote5.button2 && !remote5.button3 && !remote5.button4) remote5.isActive = false;
983
- else remote5.isActive = true;
984
-
983
+ else remote5.isActive = true;
984
+
985
985
  if (!remote6.button1 && !remote6.button2 && !remote6.button3 && !remote6.button4) remote6.isActive = false;
986
- else remote6.isActive = true;
987
-
986
+ else remote6.isActive = true;
987
+
988
988
  }
989
989
  else {
990
990
  remote5.isActive = remote6.isActive = false;
@@ -1066,43 +1066,43 @@ class Controller {
1066
1066
  }
1067
1067
 
1068
1068
  }
1069
- public static async decodePumpAsync(pDataArr: any ){
1070
- pDataArr.forEach(async (pData, idx)=>{
1069
+ public static async decodePumpAsync(pDataArr: any) {
1070
+ pDataArr.forEach(async (pData, idx) => {
1071
1071
  await sys.board.pumps.setPumpAsync(pData, false);
1072
1072
  })
1073
1073
  }
1074
- public static async decodePumpStatusAsync(id: number, slpump: SLPumpStatusData) {
1075
- /* {
1076
- pumpCircuits: [
1077
- { circuitId: 6,speed: 2000,isRPMs: true, },
1078
- { circuitId: 8, speed:2700,isRPMs: true, },
1079
- { circuitId: 2,speed: 2710,isRPMs: true, },
1080
- { circuitId: 2,speed:1000, isRPMs: true,},
1081
- { circuitId: 5,speed:2830, isRPMs: true,},
1082
- { circuitId: 0,speed: 30,isRPMs: false,},
1083
- { circuitId: 0,speed: 30,isRPMs: false,},
1084
- { circuitId: 0,speed: 30,isRPMs: false,},
1085
- ],
1086
- pumpType: 4,
1087
- isRunning: false,
1088
- pumpWatts: 0,
1089
- pumpRPMs: 0,
1090
- pumpUnknown1: 0,
1091
- pumpGPMs: 0,
1092
- pumpUnknown2: 255,
1093
- }
1094
- */
1095
- // RKS: 05-07-23 - This process of getting the pump by its id is flawed. We need to pull this information by its address.
1096
- //let pstate = state.pumps.getItemById(id);
1097
- let pstate = state.pumps.find(x => x.address === 95 + id);
1098
- if (typeof pstate !== 'undefined') {
1099
- pstate.watts = slpump.pumpWatts;
1100
- pstate.rpm = slpump.pumpRPMs;
1101
- pstate.flow = slpump.pumpGPMs === 255 ? 0 : slpump.pumpGPMs;
1102
- pstate.command = (pstate.rpm > 0 || pstate.watts > 0) ? 10 : 0;
1103
- state.emitEquipmentChanges();
1104
- }
1074
+ public static async decodePumpStatusAsync(id: number, slpump: SLPumpStatusData) {
1075
+ /* {
1076
+ pumpCircuits: [
1077
+ { circuitId: 6,speed: 2000,isRPMs: true, },
1078
+ { circuitId: 8, speed:2700,isRPMs: true, },
1079
+ { circuitId: 2,speed: 2710,isRPMs: true, },
1080
+ { circuitId: 2,speed:1000, isRPMs: true,},
1081
+ { circuitId: 5,speed:2830, isRPMs: true,},
1082
+ { circuitId: 0,speed: 30,isRPMs: false,},
1083
+ { circuitId: 0,speed: 30,isRPMs: false,},
1084
+ { circuitId: 0,speed: 30,isRPMs: false,},
1085
+ ],
1086
+ pumpType: 4,
1087
+ isRunning: false,
1088
+ pumpWatts: 0,
1089
+ pumpRPMs: 0,
1090
+ pumpUnknown1: 0,
1091
+ pumpGPMs: 0,
1092
+ pumpUnknown2: 255,
1093
+ }
1094
+ */
1095
+ // RKS: 05-07-23 - This process of getting the pump by its id is flawed. We need to pull this information by its address.
1096
+ //let pstate = state.pumps.getItemById(id);
1097
+ let pstate = state.pumps.find(x => x.address === 95 + id);
1098
+ if (typeof pstate !== 'undefined') {
1099
+ pstate.watts = slpump.pumpWatts;
1100
+ pstate.rpm = slpump.pumpRPMs;
1101
+ pstate.flow = slpump.pumpGPMs === 255 ? 0 : slpump.pumpGPMs;
1102
+ pstate.command = (pstate.rpm > 0 || pstate.watts > 0) ? 10 : 0;
1103
+ state.emitEquipmentChanges();
1105
1104
  }
1105
+ }
1106
1106
  public static async decodeSchedules(slrecurring: SLScheduleData, slrunonce: SLScheduleData) {
1107
1107
  /* reccuring schedules: [{"scheduleId":1,"circuitId":6,"startTime":"1800","stopTime":"0700","dayMask":127,"flags":0,"heatCmd":4,"heatSetPoint":70,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},
1108
1108
 
@@ -1111,18 +1111,17 @@ class Controller {
1111
1111
  Run once schedules: [{"scheduleId":12,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]},{"scheduleId":13,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]}] */
1112
1112
 
1113
1113
  for (let i = 0; i < slrecurring.data.length; i++) {
1114
- let slsched = slrecurring[i];
1115
- let data = {
1116
- id: slsched.scheduleId,
1117
- circuit: slsched.circuitId,
1118
- startTime: Math.floor(slsched.startTime / 100) * 60 + slsched.startTime % 100,
1119
- endTime: Math.floor(slsched.stopTime / 100) * 60 + slsched.stopTime % 100,
1120
- scheduleDays: slsched.dayMask,
1121
- changeHeatSetPoint: slsched.heatCmd > 0,
1122
- heatSetPoint: slsched.heatSetPoint,
1123
- schedType: 128 // recurring
1124
- }
1114
+ let slsched = slrecurring.data[i];
1125
1115
  try {
1116
+ let data = {
1117
+ circuit: slsched.circuitId,
1118
+ startTime: Math.floor(parseInt(slsched.startTime, 10) / 100) * 60 + parseInt(slsched.startTime, 10) % 100,
1119
+ endTime: Math.floor(parseInt(slsched.stopTime, 10) / 100) * 60 + parseInt(slsched.stopTime, 10) % 100,
1120
+ scheduleDays: slsched.dayMask,
1121
+ changeHeatSetPoint: slsched.heatCmd > 0,
1122
+ heatSetPoint: slsched.heatSetPoint,
1123
+ schedType: 128 // recurring
1124
+ }
1126
1125
  await sys.board.schedules.setScheduleAsync(data, false)
1127
1126
  } catch (err) {
1128
1127
  logger.error(`Error setting schedule ${slsched.scheduleId}. ${err.message}`);
@@ -1130,18 +1129,18 @@ class Controller {
1130
1129
  }
1131
1130
  for (let i = 0; i < slrunonce.data.length; i++) {
1132
1131
  let slsched = slrunonce.data[i];
1133
- let data = {
1134
- id: slsched.scheduleId,
1135
- circuit: slsched.circuitId,
1136
- // start and stop come in as military time string
1137
- startTime: parseInt(slsched.startTime, 10),
1138
- endTime: parseInt(slsched.stopTime, 10),
1139
- scheduleDays: slsched.dayMask,
1140
- changeHeatSetPoint: slsched.heatCmd > 0,
1141
- heatSetPoint: slsched.heatSetPoint,
1142
- schedType: 0 // runonce
1143
- }
1144
1132
  try {
1133
+ let data = {
1134
+ id: slsched.scheduleId,
1135
+ circuit: slsched.circuitId,
1136
+ // start and stop come in as military time string
1137
+ startTime: parseInt(slsched.startTime, 10),
1138
+ endTime: parseInt(slsched.stopTime, 10),
1139
+ scheduleDays: slsched.dayMask,
1140
+ changeHeatSetPoint: slsched.heatCmd > 0,
1141
+ heatSetPoint: slsched.heatSetPoint,
1142
+ schedType: 0 // runonce
1143
+ }
1145
1144
  await sys.board.schedules.setScheduleAsync(data, false);
1146
1145
  sys.board.system.setTZ();
1147
1146
  } catch (err) {
@@ -1607,23 +1606,23 @@ export class SLController extends SLCommands {
1607
1606
  const spaCommand: Remote = sys.remotes.getItemById(8).get();
1608
1607
  let alarm = 0;
1609
1608
 
1610
- switch (eq){
1609
+ switch (eq) {
1611
1610
  case 'misc': {
1612
- misc = extend({}, true, misc, obj);
1611
+ misc = extend({}, true, misc, obj);
1613
1612
  break;
1614
1613
  }
1615
1614
  case 'lightGroup': {
1616
- lightGroup = extend({}, true, lightGroup, obj);
1615
+ lightGroup = extend({}, true, lightGroup, obj);
1617
1616
  break;
1618
1617
  }
1619
- case 'pump':{
1620
- let idx = pumps.findIndex(el=>{console.log(el.id);return el.id === obj.id;})
1618
+ case 'pump': {
1619
+ let idx = pumps.findIndex(el => { console.log(el.id); return el.id === obj.id; })
1621
1620
  if (idx >= 0) pumps = extend({}, true, pumps[idx], obj);
1622
1621
  else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
1623
1622
  break;
1624
1623
  }
1625
1624
  case 'heater': {
1626
- let idx = heaters.findIndex(el=>{console.log(el.id);return el.id === obj.id;})
1625
+ let idx = heaters.findIndex(el => { console.log(el.id); return el.id === obj.id; })
1627
1626
  if (idx >= 0) heaters = extend({}, true, heaters[idx], obj);
1628
1627
  else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
1629
1628
  break;
@@ -1646,7 +1645,7 @@ export class SLController extends SLCommands {
1646
1645
  // await this._unit.equipment.setEquipmentConfigurationAsync(data);
1647
1646
  }
1648
1647
 
1649
- public async setSystemTime(){
1648
+ public async setSystemTime() {
1650
1649
  try {
1651
1650
  let sysTime = await this._unit.equipment.setSystemTimeAsync(state.time.toDate(), sys.general.options.adjustDST);
1652
1651
  logger.silly(`Screenlogic:set time result: ${sysTime}`);
@@ -1654,7 +1653,7 @@ export class SLController extends SLCommands {
1654
1653
  return Promise.reject(new InvalidOperationError('Unable to set system time.', error.message));
1655
1654
  }
1656
1655
  }
1657
- public async setCustomName(idx: number, name: string){
1656
+ public async setCustomName(idx: number, name: string) {
1658
1657
  try {
1659
1658
  let ack = await this._unit.equipment.setCustomNameAsync(idx, name);
1660
1659
  logger.silly(`Screenlogic:set custom name result: ${JSON.stringify(ack)}`);
@@ -105,7 +105,7 @@ export class PumpStateMessage {
105
105
  pump.mode = msg.extractPayloadByte(1);
106
106
  pump.driveState = msg.extractPayloadByte(2);
107
107
  pump.watts = (msg.extractPayloadByte(3) * 256) + msg.extractPayloadByte(4);
108
- pump.rpm = (typeof ptype !== 'undefined' && ptype.maxSpeed > 0) ? (msg.extractPayloadByte(5) * 256) + msg.extractPayloadByte(6) : 0;
108
+ pump.rpm = (typeof ptype !== 'undefined' && (ptype.maxSpeed > 0 || ptype.name === 'vf')) ? (msg.extractPayloadByte(5) * 256) + msg.extractPayloadByte(6) : 0;
109
109
  pump.flow = (typeof ptype !== 'undefined' && ptype.maxFlow > 0) ? msg.extractPayloadByte(7) : 0;
110
110
  pump.ppc = msg.extractPayloadByte(8);
111
111
  pump.status = (msg.extractPayloadByte(11) * 256) + msg.extractPayloadByte(12); // 16-bits of error codes.
@@ -158,17 +158,22 @@ export class NixieCircuit extends NixieEquipment {
158
158
  }
159
159
  protected async setIntelliBriteThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
160
160
  let arr = [];
161
- if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
162
161
  let count = typeof theme !== 'undefined' && theme.sequence ? theme.sequence : 0;
163
- if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
162
+
163
+ // Removing this. No need to turn the light off first. We actually need it on to start the sequence for theme setting to work correctly when the light is starting from the off state.
164
+ // if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
165
+
166
+ // Start the sequence of off/on after the light is on.
167
+ arr.push({ isOn: true, timeout: 100 });
164
168
  for (let i = 0; i < count; i++) {
165
- if (i < count - 1) {
166
- arr.push({ isOn: true, timeout: 100 });
167
- arr.push({ isOn: false, timeout: 100 });
168
- }
169
- else arr.push({ isOn: true, timeout: 1000 });
169
+ arr.push({ isOn: false, timeout: 100 });
170
+ arr.push({ isOn: true, timeout: 100 });
170
171
  }
171
- console.log(arr);
172
+ // Ensure light stays on long enough for the theme to stick (required for light group theme setting to function correctly).
173
+ // 2s was too short.
174
+ arr.push({ isOn: true, timeout: 3000 });
175
+
176
+ logger.debug(arr);
172
177
  let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
173
178
  // Even though we ended with on we need to make sure that the relay stays on now that we are done.
174
179
  if (!res.error) {
@@ -771,9 +771,13 @@ export class NixiePumpVF extends NixiePumpRS485 {
771
771
  // When we are 0 then it sends 4[255], 6[4], 5[6]
772
772
  // When we are not 0 then it sends 4[255], 6[10], 5[6], 1[flow]
773
773
  if (!this.closing) await this.setPumpToRemoteControlAsync(); // Action 4
774
- if (!this.closing) await this.setDriveStateAsync(); // Action 6
775
- if (!this.closing) await this.setPumpFeatureAsync(this._targetSpeed > 0 ? 6 : undefined); // Action 5
776
774
  if (!this.closing && this._targetSpeed > 0) await this.setPumpGPMAsync(); // Action 1
775
+ if (!this.closing && this._targetSpeed > 0) await this.setPumpFeatureAsync(6); // Action 5
776
+ // RKS: 07-21-24 - This used to send an empty payload when the pump should be off. For VF pumps it
777
+ // appears that not setting the feature or target flow will set the pump off when it gets to
778
+ // the drive state.
779
+ //if (!this.closing) await this.setPumpFeatureAsync(this._targetSpeed > 0 ? 6 : undefined); // Action 5
780
+ if (!this.closing) await this.setDriveStateAsync(); // Action 6
777
781
  if (!this.closing) await setTimeout(200);
778
782
  if (!this.closing) await this.requestPumpStatusAsync(); // Action 7
779
783
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-poolcontroller",
3
- "version": "8.0.2",
3
+ "version": "8.0.5",
4
4
  "description": "nodejs-poolController",
5
5
  "main": "app.js",
6
6
  "author": {
@@ -1,29 +0,0 @@
1
- # Use a base image compatible with ARMv6 architecture
2
- FROM arm32v6/node:14-alpine
3
-
4
- # Install necessary build dependencies
5
- RUN apk add --no-cache make gcc g++ python3 linux-headers udev tzdata git
6
-
7
- # Set the working directory
8
- WORKDIR /app
9
-
10
- # Copy package.json and package-lock.json
11
- COPY package*.json ./
12
-
13
- # Install dependencies (both development and production)
14
- RUN npm ci
15
-
16
- # Copy the rest of the application
17
- COPY . .
18
-
19
- # Build the application
20
- RUN npm run build
21
-
22
- # Set environment variables
23
- ENV NODE_ENV=production
24
-
25
- # Expose the port (if applicable)
26
- EXPOSE 4200
27
-
28
- # Command to run the application
29
- CMD ["node", "dist/app.js"]
@@ -1,29 +0,0 @@
1
- # Use a base image compatible with ARMv7 architecture
2
- FROM arm32v7/node:14-alpine
3
-
4
- # Install necessary build dependencies
5
- RUN apk add --no-cache make gcc g++ python3 linux-headers udev tzdata git
6
-
7
- # Set the working directory
8
- WORKDIR /app
9
-
10
- # Copy package.json and package-lock.json
11
- COPY package*.json ./
12
-
13
- # Install dependencies (both development and production)
14
- RUN npm ci
15
-
16
- # Copy the rest of the application
17
- COPY . .
18
-
19
- # Build the application
20
- RUN npm run build
21
-
22
- # Set environment variables
23
- ENV NODE_ENV=production
24
-
25
- # Expose the port (if applicable)
26
- EXPOSE 4200
27
-
28
- # Command to run the application
29
- CMD ["node", "dist/app.js"]
@@ -1,62 +0,0 @@
1
- # Use a Linux-based image as the build stage for the first application
2
- FROM node:lts-alpine AS build-njspc
3
-
4
- # Install necessary build dependencies
5
- RUN apk add --no-cache make gcc g++ python3 linux-headers udev tzdata
6
-
7
- # Create the app directory and set ownership for the first application
8
- RUN mkdir -p /app/nodejs-poolcontroller && chown node:node /app/nodejs-poolcontroller
9
-
10
- # Set the working directory for the first application
11
- WORKDIR /app/nodejs-poolcontroller
12
-
13
- # Copy the source code and necessary files for the first application
14
- COPY ./nodejs-poolcontroller/defaultConfig.json ./config.json
15
- COPY ./nodejs-poolcontroller ./
16
- COPY ./.docker/ecosystem.config.js ./ecosystem.config.js
17
-
18
- # Install dependencies and build the first application
19
- RUN npm ci
20
- RUN npm run build
21
-
22
- # Second stage for the second application
23
- FROM node:lts-alpine AS build-njspc-dp
24
-
25
- # Create the app directory and set ownership for the second application
26
- RUN mkdir -p /app/nodejs-poolcontroller-dashpanel && chown node:node /app/nodejs-poolcontroller-dashpanel
27
-
28
- # Set the working directory for the second application
29
- WORKDIR /app/nodejs-poolcontroller-dashpanel
30
-
31
- # Copy the source code and necessary files for the second application
32
- COPY ./nodejs-poolcontroller-dashpanel ./
33
-
34
- # Install dependencies and build the second application
35
- RUN npm ci
36
- RUN npm run build
37
-
38
- # Fourth stage for the final combined image
39
- FROM node:lts-alpine AS final
40
-
41
- # Install PM2 globally
42
- RUN npm install pm2 -g
43
-
44
- # Create the app directory and set ownership
45
- RUN mkdir -p /app && chown node:node /app
46
-
47
- # Set the working directory for the final combined image
48
- WORKDIR /app
49
-
50
- # Copy built applications from the previous stages into the final image
51
- COPY --from=build-njspc /app/nodejs-poolcontroller ./nodejs-poolcontroller
52
- COPY --from=build-njspc-dp /app/nodejs-poolcontroller-dashpanel ./nodejs-poolcontroller-dashpanel
53
- # COPY --from=build-rem /app/relayEquipmentManager ./relayEquipmentManager
54
-
55
- # Copy the ecosystem configuration file from the build stage
56
- COPY --from=build-njspc /app/nodejs-poolcontroller/ecosystem.config.js ./ecosystem.config.js
57
-
58
- # Expose any necessary ports
59
- EXPOSE 4200 5150
60
-
61
- # Define the command to run both applications using PM2
62
- CMD ["pm2-runtime", "start", "ecosystem.config.js"]
@@ -1,43 +0,0 @@
1
- # Use a Windows-based image as the build stage
2
- FROM mcr.microsoft.com/windows/servercore:ltsc2019 AS build
3
-
4
- # Set the working directory
5
- WORKDIR C:\app
6
-
7
- # Copy package.json and package-lock.json files
8
- COPY package*.json ./
9
- COPY defaultConfig.json config.json
10
-
11
- # Install Node.js
12
- RUN curl -sL https://nodejs.org/dist/v14.17.6/node-v14.17.6-x64.msi -o node.msi
13
- RUN msiexec /i node.msi /quiet
14
-
15
- # Install npm dependencies
16
- RUN npm ci
17
-
18
- # Copy the rest of the application files
19
- COPY . .
20
-
21
- # Build the application
22
- RUN npm run build
23
-
24
- # Second stage for the production image
25
- FROM mcr.microsoft.com/windows/servercore:ltsc2019 as prod
26
-
27
- # Install git
28
- RUN powershell -Command "Invoke-WebRequest -Uri https://github.com/git-for-windows/git/releases/download/v2.33.1.windows.1/Git-2.33.1-64-bit.exe -OutFile git.exe" && .\git.exe /VERYSILENT /NORESTART
29
-
30
- # Create the app directory
31
- RUN mkdir C:\app
32
-
33
- # Set the working directory
34
- WORKDIR C:\app
35
-
36
- # Copy built application from the build stage
37
- COPY --from=build C:\app .
38
-
39
- # Set environment variables
40
- ENV NODE_ENV=production
41
-
42
- # Define the entrypoint
43
- CMD ["node", "dist/app.js"]
@@ -1,47 +0,0 @@
1
- version: '3.8'
2
-
3
- services:
4
- poolcontroller:
5
- image: tagyoureit/nodejs-poolcontroller:latest
6
- container_name: poolcontroller
7
- restart: always
8
- group_add:
9
- - dialout
10
- devices:
11
- - /dev/ttyUSB0
12
- ports:
13
- - "4200:4200"
14
- volumes:
15
- - /data/poolcontroller/config.json:/app/nodejs-poolcontroller/config.json
16
- - /data/poolcontroller/data:/app/nodejs-poolcontroller/data
17
- - /data/poolcontroller/logs:/app/nodejs-poolcontroller/logs
18
- poolcontroller-dashpanel:
19
- restart: always
20
- container_name: poolcontroller-dashpanel
21
- ports:
22
- - "5150:5150"
23
- volumes:
24
- - /data/poolcontroller-dashpanel/config.json:/app/nodejs-poolcontroller-dashpanel/config.json
25
- image: rstrouse/nodejs-poolcontroller-dashpanel:latest
26
- depends_on:
27
- - poolcontroller
28
-
29
- # poolcontroller-armv7:
30
- # image: tagyoureit/nodejs-poolcontroller-armv7:latest
31
- # container_name: poolcontroller-armv7
32
- # restart: always
33
- # ports:
34
- # - "4200:4200"
35
- # volumes:
36
- # - /data/poolcontroller/config.json:/app/config.json
37
- # - /data/poolcontroller/data:/app/data
38
-
39
- # poolcontroller-armv6:
40
- # image: tagyoureit/nodejs-poolcontroller-armv6:latest
41
- # container_name: poolcontroller-armv6
42
- # restart: always
43
- # ports:
44
- # - "4200:4200"
45
- # volumes:
46
- # - /data/poolcontroller/config.json:/app/config.json
47
- # - /data/poolcontroller/data:/app/data
@@ -1,35 +0,0 @@
1
- module.exports = {
2
- apps: [
3
- {
4
- name: "dashPanel",
5
- script: "npm",
6
- args: ["start"],
7
- cwd: "/app/nodejs-poolcontroller-dashpanel",
8
- restart_delay: 10000,
9
- watch: [
10
- "pages",
11
- "scripts",
12
- "server",
13
- "package.json"
14
- ],
15
- watch_delay: 5000,
16
- kill_timeout: 15000
17
- },
18
- {
19
- name: "njsPC",
20
- script: "npm",
21
- args: ["start"],
22
- cwd: "/app/nodejs-poolcontroller",
23
- restart_delay: 10000,
24
- watch: [
25
- "config",
26
- "controller",
27
- "logger",
28
- "web",
29
- "package.json"
30
- ],
31
- watch_delay: 5000,
32
- kill_timeout: 15000
33
- }
34
- ]
35
- };
@@ -1,41 +0,0 @@
1
- name: Publish Docker Image - Windows
2
-
3
- on:
4
- push:
5
- branches:
6
- - docker
7
- workflow_dispatch:
8
-
9
-
10
- jobs:
11
- build-and-push-windows:
12
- runs-on: windows-latest
13
-
14
- steps:
15
- - name: Checkout code
16
- uses: actions/checkout@v4
17
-
18
- -
19
- # Add support for more platforms with QEMU (optional)
20
- # https://github.com/docker/setup-qemu-action
21
- name: Set up QEMU
22
- uses: docker/setup-qemu-action@v3
23
- -
24
- name: Set up Docker Buildx
25
- uses: docker/setup-buildx-action@v3
26
-
27
- - name: Login to Docker Hub
28
- uses: docker/login-action@v3
29
- with:
30
- username: ${{ secrets.DOCKERHUB_USERNAME }}
31
- password: ${{ secrets.DOCKERHUB_TOKEN }}
32
-
33
- - name: Build and push Windows Docker image
34
- uses: docker/build-push-action@v3
35
- with:
36
- context: .
37
- file: ./Dockerfile.windows
38
- push: true
39
- tags: |
40
- tagyoureit/nodejs-poolcontroller:windows-latest
41
- platforms: windows/amd64