-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmcp-pcf-aio.js
More file actions
884 lines (743 loc) · 45.6 KB
/
mcp-pcf-aio.js
File metadata and controls
884 lines (743 loc) · 45.6 KB
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
'Strict mode'
// const { truncate } = require("fs");
const { clearInterval } = require("timers");
const { threadId } = require("worker_threads");
debugger
// const { normalize } = require("path");
// const { abort } = require("process");
// const { isNumberObject } = require("util/types");
// Read more:
// CHIP SECTION - For inputs this reads the chip and fires the input module
// https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf
// NodeRED forum about enhancing this component:
// https://discourse.nodered.org/t/node-red-contrib-mcp23017chip/37999
// For lisencing >> see Lisence file in the upper directory.
// * 1 = Pin is configured as an input.
// * 0 = Pin is configured as an output.
// * See "3.5.1 I/O Direction register".
// *** chip initialisation (IOCON values) *** //
// ... enables "byte mode" (IOCON.BANK = 0 and IOCON.SEQOP = 0).
//bit7 BANK = 1 : sequential register addresses (See PDF: Table 3-3)
//bit6 MIRROR = 1 : use configure Interrupt
//bit5 SEQOP = 1 : sequential operation disabled, address pointer does not increment
//bit4 DISSLW = 0 : The "Slew Rate" bit controls the slew rate function on the SDA pin. If enabled, the SDA slew rate will be controlled when driving from a high to low.
//bit3 HAEN = 0 : hardware address pin is always enabled on 23017
//bit2 ODR = 0 : open drain output. Enables/disables the INT pin for open-drain configuration. Setting this bit overrides the INTPOL bit.
//bit1 INTPOL = 1 : interrupt active low (Sets the polarity of the INT pin. This bit is functional only when the ODR bit is cleared, configuring the INT pin as active push-pull)
//bit0 xxx unused = 0
// for example SEQOP = 1: write ( addr, IOCON, 0b00100000 ); = 32 dec = 0x20
module.exports = function(RED) {
const busStateTexts = [
"Opening i2c Bus", // 0 ... actually this is done only virtually, the opening happens only at first read/write
"Reading current state", // 1
"Writing byte", // 2
"Closing i2c bus"]; // 3
var log2consol = true; // enabling it shows detailed logs in: node-red-log
// var logTimer = false; // !! WARNING !! << if true, it will fill up the log with ALL read events (up to 50x3 msg. pro sec !! if read interval is 20ms)
var logMaxLines = 1000; // it will be decreased until reaching 0 to stop spamming full the whole log file
// IOCON.BANK = 0 mode << !!! Using this is NOT recommended, because it causing "sequential read" of A/B bank, not a fixed side
const BNK0_IODIR_A = 0x00;
const BNK0_IODIR_B = 0x01;
const BNK0_IPOL_A = 0x02;
const BNK0_IPOL_B = 0x03;
const BNK0_GPINTEN_A = 0x04;
const BNK0_GPINTEN_B = 0x05;
const BNK0_DEFVAL_A = 0x06;
const BNK0_DEFVAL_B = 0x07;
const BNK0_INTCON_A = 0x08;
const BNK0_INTCON_B = 0x09;
const BNK0_IOCON_A = 0x0A;
const BNK0_IOCON_B = 0x0B;
const BNK0_GPPU_A = 0x0C;
const BNK0_GPPU_B = 0x0D;
const BNK0_INTF_A = 0x0E;
const BNK0_INTF_B = 0x0F;
const BNK0_INTCAP_A = 0x10;
const BNK0_INTCAP_B = 0x11;
const BNK0_GPIO_A = 0x12;
const BNK0_GPIO_B = 0x13;
const BNK0_OLAT_A = 0x14;
const BNK0_OLAT_B = 0x15;
// The following register addresses assume IOCON.BANK = 1 << THIS IS what this program is using
const BNK1_IODIR_A = 0x00; //< Controls the direction of the data Input/Output for port A.
const BNK1_IPOL_A = 0x01; //< Configures the polarity on the corresponding GPIO_ port bits for input port A.
const BNK1_GPINTEN_A = 0x02; //< Controls the input interrupt-on-change for each pin of port A.
const BNK1_DEFVAL_A = 0x03; //< Controls the default comparison value for interrupt-on-change for port A.
const BNK1_INTCON_A = 0x04; //< Controls how the associated pin value is compared for the interrupt-on-change for port A.
const BNK1_IOCON_A = 0x05; //< Controls the device. (0, INTPOL, ODR, HAEN, DISSLW, SEQOP, MIRROR, BANK) = 0x0B too
const BNK1_GPPU_A = 0x06; //< Controls the input pull-up resistors for the port A pins.
const BNK1_INTF_A = 0x07; //< Reflects the input interrupt condition on the port A pins.
const BNK1_INTCAP_A = 0x08; //< Captures the port A value at the time the interrupt occurred.
const BNK1_GPIO_A = 0x09; //< Reflects the value on the port A.
const BNK1_OLAT_A = 0x0A; //< Provides access to the port A output latches.
const BNK1_IODIR_B = 0x10; //< Controls the direction of the data Input/Output for port B.
const BNK1_IPOL_B = 0x11; //< Configures the polarity on the corresponding GPIO_ port bits for input port B.
const BNK1_GPINTEN_B = 0x12; //< Controls the input interrupt-on-change for each pin of port B.
const BNK1_DEFVAL_B = 0x13; //< Controls the default comparison value for interrupt-on-change for port B.
const BNK1_INTCON_B = 0x14; //< Controls how the associated pin value is compared for the interrupt-on-change for port B.
const BNK1_IOCON_B = 0x15; //< Controls the device. (0, INTPOL, ODR, HAEN, DISSLW, SEQOP, MIRROR, BANK) = 0x0B too
const BNK1_GPPU_B = 0x16; //< Controls the input pull-up resistors for the port B pins.
const BNK1_INTF_B = 0x17; //< Reflects the input interrupt condition on the port B pins.
const BNK1_INTCAP_B = 0x18; //< Captures the port B value at the time the interrupt occurred.
const BNK1_GPIO_B = 0x19; //< Reflects the value on the port B.
const BNK1_OLAT_B = 0x1A; //< Provides access to the port B output latches.
var i2cModule = require("i2c-bus"); // https://github.com/fivdi/i2c-bus
var process = require('process');
const performance = require('perf_hooks').performance;
process.on('uncaughtException', (err, origin) => {
console.error( "!!! Unhandled error in MPC23017/PCF8574 node-red module. >> " + err + " >> ORIGIN: " + origin );
});
// *** Bit manipulation functions:
//Get bit
function getBit(number, bitPosition) {
return (number & (1 << bitPosition)) === 0 ? 0 : 1;
}
//Set Bit
function setBit(number, bitPosition) {
return number | (1 << bitPosition);
}
//Clear Bit
function clearBit(number, bitPosition) {
const mask = ~(1 << bitPosition);
return number & mask;
}
//Update Bit
function updateBit(number, bitPosition, bitValue) {
const bitValueNormalized = bitValue ? 1 : 0;
const clearMask = ~(1 << bitPosition);
return (number & clearMask) | (bitValueNormalized << bitPosition);
}
// Show "red / yellow / green / greey" point next to the node
function showState (_obj, _onOffState, _errorState) {
// _errorState: if address is taken or a global error occurred while trying read/write to the chip
if (log2consol) console.log(" ...state update >> _onOffState: " + _onOffState +" globalState= "+ _errorState + " Node_id=" + _obj.id);
if ((_errorState >= 2) || (_onOffState == -2)) { // ERROR !
// it is impossible to determine if a bus is existing and working, so the whole chip will be set into "error state"
_obj.status({fill:"red" ,shape:"dot" ,text:"! Error." + _errorState >= 2 ? " Re-test:" + _errorState + "sec" : ""});
}
else
{
if (_errorState == 0) { // Uninitialized
_obj.status({fill:"yellow",shape:"ring",text:"unknown yet"});
}
else
{
if (_errorState == 1) { // Working :-)
const _onOff = _obj.invert ? !_onOffState : _onOffState;
if (_onOff == true) {
_obj.status({fill:"green" ,shape:"dot" ,text:"On"});
}else
if (_onOff == false){
_obj.status({fill:"grey" ,shape:"ring",text:"Off"});
}
}
}
}
}
// *** MAIN CHIP :
// *****************
function mcp_pcf_chipNode(n) {
RED.nodes.createNode(this, n);
var mainChip = this;
log2consol = n.log2consol;
mainChip.logTimer = n.logTimer;
this.lgc = function () { // check if detailed logging (inside a high freq. timer) is enabled?
if (!log2consol) return false;
if (!mainChip.logTimer) return false;
if (logMaxLines == 0) return false;
logMaxLines--; // max 1000 log lines enabled
return true;
}
this.busNum = parseInt(n.busNum, 10); // converts string to decimal (10)
this.addr = parseInt(n.addr , 16); // converts from HEXA (16) to decimal (10)
if (isNaN(this.addr)) {this.addr = 0x20; }
this.MaxBits = (this.addr >= 0x40 ? 8 : 16);
this.isInputs = 0x0000; // which ports are input ports (saved in binary form)
this.pullUps = 0x0000;
// this.inverts = 0x0000;
this.startAllHIGH = n.startAllHIGH; // Some relay boards are negated. (HIGH = OFF)
this.ids = [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]; //Array(16)
this.globalState = 0; // 0=uninitialized 1=working: on/off=see:ids 2=error
this.errorCount = 0;
this.allStates = -1; // 0x0000;
this.lastTimeRed = 0; // when was the last time a successfull a full 16bit READ operation happened (inputs)
this.readLength = 0; // how long did it take the last read sequence (inputs)(ms)
// Timer related variables:
this.interval = 0 + n.interval;
// if ((this.interval < 20) && (this.interval != 0)) this.interval = 20; // one readout takes at least 8 ms x2 banks
this.origInterv = this.interval;
this.chipTimer = null;
this.timerIsRunning = false;
this.rwIsHappening = false; // global atomic bool (TODO for later)
// *** GLOBAL RW context *** //
// >> to prevent Read-Write operations happening at the same time on the same i2c Bus.
let global_i2c_bus_RW_ctx = this.context().global;
const _i2c_ctx_name = "i2c"+ this.busNum + "RW";
if (global_i2c_bus_RW_ctx.get(_i2c_ctx_name) == null) {
if (log2consol) console.log(" MCP/FCP context does not exists yet. Creating now: " + _i2c_ctx_name);
global_i2c_bus_RW_ctx.set(_i2c_ctx_name, false);
}
else if (log2consol) console.log(" MCP/FCP context does exists already: " + _i2c_ctx_name );
// TODO: block RW operations happening at the same time...
this.RW_check = function() {
if ( mainChip.rwIsHappening ) return false;
if (global_i2c_bus_RW_ctx.get(_i2c_ctx_name) == null) return false;
mainChip.rwIsHappening = true;
global_i2c_bus_RW_ctx.set(_i2c_ctx_name, true);
return true;
}
this.RW_finish = function() {
mainChip.rwIsHappening = false;
global_i2c_bus_RW_ctx.set(_i2c_ctx_name, false);
}
if (log2consol) console.log(" MCP/PCF chip initialization OK. BusNumber=" + this.busNum + " Address=" + this.addr + " id:" + this.id);
/*
// Sleep 4ms to wait for rwIsHappening gets false again
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
const waitForPrevOperationToFinish = async () => {
let maxTimeout = 0;
while ((maxTimeout < 40) && this.rwIsHappening) {
await sleep(4);
maxTimeout += 4;
}
return;
} */
/* ### INITIALIZATION of the Chip ### */
/* ################################## */
this.initializeBit = function(_bitNum, _isInput, _pullUp, _callerNode){
const _parCh = _callerNode.parentChip;
if (log2consol) console.log(" MCP/PCF init-Bit started... Addr=" + _parCh.addr + " bitNum=" + _bitNum + " isInput=" + _isInput + " pullUp=" + _pullUp + " startAllHigh=" + _parCh.startAllHIGH + " LastState="+ _callerNode.lastState);
if (_parCh.ids[_bitNum] != null ) {
if (log2consol) console.log("!!MCP chip-node-ids[_bitNum] != null ALREADY exists a node for this Bit set:" + _parCh.ids[_bitNum]);
if (_parCh.ids[_bitNum] != _callerNode.id ) {
if (log2consol) console.log("!!MCP chip-node-ids[_bitNum] != _callerNode.id =" + _callerNode.id );
_callerNode.lastState = -2; // error state
showState(_callerNode, -2, 2); // show red error status at the corner of the Node
_callerNode.error("!!MCP/PCF pin is already used by an other node: Bit=" + _bitNum + " Bus=" + _parCh.busNum + " Addr=" + _parCh.addr + " ID=" + _parCh.ids[_bitNum]);
return false;
} //
}
for (var i=0; i < _parCh.MaxBits; i++){ //NEED TO REMOVE ANY OTHER REFERENCES TO THIS ID (MaxBits= 8 or 16)
if (_parCh.ids[i] == _callerNode.id) { _parCh.ids[i] = null; }
}
_parCh.ids[_bitNum] = _callerNode.id; // remember, which pin (bitNum) is this Node assigned to.
if ( ! _parCh.RW_check() ) return false;
let _processState = 0;
try {
let aBus = i2cModule.openSync(_parCh.busNum);
// *** PCF8574 or PCF8574A chip *** //
if (_parCh.addr >= 0x40) {
if ((_parCh.startAllHIGH == true) && (_callerNode.lastState = -2)){
if (log2consol) console.log(" PCF Now Setting ALL pins to HIGH. A+B = 1111111111111111 Addr=" + Math.ceil( _parCh.addr / 2 ));
aBus.sendByteSync( Math.ceil( _parCh.addr / 2 ), 0xFF);
_parCh.lastTimeRed = performance.now();
_parCh.allStates = 0xFF;
_parCh.startAllHIGH = false; // 1x running is enough. Turn it off now.
}
else {
_parCh.allStates = aBus.receiveByteSync( Math.ceil( _parCh.addr / 2 ) );
}
}
else
{ // *** MCP23017 Chip *** //
function bbb () { // this proc. is only for testing
let bank0 = -1; let bank1 = -1;
bank0 = aBus.readByteSync(_parCh.addr, BNK0_IOCON_B);
bank1 = aBus.readByteSync(_parCh.addr, BNK1_IOCON_A);
console.log("************** Bank IOCON_B_BNK0=" + bank0.toString(2) + " ********* Bank IOCON_A_BNK1=" + bank1.toString(2));
console.log("**** A0=" + aBus.readByteSync(_parCh.addr, BNK0_OLAT_A).toString(2) + " B0=" + aBus.readByteSync(_parCh.addr, BNK0_OLAT_B).toString(2));
console.log("**** A1=" + aBus.readByteSync(_parCh.addr, BNK1_OLAT_A).toString(2) + " B1=" + aBus.readByteSync(_parCh.addr, BNK1_OLAT_B).toString(2));
}
// First forcing chip's IOCON.BANK=1 mode twice, so it WILL be =1 .
//bbb();
aBus.writeByteSync(_parCh.addr, BNK0_IOCON_B , 0xA0); //set mode IOCON bank to 8bit mode See:Page-17 TABLE 3-4 at chip PDF
//bbb();
// setting IOCON to interrupt 6.bit=MIRROR:1 + 2.bit=ODR:1 (Open-Drain output of interrupt)
// 11100100 = 0xE4
aBus.writeByteSync(_parCh.addr, BNK1_IOCON_A , 0xE4); //set mode IOCON bank to 8bit mode See:Page-17 TABLE 3-4 at chip PDF
//bbb();
_processState = 2;
// Turn On ALL pins at start
if ((_parCh.startAllHIGH == true) && (_callerNode.lastState = -2)){
if (log2consol) console.log(" MCP Now Setting ALL pins to HIGH. A+B = 1111111111111111");
aBus.writeByteSync(_parCh.addr, BNK1_OLAT_A, 0xFF); //Set output A to 11111111
aBus.writeByteSync(_parCh.addr, BNK1_OLAT_B, 0xFF); //Set output B to 11111111
_parCh.allStates = 0xFFFF;
_parCh.startAllHIGH = false; // 1x running is enough. Turn it off now.
}
if (_parCh.allStates = -1) {
let ip1 = aBus.readByteSync(_parCh.addr, BNK1_GPIO_A);
let ip2 = aBus.readByteSync(_parCh.addr, BNK1_GPIO_B);
_parCh.lastTimeRed = performance.now();
ip2 = (ip2 << 8);
_parCh.allStates = ip1 + ip2; // adding together with "or" = ip1 | ip2;
if (log2consol) console.log(" MCP First READ OK. A="+ip1.toString(2)+" B="+ip2.toString(2)+" allStates=" + _parCh.allStates);
}
if (_isInput) {_parCh.isInputs = _parCh.isInputs | (1 << _bitNum) }
else {_parCh.isInputs = _parCh.isInputs & ~(1 << _bitNum) };
if (_bitNum < 8) {aBus.writeByteSync(_parCh.addr, BNK1_IODIR_A, _parCh.isInputs & 0xFF);} //update in out mode A
else {aBus.writeByteSync(_parCh.addr, BNK1_IODIR_B, (_parCh.isInputs >> 8) & 0xFF);} //update in out mode B
if (_isInput) {
if (_pullUp) { _parCh.pullUps = _parCh.pullUps | (1 << _bitNum) } else { _parCh.pullUps = _parCh.pullUps & ~(1 << _bitNum) };
if (log2consol) console.log(" MCP Input pullups=" + _parCh.pullUps);
if (_bitNum < 8) {aBus.writeByteSync(_parCh.addr, BNK1_GPPU_A , _parCh.pullUps & 0xFF);} //update input pull-up 100kQ resistor A
else {aBus.writeByteSync(_parCh.addr, BNK1_GPPU_B , (_parCh.pullUps >> 8) & 0xFF);} //update input pull-up 100kQ resistor B
if (_bitNum < 8) {aBus.writeByteSync(_parCh.addr, BNK1_IPOL_A, 0x00);} //disable Input invert(=POLarity) A
else {aBus.writeByteSync(_parCh.addr, BNK1_IPOL_B, 0x00);} //disable Input invert(=POLarity) B
if (_bitNum < 8) {aBus.writeByteSync(_parCh.addr, BNK1_GPINTEN_A, _parCh.isInputs & 0xFF);} //set INTerrupts ENable A
else {aBus.writeByteSync(_parCh.addr, BNK1_GPINTEN_B, (_parCh.isInputs >> 8) & 0xFF);} //set INTerrupts ENable B
if (_bitNum < 8) {aBus.writeByteSync(_parCh.addr, BNK1_INTCON_A, _parCh.isInputs & 0xFF);} //set INTerrupts CONtrol A
else {aBus.writeByteSync(_parCh.addr, BNK1_INTCON_B, (_parCh.isInputs >> 8) & 0xFF);} //set INTerrupts CONtrol B
}
} // MCP chip
_processState = 3;
aBus.closeSync();
aBus = null;
if (log2consol) console.log("OK. MCP/PCF Bit-initialization finished. Bus closed.");
_parCh.globalState = 1; // means: Working :)
_callerNode.lastState = getBit( _parCh.allStates, _bitNum ); // SET LAST STATE
_parCh.RW_finish(); // release Read-Write blocking
return true;
}
catch (err) {
_parCh.RW_finish();
if (_parCh.globalState < 60) _parCh.globalState += 2; // The whole chip in error mode, because the Bus could not be opened
_callerNode.lastState = -2;
_callerNode.error( busStateTexts[_processState] + " failed. Bus=" + _parCh.busNum + " Pin=" + _bitNum + "\n Error:" + err);
showState( _callerNode, false, _parCh.globalState );
aBus = null;
return false;
};
}
// ********** TIMER ********** // ... for input
// *************************** //
this.startChipTimer = function(_newInterval) {
if (log2consol) console.log(" MCP/PCF startChipTimer = " + _newInterval +" ms");
if ((_newInterval == undefined) || (_newInterval == 0)) {
console.log(" MCP/PCF Timer interval is UNDEFINED or 0 ! Timer will not be started, old may be cleared. Exiting.");
if (mainChip.chipTimer) clearInterval(mainChip.chipTimer);
return null;
}
if (mainChip.chipTimer != null) { // timer is already running
if (log2consol) console.log(" MCP/PCF Timer is already running");
if (mainChip.interval == _newInterval) {
if (log2consol) console.log(" MCP/PCF This timer interval is already set. There is nothing to do.");
return null;
} // nothing to do
clearInterval(mainChip.chipTimer); // clear old, so a new can started
mainChip.interval = _newInterval;
mainChip.chipTimer = null;
if (log2consol) console.log(" MCP/PCF Old timer destroyed.");
}
// STARTING a Timer in repeat mode
if (log2consol) console.log(" MCP/PCF Starting Timer now...");
mainChip.chipTimer = setInterval(mainChip.myTimer, mainChip.interval );
}
// START the timer now ... >> moved this part to InputNode-creation
//this.startChipTimer( this.interval ); // START, if any input nodes are available
this.myTimer = function(read1x) {
let _processState = 0;
const _addr = mainChip.addr;
if (isNaN(mainChip.busNum)) {
console.error(" MCP/PCF chip myTimer busNum is undefined. Exiting.");
mainChip.globalState += 2;
return false;
}
if ( ! mainChip.RW_check() ) {
console.error(" MCP/PCF myTimer is already running. Preventing to overlap > Exiting.");
return false;
} // prevent overlapping
const _readTime = performance.now(); // millisec. To change the Timer value, if a too short period is set.
try {
if (mainChip.lgc()) console.log(" MCP/PCF myTimer: opening bus... Time: " + new Date( new Date().getTime() ).toISOString().slice(11, -1) );
// this.on('error', function( i2cErr ) { timerErrorHandler(i2cErr) } ); // start async error handling
let _aBus = i2cModule.openSync(mainChip.busNum);
_processState = 1;
let ipAll = 0;
if (_addr >= 0x40) { // it is a PCF8574 chip
if (mainChip.lgc()) console.log(" PCF8574 >> Now reading 8bit. Addr=" + _addr/2);
ipAll = _aBus.receiveByteSync( Math.ceil( _addr / 2 ) );
if (mainChip.lgc()) console.log(" PCF8574 Read success ipAll00=" + ipAll.toString(2));
}
else {
if (mainChip.lgc()) console.log(" MCP23017 >> Now reading A+B banks... Typeof _aBUS:" + typeof(_aBus));
let ipA = _aBus.readByteSync(_addr, BNK1_GPIO_A);
let ipB = _aBus.readByteSync(_addr, BNK1_GPIO_B);
ipAll = ipA + (ipB << 8);
if (mainChip.lgc()) console.log(" MCP23017 Read success ipA00=" + ipA.toString(2) + " ipB00=" + ipB.toString(2) + " ipALL (dec)=" + ipAll);
}
// mainChipNode.lastTimeRed = performance.now();
_processState = 3;
_aBus.closeSync();
if (mainChip.globalState != 1) {
mainChip.globalState = 1; // successful read occured. No more "error state" or "uninitialised"
if (mainChip.interval != mainChip.origInterv) {
if (mainChip.lgc()) console.log(" MCP/PCF Starting ChipTimer. Interval=" + mainChip.origInterv);
mainChip.startChipTimer( mainChip.origInterv ); // this will delete the old timer and start normally again
}
}
// ********* Now checking ALL the possible nodes, if any of these needs to be updated
if (ipAll != mainChip.allStates){
let diffWord = ipAll ^ mainChip.allStates;
if (log2consol) console.log("!Change! of an i2c MCP/PCF input. IP MASK0000=" + ipAll.toString(2) + " Diff Mask0000=" + diffWord.toString(2));
for (let i=0; i < mainChip.MaxBits; i++){ // (MaxBits= 8 or 16)
if (diffWord & (1 << i)){
const newState = (((ipAll & (1 << i)) == 0) ? false : true);
if ( mainChip.ids[i] != null) {
const n = RED.nodes.getNode(mainChip.ids[i]);
if (log2consol) console.log(" MCP/PCF > ..searching for node i=" + i + " id=" + mainChip.ids[i] + " found node:" + n + " glob_inp=" + mainChip.isInputs);
if (n != null) { //&& (mainChipNode.isInputs & (1 << i)) > 0){ // check bit is used and is an input
if (log2consol) console.log(" MCP/PCF > Found a Node needs to be updated. Bit=" + i + " newState=" + newState + " LastState=" + n.lastState);
n.changed(newState, read1x);
}
}
}
}
mainChip.allStates = ipAll;
}
}
catch (err) {
if (mainChip.globalState < 63) mainChip.globalState += 2; // The whole chip in error mode, because the Bus could not be opened. Increasing next time-read to 2-4-6-..-60 sec.
err.discription = busStateTexts[_processState] + " failed.";
err.busNumber = mainChip.busNum;
err.address = _addr;
err.allStates = mainChip.allStates;
console.error(err.discription + " [Bus="+ mainChip.busNum +"] [Addr=" + _addr + "] [mainChipNode.allStates=" + mainChip.allStates + "]");
mainChip.error(err);
mainChip.RW_finish();
try {
// update ALL nodes, so they show "error"
for (var i=0; i < mainChip.MaxBits; i++){ //(MaxBits= 8 or 16)
if ( mainChip.ids[i] != null) {
const n = RED.nodes.getNode(mainChip.ids[i]);
if (n != null) {
showState(n, -2, mainChip.globalState);
//if (n.type == "MCP PCF In") n.changed(-2, true);
}
}
}
if ((_processState < 3) && !read1x) { // if !read1x = called from debounce ... it should not restart
mainChip.startChipTimer( mainChip.globalState * 1000 ); // re-try every 2-4-6-...60 sec.
}
}
catch (err){
console.error(err);
};
return false;
};
mainChip.RW_finish();
mainChip.lastTimeRed = performance.now(); //new Date().getTime();
mainChip.readLength = mainChip.lastTimeRed - _readTime;
if (! read1x) { // if "continuous read" is happening now
if (mainChip.interval < mainChip.readLength) { // the time the reading took was too long. Increased the interval to double of that (ms).
mainChip.warn(" MCP/PCF Interval (" + mainChip.interval + "ms) is too short for input. Setting new time = " + (mainChip.readLength * 2).toString());
mainChip.startChipTimer( Math.trunc(mainChip.readLength * 2)); // double the waiting period
} else
if ((mainChip.origInterv != mainChip.interval) && (mainChip.readLength < mainChip.origInterv)) {
mainChip.startChipTimer( mainChip.origInterv ); // set back original interval
}
}
return true;
}
this.on('close', function() { // stopping or deleting the Main-Chip-config
try {
if (mainChip.chipTimer) {
if (log2consol) console.log(" MCP/PCF Closing ... Clearing myTimer.");
clearInterval(mainChip.chipTimer);
mainChip.chipTimer = null;
}
}
catch (err) { console.error( " MCP/PCF Error while closing timer: " + err ); };
try {
global_i2c_bus_RW_ctx.set(_i2c_ctx_name, undefined); // clearing global context
} catch {}
});
}
// REGISTERING the main chip :
RED.nodes.registerType("mcp_pcf_chip", mcp_pcf_chipNode);
//INPUT SECTION
function mcp_pcf_inNode(_inputConfig) {
RED.nodes.createNode(this, _inputConfig);
var node = this;
this.bitNum = _inputConfig.bitNum;
this.pullUp = _inputConfig.pullUp;
this.invert = _inputConfig.invert;
this.debounce = _inputConfig.debounce;
this.deB_timer = null;
this.onMsg = _inputConfig.onMsg;
this.offMsg = _inputConfig.offMsg;
this.lastState = -2;
this.initOK = false;
// check Master-Chip setup
let _parentChipNode = RED.nodes.getNode(_inputConfig.chip);
this.parentChip = _parentChipNode;
if (!_parentChipNode) {
node.error("[MCP23017 + PCF8574] Master-global-Chip not found! Skipping in-node creation.");
showState(node, true, 0);
return null;
}
if (this.debounce > (_parentChipNode.interval - 20)) this.debounce = _parentChipNode.interval - 20; // 1 read needs 20ms >> so debounce should be that much shorter
if (this.debounce < 0) this.debounce = 0;
if (log2consol) console.log("---");
if (log2consol) console.log(">>> Initializing PCF8574 or MPC23017 Input node >> bitNum=" + this.bitNum + " pullUp=" + this.pullUp + " invert=" + this.invert + " id=" + this.id );
this.initOK = _parentChipNode.initializeBit (this.bitNum, true, this.pullUp, node);
showState(node, this.lastState, _parentChipNode.globalState); // shows uninit (yellow) or error (red)
this.deB_timer = null; //deBounce
this.on('close', function() {
if (node.deB_timer != null){
if (log2consol) console.log(" MCP/PCF > clearing old Debounce Input timer... [Bit=" + node.bitNum + "]");
clearTimeout(node.deB_timer);
node.deB_timer = null;
}
});
this.updateState = function(_state, _msg) {
if (node.lastState != _state) {
if (log2consol) console.log(">> MCP/PCF Input State Changed=" + _state + " [Bit=" + node.bitNum + "] id=" + node.id);
showState(node, _state, _parentChipNode.globalState); // will show inverted, if needed
node.lastState = _state;
}
if (_parentChipNode.globalState == 1){
const nullmsg = (_msg == null);
if (nullmsg) _msg = {};
const _stateINV = node.invert ? !_state : _state;
if ( _stateINV && node.onMsg ) _msg.payload = true;
if (! _stateINV && node.offMsg) _msg.payload = false;
if (nullmsg && (_msg.payload != null)) {node.send( _msg )} else return _msg; // if called from "read_1x" input >> do not send yet
}
}
this.changed = function( _state, _read1x ) {
if (node.deB_timer != null){
if (log2consol) console.log(" MCP/PCF > clearing old Debounce Input timer... [Bit=" + node.bitNum + "]");
clearTimeout(node.deB_timer);
node.deB_timer = null;
}
if ( !_read1x && (node.debounce != 0) && (_parentChipNode.globalState == 1) && ( (_state == true) || (_state == false) ) ) {
// Start debounce re-checks the last state
node.deB_state = _state;
node.deB_timer = setTimeout(node.deBounceEnd, node.debounce, _state);
if (log2consol) console.log(" MCP/PCF > New input Debounce timer set. TimeEnd=" + node.debounce + " State=" + _state);
}
else {
node.updateState(_state, null);
node.deB_state = _state;
}
}
this.deBounceEnd = function(_state){
node.deB_timer = null;
if (_parentChipNode.globalState > 3) {
if (log2consol) console.log(" MCP/PCF > Input timer Bounce CANCELED because chip is in Global-Error-State" );
return false;
}
let _now = performance.now();
if ((_now - node.lastTimeRed) < node.debounce * 1.2 ) {
_state = getBit( _parentChipNode.allStates, node.bitNum );
node.updateState(_state, null);
}
else {
let _read_success = _parentChipNode.myTimer(true); // forcing to re-read the current state from chip
if ( _read_success ){
_state = getBit( _parentChipNode.allStates, node.bitNum );
if (log2consol) console.log(" MCP/PCF > Input timer Bounce Ended. [NewState=" + _state + "] [Last State=" + node.lastState + "] [Deb.state=" + node.deB_state +"] [Bit=" + node.bitNum + "] Ellapsed=" + (_now - node.lastTimeRed) + "ms" );
if (_state == node.deB_state) node.updateState(_state, null);
}
else { node.deB_timer = setTimeout(node.deBounceEnd, node.debounce, _state); }
}
}
this.on('input', function(msg, send, done) { // triggering an Immediate read if payload is True or 1 >> to support Interrupts
if (msg.payload) {
let _parCh = node.parentChip;
if (log2consol) console.log(" MCP/PCF > Input Force-read triggered! msg.payload=" + msg.payload );
msg.immediateReadSuccess = _parCh.myTimer(true); // result is True, if succesfully red, false if any error occured during read
msg.readingTimeLengthMs = _parCh.readLength;
msg.allStatesRaw = _parCh.allStates;
msg.allBinary = msg.immediateReadSuccess ? _parCh.allStates.toString(2) : "";
msg = node.updateState( node.lastState, msg ); // << this will add (inverted) .payload
if (log2consol) console.log(" MCP/PCF > Now sending msg. msg.payload=" + msg.payload );
node.send(msg);
if (done) done();
}
});
this.on('close', function() { // stopping or deleting this node
let _parCh = node.parentChip;
try {
for (let i=0; i < _parCh.MaxBits; i++){ //(MaxBits= 8 or 16)
if ( _parCh.ids[i] == node.id) {
_parCh.ids[i] = null;
break;
}
}
}
catch (err) { console.error( " MCP/PCF Error while closing an In-Node: " + err ); };
});
if (this.initOK) { _parentChipNode.startChipTimer( _parentChipNode.interval ); }// START continuous read, if any input nodes are available
else { }
}
RED.nodes.registerType("MCP PCF In", mcp_pcf_inNode);
//OUTPUT SECTION
function mcp_pcf_outNode(_OutputConfig) {
RED.nodes.createNode(this, _OutputConfig);
var node = this;
node.bitNum = _OutputConfig.bitNum;
node.invert = _OutputConfig.invert;
node.lastState = -2;
node.initOK = false;
// check Master-Chip setup
let _parentChipNode = RED.nodes.getNode(_OutputConfig.chip); // this is not a visible node, but a hidden Chip-configuration one
node.parentChip = _parentChipNode;
if (!_parentChipNode) {
node.error("Master MCP23017 or PCF8574 Chip not set! Skipping node creation. Node.id=" + node.id);
showState(node, -2, 2);
return null;
}
node.startAllHIGH = _parentChipNode.startAllHIGH;
if (log2consol) console.log("---");
console.log(">>> Initializing PCF8574 or MPC23017 Output node >> invert=" + node.invert + " bitNum=" + node.bitNum + " ID=" + node.id);
this.initOK = _parentChipNode.initializeBit(node.bitNum, false, false, node);
showState(node, this.lastState, _parentChipNode.globalState); // shows uninit (yellow) or error (red)
this.changed = function( _state, _read1x ) {
showState(node, _state, _parentChipNode.globalState); // will show inverted, if needed
node.lastState = _state;
}
this.setOutput = function(_bitNum, _newState, _callerNode){
let _processState = 0;
if ( ! _callerNode) { consol.error(" MCP PCF setOutpup >> _callerNode=null !"); return false; }
let _parCh = _callerNode.parentChip;
if ( ! _parCh) { _callerNode.error(" MCP PCF setOutpup >> _callerNode.parentChip=null !"); return false; }
const _addr = _parCh.addr;
if ( ! _addr) { _callerNode.error(" MCP PCF setOutpup >> parentChip.addr=null !"); return false; }
if ( ! _parCh.RW_check() ) return false; // checks, if there is any
try {
let ip8 = -1;
let ip16 = -1;
if (log2consol) console.log(" MCP/PCF setOutput "+ _callerNode.id +" > Addr=" + _addr + " BitNum=" + _bitNum + " _newState:" + _newState +" > opening bus...");
let _aBus = i2cModule.openSync(_parCh.busNum);
_processState = 1;
// Set ALL pins to 0000000000000000 or 1111111111111111
if (_bitNum == -1) {
let on_off = _newState ? 0xFF : 0x00;
if (_addr >= 0x40) { // This is a PCF 8bit chip
_aBus.sendByteSync (Math.ceil( _addr / 2 ), on_off & 0xFF);
_parCh.lastTimeRed = performance.now();
if (log2consol) console.log(" PCF 8bit chip ALL set to=" + on_off);
_parCh.allStates = on_off;
}
else { // MCP chip
_aBus.writeByteSync(_addr, BNK1_OLAT_A, on_off & 0xFF); //Set output A
_aBus.writeByteSync(_addr, BNK1_OLAT_B, on_off & 0xFF); //Set output B
_parCh.lastTimeRed = performance.now();
if (log2consol) console.log(" MPC 16bit chip ALL set to=" + on_off);
_parCh.allStates = _newState ? 0xFFFF : 0x00;
}
for (let i=0; i < _parCh.MaxBits; i++){ //(MaxBits= 8 or 16)
if ( _parCh.ids[i] != null) {
const n = RED.nodes.getNode(_parCh.ids[i]);
if (n != null) {
showState( n , _newState, _parCh.globalState);
n.lastState = _newState;
}
}
}
}
// Set one pin only to: 0 or 1
else {
// first it reads the current state of pins of 1 bank (A or B) (takes 4ms)
if (_bitNum < 8) {
if (_addr >= 0x40) ip8 = _aBus.receiveByteSync(Math.ceil( _addr / 2 )); // PCF chip
else ip8 = _aBus.readByteSync(_addr, BNK1_GPIO_A);
ip16 = ip8;
} else {
ip8 = _aBus.readByteSync(_addr, BNK1_GPIO_B);
ip16 = (ip8 << 8); // make it 16 bit
}
_parCh.lastTimeRed = performance.now();
if (log2consol) console.log(" Read before write success ip8="+ ip8 + " ip16="+ ip16 );
ip16 = updateBit(ip16, _bitNum, _newState); //16 bit
if (log2consol) console.log(" Updated bit ip16="+ ip16 );
//if (_newState) {ip1 = ip1 | 1 << _bitNum ;}
//else {ip1 = ip1 & ~(1 << _bitNum);}
_processState = 2;
if (_addr >= 0x40) _aBus.sendByteSync(Math.ceil( _addr / 2 ), ip16 & 0xFF); // PCF chip
else {
if (_bitNum < 8) {_aBus.writeByteSync(_addr, BNK1_OLAT_A, ip16 & 0xFF);} //Set output A
else {_aBus.writeByteSync(_addr, BNK1_OLAT_B, (ip16 >> 8) & 0xFF);} //Set output B
}
}
_processState = 3;
_aBus.closeSync();
_aBus = null;
_parCh.globalState = 1; // working
let n = _callerNode;
if ( n.bitNum != _bitNum ) n = _parCh.ids[_bitNum]; // if the function was called with a "direct-msg-control-method" change the right node if exists
if (n != null) showState(n, _newState, 1);
if (log2consol) console.log(":-) MCP/PCF setOutput finished. Closing bus. ip1="+ ip16 );
_parCh.RW_finish();
return true;
}
catch (err) {
if (_parCh.globalState < 60) _parCh.globalState += 2; // The whole chip in error mode, because the Bus could not be opened
_callerNode.lastState = -2;
showState(_callerNode, -2, 2);
let _ee = busStateTexts[_processState] + " failed. Bus="+ _parCh.busNum +" Addr=" + _addr + " Pin="+_bitNum + " NewState=" + _newState;
console.error(_ee + " " + err);
_callerNode.error([_ee, err]);
_aBus = null;
_parCh.RW_finish();
return false;
};
}
this.on('input', function(msg) {
let _parCh = node.parentChip;
if (!node.initOK) {
if (log2consol) console.log(" MCP/PCF Out > New msg recieved, but Node not initialized yet. ID=" + node.id + " bitNum=" + node.bitNum);
node.initOK = _parCh.initializeBit(node.bitNum, false, false, this.id);
if (!node.initOK) {return null;}
}
if (! _parCh) { consol.error(" MCP PCF Out >> input msg recieved, but ParentChip of Node is null!"); return null }
// *** SET only 1 pin via msg JSON *** //
if ( msg.payload == -1 ) {
if (log2consol) console.log(" MCP/PCF > Set direct pin Chip Addr=" + _parCh.addr);
const _Pin1 = parseInt( msg.pin );
if ((_Pin1 == NaN) || (_Pin1 < 0) || (_Pin1 > 15) ) {
node.error("msg.pin not properly set for direct control of a MCP or PCF chip. It must be a number between 0-7 or 8-15");
return false;
}
if (msg.state == null) {
node.error("msg.state not set for direct control of a MCP or PCF chip.");
return false;
}
const _OnOff1 = (msg.state == true) || (msg.state == 1); //safe boolean conversion
node.setOutput(_Pin1, _OnOff1, node);
} else
if ( msg.payload == "all0" ) {
if (log2consol) console.log(" MCP/PCF > Set ALL pins to 0000... Chip Addr=" + _parCh.addr);
node.setOutput(-1, false, node);
} else
if ( msg.payload == "all1" ) {
if (log2consol) console.log(" MCP/PCF > Set ALL pins to 1111... Chip Addr=" + _parCh.addr);
node.setOutput(-1, true, node);
} else
{
//console.log("OUT: NEW input... payl=" + msg.payload);
let _pinOn = (msg.payload == true) || (msg.payload == 1); //safe boolean conversion
let _invPinOn = node.invert ? !_pinOn : _pinOn;
if (node.setOutput(node.bitNum, _invPinOn, node)) {
showState(node, _invPinOn, _parCh.globalState);
node.lastState = _invPinOn;
}
}
}) ;
this.on('close', function() { // stopping or deleting this node
let _parCh = node.parentChip;
try {
for (let i=0; i < _parCh.MaxBits; i++){ //(MaxBits= 8 or 16)
if ( _parCh.ids[i] == node.id) {
_parCh.ids[i] = null;
break;
}
}
}
catch (err) { console.error( " MCP/PCF Error while closing an In-Node: " + err ); };
});
}
RED.nodes.registerType("MCP PCF Out", mcp_pcf_outNode);
}