forked from tagyoureit/nodejs-Pentair
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
3274 lines (2842 loc) · 142 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(function() {
'use strict';
// this function is strict...
}());
console.log('\033[2J'); //clear the console
var dateFormat = require('dateformat');
var Dequeue = require('dequeue')
var version = '1.0.0'
const events = require('events')
var bufferArr = []; //variable to process buffer. interimBufferArr will be copied here when ready to process
var interimBufferArr = []; //variable to hold all serialport.open data; incomind data is appended to this with each read
var currentStatus; // persistent object to hold pool equipment status.
var currentStatusBytes; //persistent variable to hold full bytes of pool status
var currentWhatsDifferent; //persistent variable to hold what's different
var currentCircuitArrObj; //persistent variable to hold circuit info
var currentPumpStatus; //persistent variable to hold pump information
var currentHeat; //persistent variable to heald heat set points
var currentSchedule = ["blank"]; //schedules
var queuePacketsArr = []; //array to hold messages to send
var writeQueueActive = false //flag to tell us if we are currently processing the write queue or note
var processingBuffer = false; //flag to tell us if we are processing the buffer currently
var msgCounter = 0; //log counter to help match messages with buffer in log
var msgWriteCounter = {
counter: 0, //how many times the packet has been written to the bus
packetWrittenAt: 0, //var to hold the message counter variable when the message was sent. Used to keep track of how many messages passed without a successful counter.
msgWrote: []
}; //How many times are we writing the same packet to the bus?
var skipPacketWrittenCount = 0 //keep track of how many times we skipped writing the packet
var needConfiguration = 1; //variable to let the program know we need the configuration from the Intellitouch
var checkForChange = [0, 0, 0]; //[custom names, circuit names, schedules] 0 if we have not logged the initial array; 1 if we will only log changes
var preambleByte; //variable to hold the 2nd preamble byte... it used to by 10 for me. Now it is 16. Very strange. So here is a variable to find it.
var countChecksumMismatch = 0; //variable to count checksum mismatches
var desiredChlorinatorOutput = 0; //variable for chlorinator output % (in pumpOnly mode; controllers take care of this otherwise). 0 = off. 1-100=% output; 101=superChlorinate
//search placeholders for sockets.io Search
var searchMode = 'stop'
var searchSrc = '';
var searchDest = '';
var searchAction = '';
var customNameArr = [];
// this first four bytes of ANY packet are the same
const packetFields = {
DEST: 2,
FROM: 3,
ACTION: 4,
LENGTH: 5,
}
const controllerStatusPacketFields = {
HOUR: 6,
MIN: 7,
EQUIP1: 8,
EQUIP2: 9,
EQUIP3: 10,
UOM: 15, //Celsius (4) or Farenheit (0); Also Service/Timeout. See strRunMode below.
VALVES: 16,
UNKNOWN: 19, //Something to do with heat.
POOL_TEMP: 20,
SPA_TEMP: 21,
HEATER_ACTIVE: 22, //0=off. 32=on. More here?
AIR_TEMP: 24,
SOLAR_TEMP: 25,
HEATER_MODE: 28
}
const chlorinatorPacketFields = {
DEST: 2,
ACTION: 3
}
const pumpPacketFields = {
DEST: 2,
FROM: 3,
ACTION: 4,
LENGTH: 5,
CMD: 6, //
MODE: 7, //?? Mode in pump status. Means something else in pump write/response
DRIVESTATE: 8, //?? Drivestate in pump status. Means something else in pump write/response
WATTSH: 9,
WATTSL: 10,
RPMH: 11,
RPML: 12,
PPC: 13, //??
//14 Unknown
ERR: 15,
//16 Unknown
TIMER: 18, //Have to explore
HOUR: 19, //Hours
MIN: 20 //Mins
}
const namePacketFields = {
NUMBER: 6,
CIRCUITFUNCTION: 7,
NAME: 8,
}
const pumpAction = {
1: 'WRITE', //Write commands to pump
4: 'REMOTE', //Turn on/off pump control panel
5: 'MODE', //Set pump mode
6: 'RUN', //Set run mode
7: 'STATUS' //Request status
}
const strCircuitName = {
0: 'NOT USED',
1: 'AERATOR',
2: 'AIR BLOWER',
3: 'AUX 1',
4: 'AUX 2',
5: 'AUX 3',
6: 'AUX 4',
7: 'AUX 5',
8: 'AUX 6',
9: 'AUX 7',
10: 'AUX 8',
11: 'AUX 9',
12: 'AUX 10',
13: 'BACKWASH',
14: 'BACK LIGHT',
15: 'BBQ LIGHT',
16: 'BEACH LIGHT',
17: 'BOOSTER PUMP',
18: 'BUG LIGHT',
19: 'CABANA LTS',
20: 'CHEM. FEEDER',
21: 'CHLORINATOR',
22: 'CLEANER',
23: 'COLOR WHEEL',
24: 'DECK LIGHT',
25: 'DRAIN LINE',
26: 'DRIVE LIGHT',
27: 'EDGE PUMP',
28: 'ENTRY LIGHT',
29: 'FAN',
30: 'FIBER OPTIC',
31: 'FIBER WORKS',
32: 'FILL LINE',
33: 'FLOOR CLNR',
34: 'FOGGER',
35: 'FOUNTAIN',
36: 'FOUNTAIN 1',
37: 'FOUNTAIN 2',
38: 'FOUNTAIN 3',
39: 'FOUNTAINS',
40: 'FRONT LIGHT',
41: 'GARDEN LTS',
42: 'GAZEBO LTS',
43: 'HIGH SPEED',
44: 'HI-TEMP',
45: 'HOUSE LIGHT',
46: 'JETS',
47: 'LIGHTS',
48: 'LOW SPEED',
49: 'LO-TEMP',
50: 'MALIBU LTS',
51: 'MIST',
52: 'MUSIC',
53: 'NOT USED',
54: 'OZONATOR',
55: 'PATH LIGHTS',
56: 'PATIO LTS',
57: 'PERIMETER L',
58: 'PG2000',
59: 'POND LIGHT',
60: 'POOL PUMP',
61: 'POOL',
62: 'POOL HIGH',
63: 'POOL LIGHT',
64: 'POOL LOW',
65: 'SAM',
66: 'POOL SAM 1',
67: 'POOL SAM 2',
68: 'POOL SAM 3',
69: 'SECURITY LT',
70: 'SLIDE',
71: 'SOLAR',
72: 'SPA',
73: 'SPA HIGH',
74: 'SPA LIGHT',
75: 'SPA LOW',
76: 'SPA SAL',
77: 'SPA SAM',
78: 'SPA WTRFLL',
79: 'SPILLWAY',
80: 'SPRINKLERS',
81: 'STREAM',
82: 'STATUE LT',
83: 'SWIM JETS',
84: 'WTR FEATURE',
85: 'WTR FEAT LT',
86: 'WATERFALL',
87: 'WATERFALL 1',
88: 'WATERFALL 2',
89: 'WATERFALL 3',
90: 'WHIRLPOOL',
91: 'WTRFL LGHT',
92: 'YARD LIGHT',
93: 'AUX EXTRA',
94: 'FEATURE 1',
95: 'FEATURE 2',
96: 'FEATURE 3',
97: 'FEATURE 4',
98: 'FEATURE 5',
99: 'FEATURE 6',
100: 'FEATURE 7',
101: 'FEATURE 8',
200: 'USERNAME-01',
201: 'USERNAME-02',
202: 'USERNAME-03',
203: 'USERNAME-04',
204: 'USERNAME-05',
205: 'USERNAME-06',
206: 'USERNAME-07',
207: 'USERNAME-08',
208: 'USERNAME-09',
209: 'USERNAME-10'
}
const strCircuitFunction = {
0: 'Generic',
1: 'Spa',
2: 'Pool',
5: 'Master Cleaner',
7: 'Light',
9: 'SAM Light',
10: 'SAL Light',
11: 'Photon Gen',
12: 'color wheel',
14: 'Spillway',
15: 'Floor Cleaner',
16: 'Intellibrite',
17: 'MagicStream',
19: 'Not Used',
64: 'Freeze protection on'
}
const strPumpActions = {
1: 'Pump set speed/program or run program',
4: 'Pump control panel',
5: 'Pump speed',
6: 'Pump power',
7: 'Pump Status'
}
const strChlorinatorActions = {
0: 'Get Status',
1: 'Response to Get Status',
3: 'Response to Get Version',
17: 'Set Salt %',
18: 'Response to Set Salt % & Salt PPM',
20: 'Get Version',
21: 'Set Salt Generate % / 10'
}
const strControllerActions = {
1: 'Ack Message',
2: 'Controller Status',
5: 'Date/Time',
7: 'Pump Status',
8: 'Heat/Temperature Status',
10: 'Custom Names',
11: 'Circuit Names/Function',
16: 'Heat Pump Status?',
17: 'Schedule details',
19: 'IntelliChem pH',
23: 'Pump Status',
24: 'Pump Config',
25: 'IntelliChlor Status',
29: 'Valve Status',
34: 'Solar/Heat Pump Status',
35: 'Delay Status',
39: 'Set ?',
40: 'Settings?',
133: 'Set Date/Time',
134: 'Set Circuit',
136: 'Set Heat/Temperature',
138: 'Set Custom Name',
139: 'Set Circuit Name/Function',
144: 'Set Heat Pump',
147: 'Set IntelliChem',
152: 'Set Pump Config',
153: 'Set IntelliChlor',
157: 'Set Valves',
162: 'Set Solar/Heat Pump',
163: 'Set Delay',
194: 'Get Status',
197: 'Get Date/Time',
200: 'Get Heat/Temperature',
202: 'Get Custom Name',
203: 'Get Circuit Name/Function',
208: 'Get Heat Pump',
209: 'Get Schedule',
211: 'Get IntelliChem',
215: 'Get Pump Status',
216: 'Get Pump Config',
217: 'Get IntelliChlor',
221: 'Get Valves',
226: 'Get Solar/Heat Pump',
227: 'Get Delays',
231: 'Get ?',
232: 'Get Settings?',
252: 'SW Version Info',
253: 'Get SW Version',
}
const strRunMode = {
//same bit as UOM. Need to fix naming.
0: 'Auto', //0x00000000
1: 'Service', //0x00000001
4: 'Celsius', //if 1, Celsius. If 0, Farenheit
128: '/Timeout' //Timeout always appears with Service; eg this bit has not been observed to be 128 but rather 129. Not sure if the timer is in the controller. 0x10000001
}
const strValves = {
3: 'Pool',
15: 'Spa',
48: 'Heater' // I've seen the value of 51. I think it is Pool + Heater. Need to investigate.
}
const heatModeStr = {
//Pentair controller sends the pool and spa heat status as a 4 digit binary byte from 0000 (0) to 1111 (15). The left two (xx__) is for the spa and the right two (__xx) are for the pool. EG 1001 (9) would mean 10xx = 2 (Spa mode Solar Pref) and xx01 = 1 (Pool mode Heater)
//0: all off
//1: Pool heater Spa off
//2: Pool Solar Pref Spa off
//3: Pool Solar Only Spa off
//4: Pool Off Spa Heater
//5: Pool Heater Spa Heater
//6: Pool Solar Pref Spa Heater
//7: Pool Solar Only Spa Heater
//8: Pool Off Spa Solar Pref
//9: Pool Heater Spa Solar Pref
//10: Pool Solar Pref Spa Solar Pref
//11: Pool Solar Only Spa Solar Pref
//12: Pool Off Spa Solar Only
//13: Pool Heater Spa Solar Only
//14: Pool Solar Pref Spa Solar Only
//15: Pool Solar Only Spa Solar Only
0: 'OFF',
1: 'Heater',
2: 'Solar Pref',
3: 'Solar Only'
}
const heatMode = {
OFF: 0,
HEATER: 1,
SOLARPREF: 2,
SOLARONLY: 3
}
const ctrl = {
CHLORINATOR: 2,
BROADCAST: 15,
INTELLITOUCH: 16,
REMOTE: 32,
WIRELESS: 34, //Looks like this is any communications through the wireless link (ScreenLogic on computer, iPhone...)
PUMP1: 96,
PUMP2: 97
}
const ctrlString = {
2: 'Chlorinator',
15: 'Broadcast',
16: 'Main',
32: 'Remote',
34: 'Wireless',
96: 'Pump1',
97: 'Pump2',
appAddress: 'nodejs-Pentair Server'
}
//------- EQUIPMENT SETUP -----------
//ONE and only 1 of the following should be set to 1.
var intellicom; //set this to 1 if you have the IntelliComII, otherwise 0.
var intellitouch; //set this to 1 if you have the IntelliTouch, otherwise 0.
var pumpOnly; //set this to 1 if you ONLY have pump(s), otherwise 0.
//1 or 0
var ISYController; //1 if you have an ISY, otherwise 0
var chlorinator; //set this to 1 if you have a chlorinator, otherwise 0.
//only relevant if pumpOnly=1
var numberOfPumps; //this is only used with pumpOnly=1. It will query 1 (or 2) pumps every 30 seconds for their status
var appAddress; //address the app should emulate/use on the serial bus
//------- END EQUIPMENT SETUP -----------
//------- MISC SETUP -----------
// Setup for Miscellaneous items
var netConnect; //set this to 1 to use a remote (net) connection, 0 for direct serial connection;
var netPort; //port for the SOCAT communications
var netHost; //host for the SOCAT communications
//------- END MISC SETUP -----------
//------- NETWORK SETUP -----------
// Setup for Network Connection (socat or nc)
var expressDir; //set this to the default directory for the web interface (either "/bootstrap" or "/public")
var expressPort; //port for the Express App Server
var expressTransport; //http, https, or both
//------- END NETWORK SETUP -----------
//------- LOG SETUP -----------
//Change the following log message levels as needed
var logType; // one of { error: 0, warn: 1, info: 2, verbose: 3, debug: 4, silly: 5 }
var logPumpMessages; //variable if we want to output pump messages or not
var logDuplicateMessages; //variable if we want to output duplicate broadcast messages
var logConsoleNotDecoded; //variable to hide any unknown messages
var logConfigMessages; //variable to show/hide configuration messages
var logMessageDecoding; //variable to show messages regarding the buffer, checksum calculation, etc.
var logChlorinator; //variable to show messages from the chlorinator
var logPacketWrites; //variable to log queueing/writing activities
var logPumpTimers; //variable to output timer debug messages for the pumps
//------- END EQUIPMENT SETUP -----------
var configurationFile = 'config.json';
const fs = require('fs');
var configFile = JSON.parse(fs.readFileSync(configurationFile));
intellicom = configFile.Equipment.intellicom;
intellitouch = configFile.Equipment.intellitouch;
pumpOnly = configFile.Equipment.pumpOnly;
ISYController = configFile.Equipment.ISYController;
chlorinator = configFile.Equipment.chlorinator;
numberOfPumps = configFile.Equipment.numberOfPumps;
appAddress = configFile.Equipment.appAddress;
expressDir = configFile.Misc.expressDir;
expressPort = configFile.Misc.expressPort;
netConnect = configFile.Network.netConnect;
netPort = configFile.Network.netPort;
netHost = configFile.Network.netHost;
logType = configFile.Log.logType;
logPumpMessages = configFile.Log.logPumpMessages;
logDuplicateMessages = configFile.Log.logDuplicateMessages;
logConsoleNotDecoded = configFile.Log.logConsoleNotDecoded;
logConfigMessages = configFile.Log.logConfigMessages;
logMessageDecoding = configFile.Log.logMessageDecoding;
logChlorinator = configFile.Log.logChlorinator;
logPacketWrites = configFile.Log.logPacketWrites;
logPumpTimers = configFile.Log.logPumpTimers;
/*
for (var key in configFile.Equipment) {
if (poolConfig.Pentair.hasOwnProperty(key)) {
var myEQ = 0;
if (j < 8) {
myEQ = 0; //8 bits for first mode byte
} else if (j >= 8 && j < 16) {
(myEQ = 1) //8 bits for 2nd mode byte
} else(myEQ = 2); //8 bits for 3rd mode byte
circuitArr[myEQ].push(poolConfig.Pentair[key]);
j++;
}
}*/
// Setup express server
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var port = process.env.PORT || 3000;
server.listen(port, function() {
logger.verbose('Express Server listening at port %d', port);
});
var winston = require('winston');
var winsocketio = require('./lib/winston-socketio.js').SocketIO
var logger = new(winston.Logger)({
transports: [
new(winston.transports.Console)({
timestamp: function() {
return dateFormat(Date.now(), "HH:MM:ss.l");
},
formatter: function(options) {
// Return string will be passed to logger.
return options.timestamp() + ' ' + winston.config.colorize(options.level, options.level.toUpperCase()) + ' ' + (undefined !== options.message ? options.message : '') +
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '');
},
colorize: true,
level: logType,
stderrLevels: []
})
]
});
var winsocketOptions = {
server: server,
level: 'info',
SocketIO: io,
customFormatter: function(level, message, meta) {
// Return string will be passed to logger.
return dateFormat(Date.now(), "HH:MM:ss.l") + ' ' + level.toUpperCase() + ' ' + (undefined !== message ? message.split('\n').join('<br \>') : '') +
(meta && Object.keys(meta).length ? '\n\t' + JSON.stringify(meta, null, '<br \>') : '');
}
}
logger.add(winsocketio, winsocketOptions)
var decodeHelper = require('./lib/decode.js');
if (netConnect === 0) {
const serialport = require("serialport");
//var SerialPort = serialport.SerialPort;
var sp = new serialport("/dev/ttyUSB0", {
baudrate: 9600,
databits: 8,
parity: 'none',
stopBits: 1,
flowControl: false,
parser: serialport.parsers.raw
});
} else {
var network = require('net');
var sp = new network.Socket();
sp.connect(netPort, netHost, function() {
logger.info('Network connected to: ' + netHost + ':' + netPort);
});
}
const loggerEmitter = new events.EventEmitter();
//loggerEmitter.on('debug', )
if (ISYController) {
var ISYHelper = require('./lib/ISY.js')
var ISYConfig; //object to hold ISY variables.
ISYConfig = JSON.parse(JSON.stringify(configFile.ISY))
}
var Circuit = require('./lib/circuit.js')
var currentCircuitArrObj = (new Circuit()).circuitArrObj()
var Heat = require('./lib/heat.js')
var currentHeat = new Heat();
function chlorinatorObj(saltPPM, outputPercent, outputSpaPercent, outputLevel, superChlorinate, version, name, status) {
this.saltPPM = saltPPM;
this.outputPercent = outputPercent; //for intellitouch this is the pool setpoint, for standalone it is the default
this.outputSpaPercent = outputSpaPercent; //intellitouch has both pool and spa set points
this.outputLevel = outputLevel;
this.superChlorinate = superChlorinate;
this.version = version;
this.name = name;
this.status = status;
}
var currentChlorinatorStatus = new chlorinatorObj(0, 0, 0, 0, 0, 0, '', '');
// This one should be removed once all instances are cleaned up and moved the the object directly above this.
//-----array format
var j = 0;
var circuitArr = [
[], //Circuits 0-7
[], //Circuits 8-15
[] //Circuits 16-?
];
function pump(number, time, run, mode, drivestate, watts, rpm, ppc, err, timer, duration, currentprogram, program1rpm, program2rpm, program3rpm, program4rpm, remotecontrol, power) {
this.pump = number; //1 or 2
this.time = time;
this.run = run;
this.mode = mode;
this.drivestate = drivestate;
this.watts = watts;
this.rpm = rpm;
this.ppc = ppc;
this.err = err;
this.timer = timer;
this.duration = duration;
this.currentprogram = currentprogram;
this.program1rpm = program1rpm;
this.program2rpm = program2rpm;
this.program3rpm = program3rpm;
this.program4rpm = program4rpm;
this.remotecontrol = remotecontrol;
this.power = power;
}
var pump1 = new pump(1, 'timenotset', 'runnotset', 'modenotset', 'drivestatenotset', 'wattsnotset', 'rpmnotset', 'ppcnotset', 'errnotset', 'timernotset', 'durationnotset', 'currentprognotset', 'prg1notset', 'prg2notset', 'prg3notset', 'prg4notset', 'remotecontrolnotset', 'powernotset');
var pump2 = new pump(2, 'timenotset', 'runnotset', 'modenotset', 'drivestatenotset', 'wattsnotset', 'rpmnotset', 'ppcnotset', 'errnotset', 'timernotset', 'durationnotset', 'currentprognotset', 'prg1notset', 'prg2notset', 'prg3notset', 'prg4notset', 'remotecontrolnotset', 'powernotset');
//object to hold pump information. Pentair uses 1 and 2 as the pumps so we will set array[0] to a placeholder.
var currentPumpStatus = ['blank', pump1, pump2]
var currentPumpStatusPacket = ['blank', [],
[]
]; // variable to hold the status packets of the pumps
var NanoTimer = require('nanotimer'); //If you get an error here, re-run 'npm install` because this is a new dependency.
var writePacketTimer = new NanoTimer();
var introMsg = '\n*******************************';
introMsg += '\n Important:';
introMsg += '\n Configuration is now read from your pool. The application will send the commands to retrieve the custom names and circuit names.';
introMsg += '\n It will dynamically load as the information is parsed. If there is a write error 10 times, the logging will change to debug mode.';
introMsg += '\n If the message fails to be written 20 times, it will abort the packet and go to the next one.';
introMsg += '\n If you have an IntelliComII, or pumps only, set the appropriate flags in lines 21-23 of this app.';
introMsg += '\n In general, if you specify the Intellitouch controller, the app will get the status (pumps, chlorinator, heater, etc)from the controller directly. If you specify pumps only or IntellicomII, the app will retrieve the status information from the peripherals themselves.'
introMsg += '\n To change the amount of output to the console, change the "logx" flags in lines 45-51 of this app.';
introMsg += '\n Visit http://_your_machine_name_:3000 to see a basic UI';
introMsg += '\n Visit http://_your_machine_name_:3000/debug.html for a way to listen for specific messages\n\n';
introMsg += '*******************************\n'
logger.info(introMsg)
var settingsStr = '' // \n*******************************';
settingsStr += '\n Version: ' + version;
settingsStr += '\n ';
settingsStr += '\n //------- EQUIPMENT SETUP -----------';
settingsStr += '\n var intellicom = ' + intellicom;
settingsStr += '\n var intellitouch = ' + intellitouch;
settingsStr += '\n var chlorinator = ' + chlorinator;
settingsStr += '\n var pumpOnly = ' + pumpOnly;
settingsStr += '\n var numberOfPumps = ' + numberOfPumps;
settingsStr += '\n var ISYController = ' + ISYController;
settingsStr += '\n var appAddress = ' + appAddress;
settingsStr += '\n //------- END EQUIPMENT SETUP -----------';
settingsStr += '\n ';
settingsStr += '\n //------- MISC SETUP -----------';
settingsStr += '\n var expressDir = ' + expressDir;
settingsStr += '\n var expressPort = ' + expressPort;
settingsStr += '\n var expressTransport = ' + expressTransport;
settingsStr += '\n //------- END MISC SETUP -----------';
settingsStr += '\n ';
settingsStr += '\n //------- NETWORK SETUP -----------';
settingsStr += '\n // Setup for Network Connection (socat or nc)';
settingsStr += '\n var netConnect = ' + netConnect;
settingsStr += '\n var netHost = ' + netHost;
settingsStr += '\n var netPort = ' + netPort;
settingsStr += '\n //------- END NETWORK SETUP -----------';
settingsStr += '\n ';
settingsStr += '\n //------- LOG SETUP -----------';
settingsStr += '\n var logType = ' + logType;
settingsStr += '\n var logPumpMessages = ' + logPumpMessages;
settingsStr += '\n var logDuplicateMessages = ' + logDuplicateMessages;
settingsStr += '\n var logConsoleNotDecoded = ' + logConsoleNotDecoded;
settingsStr += '\n var logConfigMessages = ' + logConfigMessages;
settingsStr += '\n var logMessageDecoding = ' + logMessageDecoding;
settingsStr += '\n var logChlorinator = ' + logChlorinator;
settingsStr += '\n var logPacketWrites = ' + logPacketWrites;
settingsStr += '\n var logPumpTimers = ' + logPumpTimers;
settingsStr += '\n //------- END LOG SETUP -----------\n\n';
//settingsStr += '\n*******************************';
logger.debug(settingsStr);
//return a given equipment name given the circuit # 0-16++
/*function circuitArrStr(equip) {
equip = equip - 1; //because equip starts at 1 but array starts at 0
if (equip < 8) {
return circuitArr[0][equip]
} else if (equip >= 8 && equip < 16) {
return circuitArr[1][equip - 8]
} else {
return circuitArr[2][equip - 16]
}
return 'Error';
}*/
//Used to count all items in array
//countObjects(circuitArr) will count all items provided in config.json
function countObjects(obj) {
var count = 0;
// iterate over properties, increment if a non-prototype property
for (var key in obj) {
if (obj.hasOwnProperty(key))
for (var key2 in obj[key]) {
if (obj[key].hasOwnProperty(key2))
count++
}
}
return count;
}
sp.on('error', function(err) {
logger.error('Error opening port: ', err.message)
process.exit(1)
})
sp.on('open', function() {
logger.verbose('Serial Port opened');
})
var interimBuffer = [];
var bufferToProcess = [];
var bufferArrayOfArrays = new Dequeue();
var testbufferArrayOfArrays = []
sp.on('data', function(data) {
//Push the incoming array onto the end of the dequeue array
bufferArrayOfArrays.push(Array.prototype.slice.call(data));
//console.log('Input: ', JSON.stringify(data.toJSON().data) + '\n');
//testbufferArrayOfArrays.push(Array.prototype.slice.call(data));
if (!processingBuffer) {
//console.log('Arrays being passed for processing: \n[[%s]]\n\n', testbufferArrayOfArrays.join('],\n['))
iterateOverArrayOfArrays()
//testbufferArrayOfArrays=[]
}
});
//TEST function: This function should simply output whatever comes into the serialport. Comment out the one above and use this one if you want to test what serialport logs.
/*
var bufferArrayOfArrays = [];
sp.on('data', function (data) {
console.log('Input: ', JSON.stringify(data.toJSON().data) + '\n');
bufferArrayOfArrays.push(Array.prototype.slice.call(data));
console.log('Array: \n[[%s]]\n\n', bufferArrayOfArrays.join('],\n['))
});*/
function pushBufferToArray() {
if (bufferArrayOfArrays.length > 0) {
if (bufferToProcess.length == 0) {
bufferToProcess = bufferArrayOfArrays.shift() //move the first element from Array of Arrays to bufferToProcess
if (logMessageDecoding)
logger.silly('pBTA: bufferToProcess length=0; bufferArrayOfArrays>0. Shifting AoA to BTP')
} else {
bufferToProcess = bufferToProcess.concat(bufferArrayOfArrays.shift())
if (logMessageDecoding)
logger.silly('pBTA: bufferToProcess length>0; bufferArrayOfArrays>0. CONCAT AoA to BTP')
}
}
}
function iterateOverArrayOfArrays() {
var chatter = []; //a {potential} message we have found on the bus
var packetType;
var preambleStd = [255, 165];
var preambleChlorinator = [16, 2]
var breakLoop = false
processingBuffer = true; //we don't want this function to run asynchronously beyond this point or it will start to process the same array multiple times
pushBufferToArray()
if (logMessageDecoding) {
if (logType === 'debug')
console.log('\n\n')
logger.debug('iOAOA: Packet being analyzed: %s', bufferToProcess);
if (bufferArrayOfArrays.length === 1) {
logger.silly('iOAOA: Next two packets in buffer: \n %s ', bufferArrayOfArrays.first())
} else if (bufferArrayOfArrays.length > 1) {
var tempArr = bufferArrayOfArrays.shift()
logger.silly('iOAOA: Next two packets in buffer: \n %s \n %s', tempArr, bufferArrayOfArrays.first())
bufferArrayOfArrays.unshift(tempArr)
} else {
logger.silly('iOAOA: No more packets in bufferArrayOfArrays')
}
}
while (bufferToProcess.length > 0 && !breakLoop) {
if (preambleStd[0] == bufferToProcess[0] && preambleStd[1] == bufferToProcess[1]) //match on pump or controller packet
{
var chatterlen = bufferToProcess[6] + 6 + 2; //chatterlen is length of following message not including checksum (need to add 6 for start of chatter, 2 for checksum)
// 0, 1, 2, 3, 4, 5, 6
//(255,165,preambleByte,Dest,Src,cmd,chatterlen) and 2 for checksum)
if (chatterlen >= 50) //we should never get a packet greater than or equal to 50. So if the chatterlen is greater than that let's shift the array and retry
{
if (logMessageDecoding) logger.debug('iOAOA: Will shift first element out of bufferToProcess because it appears there is an invalid length packet (>=50) Lengeth: %s Packet: %s', bufferToProcess[6], bufferToProcess)
bufferToProcess.shift() //remove the first byte so we look for the next [255,165] in the array.
} else if ((bufferToProcess.length - chatterlen) <= 0) {
if (logMessageDecoding)
logger.silly('Msg# n/a Incomplete message in bufferToProcess. %s', bufferToProcess)
if (bufferArrayOfArrays.length > 0) {
pushBufferToArray()
} else {
if (logMessageDecoding) logger.silly('iOAOA: Setting breakLoop=true because (bufferToProcess.length(%s) - chatterlen) <= 0(%s): %s', bufferToProcess.length, chatterlen == undefined || ((bufferToProcess.length - chatterlen), chatterlen == undefined || (bufferToProcess.length - chatterlen) <= 0))
breakLoop = true //do nothing, but exit until we get a second buffer to concat
}
} else
if (chatterlen == undefined || isNaN(chatterlen)) {
if (logMessageDecoding)
logger.silly('Msg# n/a chatterlen NaN: %s.', bufferToProcess)
if (bufferArrayOfArrays.length > 0) {
pushBufferToArray()
} else {
if (logMessageDecoding) logger.silly('iOAOA: Setting breakLoop=true because isNan(chatterlen) is %s. bufferToProcess:', chatterlen, bufferToProcess)
breakLoop = true //do nothing, but exit until we get a second buffer to concat
}
} else {
if (logMessageDecoding)
logger.silly('iOAOA: Think we have a packet. bufferToProcess: %s chatterlen: %s', bufferToProcess, chatterlen)
msgCounter += 1;
bufferToProcess.shift() //remove the 255 byte
chatter = bufferToProcess.splice(0, chatterlen); //splice modifies the existing buffer. We remove chatter from the bufferarray.
if (((chatter[2] == ctrl.PUMP1 || chatter[2] == ctrl.PUMP2)) || chatter[3] == ctrl.PUMP1 || chatter[3] == ctrl.PUMP2) {
packetType = 'pump'
} else {
packetType = 'controller';
preambleByte = chatter[1]; //not sure why we need this, but the 165,XX packet seems to change. On my system it used to be 165,10 and then switched to 165,16. Not sure why! But we dynamically adjust it so it works for any value. It is also different for the pumps (should always be 0 for pump messages)
}
if (logMessageDecoding)
logger.debug('Msg# %s Found incoming %s packet: %s', msgCounter, packetType, chatter)
processChecksum(chatter, msgCounter, packetType);
}
//breakLoop = true;
} else if (preambleChlorinator[0] == bufferToProcess[0] && preambleChlorinator[1] == bufferToProcess[1] &&
(bufferToProcess[2] == 0 || bufferToProcess[2] == 80)) {
/*Match on chlorinator packet
//the ==80 and ==0 is a double check in case a partial packet comes through.
//example packet:
//byte 0 1 2 3 4 5 6 7
//len 8
// 16 2 80 20 2 120 16 3*/
msgCounter += 1;
chatter = [];
var i = 0;
//Looking for the Chlorinator preamble 16,2
while (!(bufferToProcess[i] == 16 && bufferToProcess[i + 1] == 3) && !breakLoop) {
//check to make sure we aren't reaching the end of the buffer.
if ((i + 1) === bufferToProcess.length) {
//if we get here, just silently abort
breakLoop = true
if (logMessageDecoding) logger.silly('Msg# %s Aborting chlorinator packet because we reached the end of the buffer.', msgCounter, bufferToProcess)
} else {
chatter.push(bufferToProcess[i]);
i++;
if (bufferToProcess[i] == 16 && bufferToProcess[i + 1] == 3) {
chatter.push(bufferToProcess[i]);
chatter.push(bufferToProcess[i + 1]);
i += 2;
processChecksum(chatter, msgCounter, 'chlorinator');
bufferToProcess.splice(0, i)
breakLoop = true;
}
}
}
} else { //not a preamble for chlorinator or pump/controller packet. Eject the first byte.
bufferToProcess.shift();
}
}
logger.silly('iOAOA: Criteria for recursing/exting. \nbreakLoop: %s\nbufferArrayOfArrays.length(%s) === 0 && bufferToProcess.length(%s) > 0: %s', breakLoop, bufferArrayOfArrays.length, bufferToProcess.length, bufferArrayOfArrays.length === 0 && bufferToProcess.length > 0)
if (breakLoop) {
processingBuffer = false;
if (logMessageDecoding)
logger.silly('iOAOA: Exiting because breakLoop: %s', breakLoop)
} else
if (bufferToProcess.length > 0) {
if (logMessageDecoding)
logger.silly('iOAOA: Recursing back into iOAOA because no bufferToProcess.length > 0: %s', bufferToProcess.length > 0)
iterateOverArrayOfArrays()
} else
if (bufferArrayOfArrays.length === 0) {
processingBuffer = false;
if (logMessageDecoding)
logger.silly('iOAOA: Exiting out of loop because no further incoming buffers to append. bufferArrayOfArrays.length === 0 (%s) ', bufferArrayOfArrays.length === 0)
} else {
if (logMessageDecoding)
logger.silly('iOAOA: Recursing back into iOAOA because no other conditions met.')
iterateOverArrayOfArrays()
}
}
function processChecksum(chatter, counter, packetType) {
//call new function to process message; if it isn't valid, we noted above so just don't continue
//TODO: countChecksumMismatch is not incrementing properly
if (decodeHelper.checksum(chatter, counter, packetType, logMessageDecoding, logger, countChecksumMismatch)) {
if (queuePacketsArr.length > 0) {
if (decodeHelper.isResponse(chatter, counter, packetType, logger, logMessageDecoding, packetFields, queuePacketsArr)) {
successfulAck(chatter, counter, 1);
} else {
successfulAck(chatter, counter, 0);
}
}
decode(chatter, counter, packetType)
} else {
//TODO: we shouldn't need to increment the countChecksumMismatch. Why is it not being increased with decodeHelper.checksum above?
countChecksumMismatch++
}
}
function decode(data, counter, packetType) {
var decoded = false;
//when searchMode (from socket.io) is in 'start' status, any matching packets will be set to the browser at http://machine.ip:3000/debug.html
if (searchMode == 'start') {
var resultStr = 'Msg#: ' + counter + ' Data: ' + JSON.stringify(data)
if (searchAction == data[packetFields.ACTION] && searchSrc == data[packetFields.FROM] && searchDest == data[packetFields.DEST]) {
io.sockets.emit('searchResults',
resultStr
)
}
}
if (needConfiguration) {
if (intellitouch) // ONLY check the configuration if the controller is Intellitouch (address 16)
{
if (preambleByte != undefined) {
//NOTE: Why do we need to send the DEST and FROM packets?
getControllerConfiguration(data[packetFields.DEST], data[packetFields.FROM])
needConfiguration = 0; //we will no longer request the configuration. Need this first in case multiple packets come through.
}
} else {
if (intellicom) {
logger.info('IntellicomII Controller Detected. No configuration request messages sent.')
} else {
logger.info('No pool controller (Intellitouch or IntelliComII) detected. No configuration request messages sent.')
}
needConfiguration = 0; //we will no longer request the configuration. Need this first in case multiple packets come through.
}
}
if (logMessageDecoding)
logger.silly('Msg# %s TYPE %s, packet %s', counter, packetType, data)
//Start Controller Decode
//I believe this should be any packet with 165,10. Need to verify. --Nope, 165,16 as well.
if (packetType == 'controller') {
//logger.silly('Msg# %s Packet info: dest %s from %s', counter, data[packetFields.DEST], data[packetFields.FROM]);
switch (data[packetFields.ACTION]) {
case 2: //Controller Status
{
//quick gut test to see if we have a duplicate packet
if (JSON.stringify(data) != JSON.stringify(currentStatusBytes)) {
//--------Following code (re)-assigns all the incoming data to the status object
var status = {};
//if the currentStatus is present, copy it.
if (currentStatus != undefined) {
status = JSON.parse(JSON.stringify(currentStatus))
}
var timeStr = ''
if (data[controllerStatusPacketFields.HOUR] > 12) {
timeStr += data[controllerStatusPacketFields.HOUR] - 12
} else {
timeStr += data[controllerStatusPacketFields.HOUR]
}
timeStr += ':'
if (data[controllerStatusPacketFields.MIN] < 10)
timeStr += '0';
timeStr += data[controllerStatusPacketFields.MIN]
if (data[controllerStatusPacketFields.HOUR] > 11 && data[controllerStatusPacketFields.HOUR] < 24) {
timeStr += " PM"
} else {
timeStr += " AM"
}
status.time = timeStr;
status.poolTemp = data[controllerStatusPacketFields.POOL_TEMP];
status.spaTemp = data[controllerStatusPacketFields.SPA_TEMP];
status.airTemp = data[controllerStatusPacketFields.AIR_TEMP];
status.solarTemp = data[controllerStatusPacketFields.SOLAR_TEMP];
status.poolHeatMode2 = heatModeStr[data[controllerStatusPacketFields.UNKNOWN] & 3]; //mask the data[6] with 0011
status.spaHeatMode2 = heatModeStr[(data[controllerStatusPacketFields.UNKNOWN] & 12) >> 2]; //mask the data[6] with 1100 and shift right two places
status.poolHeatMode = heatModeStr[data[controllerStatusPacketFields.HEATER_MODE] & 3]; //mask the data[6] with 0011
status.spaHeatMode = heatModeStr[(data[controllerStatusPacketFields.HEATER_MODE] & 12) >> 2]; //mask the data[6] with 1100 and shift right two places
status.valves = strValves[data[controllerStatusPacketFields.VALVES]];