node-red-contrib-knx-ultimate 1.3.10 → 1.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/KNXEngine/KNXClient.js +110 -66
- package/KNXEngine/protocol/KNXConnectResponse.js +15 -15
- package/KNXEngine/protocol/KNXConstants.js +9 -8
- package/KNXEngine/protocol/KNXUtils.js +22 -3
- package/KNXEngine/protocol/cEMI/ControlField.js +1 -1
- package/nodes/knxUltimate-config.html +1 -1
- package/nodes/knxUltimate-config.js +11 -16
- package/nodes/knxUltimate.js +8 -6
- package/nodes/locales/de/knxUltimate-config.json +2 -2
- package/nodes/locales/en-US/knxUltimate-config.json +2 -2
- package/nodes/locales/it/knxUltimate-config.json +2 -2
- package/nodes/locales/zh-CN/knxUltimate-config.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
<br/>
|
|
6
6
|
<p>
|
|
7
|
+
<b>Version 1.3.14</b> - 26 December 2021<br/>
|
|
8
|
+
- KNXEngine: ACK management: the not acknowledged message will be re-transmitted once, then the connection will be dropped, as per KNX specs.<br/>
|
|
9
|
+
- KNXEngine: ACK management: the telegram's queue to be sent to the KNX BUS will be paused during the ACK waiting.<br/>
|
|
10
|
+
- KNXEngine: Routing: now the routing_busy and routing_lost_messages telegrams sent by the KNX/IP Router are handled.<br/>
|
|
11
|
+
</p>
|
|
12
|
+
<p>
|
|
13
|
+
<b>Version 1.3.13</b> - 25 December 2021<br/>
|
|
14
|
+
- KNXEngine: when in tunneling and suppress ACK request is disabled, the error is raised only after 3° failed ACK reception instead of 1°.<br/>
|
|
15
|
+
- KNXEngine: at disconnection, delete all pending ACK requests timer.<br/>
|
|
16
|
+
- Warning: if you've suppressed the ACK requests, in some cases the node cannot detect the disconnection. In this case, please use the KNX Watchdog to detect the disconnecitons and reconnect.<br/>
|
|
17
|
+
</p>
|
|
18
|
+
<p>
|
|
19
|
+
<b>Version 1.3.12</b> - December 2021<br/>
|
|
20
|
+
- KNX-Ultimate DEVICE node: added the validation of Group Address while deploy, with support for modern addressing up to 31/7/255.<br/>
|
|
21
|
+
</p>
|
|
22
|
+
<p>
|
|
7
23
|
<b>Version 1.3.10</b> - December 2021<br/>
|
|
8
24
|
- FIX: fixed a stupid "Disconnected by Message length mismatch 8/16" error due to a dumb find/replace error in the code.<br/>
|
|
9
25
|
- Added some more log to help resolving issues.<br/>
|
package/KNXEngine/KNXClient.js
CHANGED
|
@@ -86,7 +86,7 @@ class KNXClient extends EventEmitter {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
super();
|
|
89
|
-
this._clientTunnelSeqNumber =
|
|
89
|
+
this._clientTunnelSeqNumber = -1;
|
|
90
90
|
this._options = options;//Object.assign(optionsDefaults, options);
|
|
91
91
|
this._options.connectionKeepAliveTimeout = KNXConstants.KNX_CONSTANTS.CONNECTION_ALIVE_TIME,
|
|
92
92
|
this._localPort = null;
|
|
@@ -169,10 +169,11 @@ class KNXClient extends EventEmitter {
|
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
this._clientTunnelSeqNumber =
|
|
172
|
+
this._clientTunnelSeqNumber = -1;
|
|
173
173
|
this._channelID = null;
|
|
174
174
|
this._connectionState = STATE.DISCONNECTED;
|
|
175
|
-
this._tunnelReqTimer =
|
|
175
|
+
this._tunnelReqTimer = null;
|
|
176
|
+
this._numFailedTelegramACK = 0; // 25/12/2021 Keep count of the failed tunnelig ACK telegrams
|
|
176
177
|
|
|
177
178
|
}
|
|
178
179
|
get channelID() {
|
|
@@ -239,6 +240,7 @@ class KNXClient extends EventEmitter {
|
|
|
239
240
|
});
|
|
240
241
|
}
|
|
241
242
|
send(knxPacket) {
|
|
243
|
+
|
|
242
244
|
// Logging
|
|
243
245
|
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
244
246
|
try {
|
|
@@ -341,9 +343,11 @@ class KNXClient extends EventEmitter {
|
|
|
341
343
|
cEMIMessage.control.priority = 3;
|
|
342
344
|
cEMIMessage.control.addressType = 1;
|
|
343
345
|
cEMIMessage.control.hopCount = 6;
|
|
346
|
+
this._incSeqNumber(); // 26/12/2021
|
|
344
347
|
const seqNum = this._getSeqNumber();
|
|
345
348
|
const knxPacketRequest = KNXProtocol.KNXProtocol.newKNXTunnelingRequest(this._channelID, seqNum, cEMIMessage);
|
|
346
|
-
if (!this._options.suppress_ack_ldatareq) this.
|
|
349
|
+
if (!this._options.suppress_ack_ldatareq) this._setTimerWaitingForACK(knxPacketRequest);
|
|
350
|
+
//if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error("this._tunnelReqTimer "+ this._tunnelReqTimer.size);
|
|
347
351
|
this.send(knxPacketRequest);
|
|
348
352
|
// 06/12/2021 Echo the sent telegram. Last parameter is the echo true/false
|
|
349
353
|
try {
|
|
@@ -385,9 +389,10 @@ class KNXClient extends EventEmitter {
|
|
|
385
389
|
cEMIMessage.control.priority = 3;
|
|
386
390
|
cEMIMessage.control.addressType = 1;
|
|
387
391
|
cEMIMessage.control.hopCount = 6;
|
|
392
|
+
this._incSeqNumber(); // 26/12/2021
|
|
388
393
|
const seqNum = this._getSeqNumber();
|
|
389
394
|
const knxPacketRequest = KNXProtocol.KNXProtocol.newKNXTunnelingRequest(this._channelID, seqNum, cEMIMessage);
|
|
390
|
-
if (!this._options.suppress_ack_ldatareq) this.
|
|
395
|
+
if (!this._options.suppress_ack_ldatareq) this._setTimerWaitingForACK(knxPacketRequest);
|
|
391
396
|
this.send(knxPacketRequest);
|
|
392
397
|
// 06/12/2021 Echo the sent telegram. Last parameter is the echo true/false
|
|
393
398
|
try {
|
|
@@ -426,9 +431,10 @@ class KNXClient extends EventEmitter {
|
|
|
426
431
|
cEMIMessage.control.priority = 3;
|
|
427
432
|
cEMIMessage.control.addressType = 1;
|
|
428
433
|
cEMIMessage.control.hopCount = 6;
|
|
434
|
+
this._incSeqNumber(); // 26/12/2021
|
|
429
435
|
const seqNum = this._getSeqNumber();
|
|
430
436
|
const knxPacketRequest = KNXProtocol.KNXProtocol.newKNXTunnelingRequest(this._channelID, seqNum, cEMIMessage);
|
|
431
|
-
if (!this._options.suppress_ack_ldatareq) this.
|
|
437
|
+
if (!this._options.suppress_ack_ldatareq) this._setTimerWaitingForACK(knxPacketRequest);
|
|
432
438
|
this.send(knxPacketRequest);
|
|
433
439
|
// 06/12/2021 Echo the sent telegram. Last parameter is the echo true/false
|
|
434
440
|
try {
|
|
@@ -474,9 +480,10 @@ class KNXClient extends EventEmitter {
|
|
|
474
480
|
cEMIMessage.control.priority = 3;
|
|
475
481
|
cEMIMessage.control.addressType = 1;
|
|
476
482
|
cEMIMessage.control.hopCount = 6;
|
|
483
|
+
this._incSeqNumber(); // 26/12/2021
|
|
477
484
|
const seqNum = this._getSeqNumber();
|
|
478
485
|
const knxPacketRequest = KNXProtocol.KNXProtocol.newKNXTunnelingRequest(this._channelID, seqNum, cEMIMessage);
|
|
479
|
-
if (!this._options.suppress_ack_ldatareq) this.
|
|
486
|
+
if (!this._options.suppress_ack_ldatareq) this._setTimerWaitingForACK(knxPacketRequest);
|
|
480
487
|
this.send(knxPacketRequest);
|
|
481
488
|
// 06/12/2021 Echo the sent telegram. Last parameter is the echo true/false
|
|
482
489
|
try {
|
|
@@ -544,6 +551,8 @@ class KNXClient extends EventEmitter {
|
|
|
544
551
|
}
|
|
545
552
|
|
|
546
553
|
this._connectionState = STATE.CONNECTING;
|
|
554
|
+
this._numFailedTelegramACK = 0; // 25/12/2021 Reset the failed ACK counter
|
|
555
|
+
this._clearToSend = true; // 26/12/2021 allow to send
|
|
547
556
|
|
|
548
557
|
if (this._timer !== null) clearTimeout(this._timer);
|
|
549
558
|
|
|
@@ -564,7 +573,7 @@ class KNXClient extends EventEmitter {
|
|
|
564
573
|
|
|
565
574
|
}, 1000 * KNXConstants.KNX_CONSTANTS.CONNECT_REQUEST_TIMEOUT);
|
|
566
575
|
this._awaitingResponseType = KNXConstants.KNX_CONSTANTS.CONNECT_RESPONSE;
|
|
567
|
-
this._clientTunnelSeqNumber =
|
|
576
|
+
this._clientTunnelSeqNumber = -1;
|
|
568
577
|
this._sendConnectRequestMessage(new TunnelCRI.TunnelCRI(knxLayer));
|
|
569
578
|
|
|
570
579
|
} else if (this._options.hostProtocol === "TunnelTCP") {
|
|
@@ -582,12 +591,11 @@ class KNXClient extends EventEmitter {
|
|
|
582
591
|
if (conn._options.isSecureKNXEnabled) conn._sendSecureSessionRequestMessage(new TunnelCRI.TunnelCRI(knxLayer));
|
|
583
592
|
});
|
|
584
593
|
|
|
585
|
-
|
|
586
594
|
} else {
|
|
587
595
|
|
|
588
596
|
// Multicast
|
|
589
597
|
this._connectionState = STATE.CONNECTED;
|
|
590
|
-
this._clientTunnelSeqNumber =
|
|
598
|
+
this._clientTunnelSeqNumber = -1;
|
|
591
599
|
try {
|
|
592
600
|
this.emit(KNXClientEvents.connected, this._options);
|
|
593
601
|
} catch (error) {
|
|
@@ -645,6 +653,7 @@ class KNXClient extends EventEmitter {
|
|
|
645
653
|
if (this._timerTimeoutSendDisconnectRequestMessagetimer !== null) clearTimeout(this._timerTimeoutSendDisconnectRequestMessagetimer);
|
|
646
654
|
this._timerTimeoutSendDisconnectRequestMessage = null;
|
|
647
655
|
if (this._timer !== null) clearTimeout(this._timer);
|
|
656
|
+
if (this._tunnelReqTimer !== null) clearTimeout(this._tunnelReqTimer);
|
|
648
657
|
this.stopHeartBeat();
|
|
649
658
|
this._connectionState = STATE.DISCONNECTED;
|
|
650
659
|
try {
|
|
@@ -652,9 +661,10 @@ class KNXClient extends EventEmitter {
|
|
|
652
661
|
} catch (error) {
|
|
653
662
|
}
|
|
654
663
|
|
|
655
|
-
this._clientTunnelSeqNumber =
|
|
664
|
+
this._clientTunnelSeqNumber = -1;
|
|
665
|
+
this._clearToSend = true; // 26/12/2021 allow to send
|
|
656
666
|
this._channelID = null;
|
|
657
|
-
|
|
667
|
+
|
|
658
668
|
// 08/12/2021
|
|
659
669
|
try {
|
|
660
670
|
this._clientSocket.close();
|
|
@@ -672,46 +682,77 @@ class KNXClient extends EventEmitter {
|
|
|
672
682
|
_getSeqNumber() {
|
|
673
683
|
return this._clientTunnelSeqNumber;
|
|
674
684
|
}
|
|
685
|
+
// 26/12/2021 Handle the busy state, for example while waiting for ACK
|
|
686
|
+
_getClearToSend() {
|
|
687
|
+
return (this._clearToSend !== undefined ? this._clearToSend : true);
|
|
688
|
+
}
|
|
689
|
+
|
|
675
690
|
_incSeqNumber(seq) {
|
|
676
|
-
this._clientTunnelSeqNumber
|
|
691
|
+
this._clientTunnelSeqNumber++;
|
|
677
692
|
if (this._clientTunnelSeqNumber > 255) {
|
|
678
693
|
this._clientTunnelSeqNumber = 0;
|
|
679
694
|
}
|
|
680
695
|
return this._clientTunnelSeqNumber;
|
|
681
696
|
}
|
|
682
|
-
|
|
683
697
|
_keyFromCEMIMessage(cEMIMessage) {
|
|
684
698
|
return cEMIMessage.dstAddress.toString();
|
|
685
699
|
}
|
|
686
|
-
|
|
700
|
+
_setTimerWaitingForACK(knxTunnelingRequest) {
|
|
687
701
|
const timeoutErr = new errors.RequestTimeoutError(`RequestTimeoutError seqCounter:${knxTunnelingRequest.seqCounter}, DestAddr:${knxTunnelingRequest.cEMIMessage.dstAddress.toString() || "Non definito"}, AckRequested:${knxTunnelingRequest.cEMIMessage.control.ack}, timed out waiting telegram acknowledge by ${this._options.ipAddr || "No Peer host detected"}`);
|
|
688
|
-
|
|
689
|
-
this.
|
|
690
|
-
|
|
691
|
-
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error("KNXClient: _setTimerAndCallback: " + (timeoutErr.message || "Undef error"));
|
|
702
|
+
if (this._tunnelReqTimer !== null) clearTimeout(this._tunnelReqTimer);
|
|
703
|
+
this._clearToSend = false; // 26/12/2021 stop sending until ACK received
|
|
704
|
+
this._tunnelReqTimer = setTimeout(() => {
|
|
692
705
|
try {
|
|
693
|
-
this.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
706
|
+
this._numFailedTelegramACK += 1;
|
|
707
|
+
if (this._numFailedTelegramACK > 2) {
|
|
708
|
+
this._numFailedTelegramACK = 0;
|
|
709
|
+
this._clearToSend = true;
|
|
710
|
+
this.emit(KNXClientEvents.error, timeoutErr);
|
|
711
|
+
} else {
|
|
712
|
+
// 26/12/2021 // If no ACK received, resend the datagram once with the same sequence number
|
|
713
|
+
this._setTimerWaitingForACK(knxTunnelingRequest);
|
|
714
|
+
this.send(knxTunnelingRequest);
|
|
715
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error("KNXClient: _setTimerWaitingForACK: " + (timeoutErr.message || "Undef error") + " no ACK received. Retransmit datagram with seqNumber " + this._getSeqNumber());
|
|
716
|
+
}
|
|
717
|
+
} catch (error) { }
|
|
718
|
+
}, KNXConstants.KNX_CONSTANTS.TUNNELING_REQUEST_TIMEOUT * 1000);
|
|
719
|
+
|
|
697
720
|
}
|
|
698
721
|
_processInboundMessage(msg, rinfo) {
|
|
699
722
|
|
|
700
723
|
try {
|
|
701
724
|
|
|
702
725
|
// Composing debug string
|
|
703
|
-
var sProcessInboundLog = "???";
|
|
704
|
-
try {
|
|
705
|
-
sProcessInboundLog = "Data received: " + msg.toString("hex");
|
|
706
|
-
sProcessInboundLog += " srcAddress: " + JSON.stringify(rinfo);
|
|
707
|
-
} catch (error) { }
|
|
708
726
|
try {
|
|
709
|
-
if (this.sysLogger !== undefined && this.sysLogger !== null)
|
|
727
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
728
|
+
var sProcessInboundLog = "???";
|
|
729
|
+
try {
|
|
730
|
+
sProcessInboundLog = "Data received: " + msg.toString("hex");
|
|
731
|
+
sProcessInboundLog += " srcAddress: " + JSON.stringify(rinfo);
|
|
732
|
+
} catch (error) { }
|
|
733
|
+
this.sysLogger.trace("Received KNX packet: _processInboundMessage, " + sProcessInboundLog + " ChannelID:" + this._channelID || "??" + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
734
|
+
}
|
|
710
735
|
} catch (error) { }
|
|
711
736
|
|
|
712
737
|
const { knxHeader, knxMessage } = KNXProtocol.KNXProtocol.parseMessage(msg);
|
|
713
738
|
|
|
739
|
+
// 26/12/2021 ROUTING LOST MESSAGE OR BUSY
|
|
740
|
+
if (knxHeader.service_type === KNXConstants.KNX_CONSTANTS.ROUTING_LOST_MESSAGE) {
|
|
741
|
+
try {
|
|
742
|
+
this.emit(KNXClientEvents.error, new Error('ROUTING_LOST_MESSAGE'));
|
|
743
|
+
this._setDisconnected();
|
|
744
|
+
return;
|
|
745
|
+
} catch (error) { }
|
|
746
|
+
} else if (knxHeader.service_type === KNXConstants.KNX_CONSTANTS.ROUTING_BUSY) {
|
|
747
|
+
try {
|
|
748
|
+
this.emit(KNXClientEvents.error, new Error('ROUTING_BUSY'));
|
|
749
|
+
this._setDisconnected();
|
|
750
|
+
return;
|
|
751
|
+
} catch (error) { }
|
|
752
|
+
}
|
|
753
|
+
|
|
714
754
|
if (knxHeader.service_type === KNXConstants.KNX_CONSTANTS.SEARCH_RESPONSE) {
|
|
755
|
+
|
|
715
756
|
if (this._discovery_timer == null) {
|
|
716
757
|
return;
|
|
717
758
|
}
|
|
@@ -729,8 +770,7 @@ class KNXClient extends EventEmitter {
|
|
|
729
770
|
if (knxConnectResponse.status !== KNXConstants.ConnectionStatus.E_NO_ERROR) {
|
|
730
771
|
try {
|
|
731
772
|
this.emit(KNXClientEvents.error, KNXConnectResponse.KNXConnectResponse.statusToString(knxConnectResponse.status));
|
|
732
|
-
} catch (error) {
|
|
733
|
-
}
|
|
773
|
+
} catch (error) { }
|
|
734
774
|
this._setDisconnected();
|
|
735
775
|
return;
|
|
736
776
|
}
|
|
@@ -785,34 +825,38 @@ class KNXClient extends EventEmitter {
|
|
|
785
825
|
if (knxTunnelingRequest.cEMIMessage.msgCode === CEMIConstants.CEMIConstants.L_DATA_IND) {
|
|
786
826
|
|
|
787
827
|
// Composing debug string
|
|
788
|
-
let sDebugString = "???";
|
|
789
828
|
try {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
829
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
830
|
+
let sDebugString = "???";
|
|
831
|
+
try {
|
|
832
|
+
sDebugString = "Data: " + JSON.stringify(knxTunnelingRequest.cEMIMessage.npdu);
|
|
833
|
+
sDebugString += " srcAddress: " + knxTunnelingRequest.cEMIMessage.srcAddress.toString();
|
|
834
|
+
sDebugString += " dstAddress: " + knxTunnelingRequest.cEMIMessage.dstAddress.toString();
|
|
835
|
+
} catch (error) { }
|
|
836
|
+
this.sysLogger.debug("Received KNX packet: TUNNELING: L_DATA_IND, " + sDebugString + " ChannelID:" + this._channelID + " seqCounter:" + knxTunnelingRequest.seqCounter + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
837
|
+
}
|
|
797
838
|
} catch (error) { }
|
|
798
839
|
|
|
799
840
|
try {
|
|
800
841
|
this.emit(KNXClientEvents.indication, knxTunnelingRequest, false, msg.toString("hex"));
|
|
801
|
-
} catch (error) {
|
|
802
|
-
}
|
|
842
|
+
} catch (error) { }
|
|
803
843
|
|
|
804
|
-
}
|
|
805
|
-
else if (knxTunnelingRequest.cEMIMessage.msgCode === CEMIConstants.CEMIConstants.L_DATA_CON) {
|
|
844
|
+
} else if (knxTunnelingRequest.cEMIMessage.msgCode === CEMIConstants.CEMIConstants.L_DATA_CON) {
|
|
806
845
|
|
|
807
846
|
try {
|
|
808
847
|
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug("Received KNX packet: TUNNELING: L_DATA_CON, ChannelID:" + this._channelID + " seqCounter:" + knxTunnelingRequest.seqCounter + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
809
848
|
} catch (error) { }
|
|
810
849
|
|
|
811
850
|
}
|
|
851
|
+
|
|
852
|
+
// 26/12/2021 send the ACK if the server requestet that
|
|
853
|
+
// Then REMOVED, because some interfaces sets the "ack request" always to 0 even if it needs ack.
|
|
854
|
+
//if (knxMessage.cEMIMessage.control.ack){
|
|
812
855
|
const knxTunnelAck = KNXProtocol.KNXProtocol.newKNXTunnelingACK(knxTunnelingRequest.channelID, knxTunnelingRequest.seqCounter, KNXConstants.KNX_CONSTANTS.E_NO_ERROR);
|
|
813
856
|
this.send(knxTunnelAck);
|
|
814
|
-
|
|
815
|
-
|
|
857
|
+
//}
|
|
858
|
+
|
|
859
|
+
} else if (knxHeader.service_type === KNXConstants.KNX_CONSTANTS.TUNNELING_ACK) {
|
|
816
860
|
//const knxTunnelingAck = lodash.cloneDeep(knxMessage);
|
|
817
861
|
const knxTunnelingAck = knxMessage;
|
|
818
862
|
if (knxTunnelingAck.channelID !== this._channelID) {
|
|
@@ -823,19 +867,17 @@ class KNXClient extends EventEmitter {
|
|
|
823
867
|
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug("Received KNX packet: TUNNELING: TUNNELING_ACK, ChannelID:" + this._channelID + " seqCounter:" + knxTunnelingAck.seqCounter + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
824
868
|
} catch (error) { }
|
|
825
869
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
// Avoid warning if the KNXEngine is set to ignore ACK's telegrams
|
|
838
|
-
if (!this._options.suppress_ack_ldatareq) {
|
|
870
|
+
// Check the received ACK sequence number
|
|
871
|
+
if (!this._options.suppress_ack_ldatareq) {
|
|
872
|
+
if (knxTunnelingAck.seqCounter === this._getSeqNumber()) {
|
|
873
|
+
if (this._tunnelReqTimer !== null) clearTimeout(this._tunnelReqTimer);
|
|
874
|
+
this._numFailedTelegramACK = 0; // 25/12/2021 clear the current ACK failed telegram number
|
|
875
|
+
this._clearToSend = true; // I'm ready to send a new datagram now
|
|
876
|
+
try {
|
|
877
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug("Received KNX packet: TUNNELING: DELETED_TUNNELING_ACK FROM PENDING ACK's, ChannelID:" + this._channelID + " seqCounter:" + knxTunnelingAck.seqCounter + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
878
|
+
} catch (error) { }
|
|
879
|
+
} else {
|
|
880
|
+
// Inform that i received an ACK with an unexpected sequence number
|
|
839
881
|
try {
|
|
840
882
|
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error("Received KNX packet: TUNNELING: Unexpected Tunnel Ack with seqCounter = " + knxTunnelingAck.seqCounter);
|
|
841
883
|
} catch (error) { }
|
|
@@ -850,15 +892,16 @@ class KNXClient extends EventEmitter {
|
|
|
850
892
|
if (knxRoutingInd.cEMIMessage.msgCode === CEMIConstants.CEMIConstants.L_DATA_IND) {
|
|
851
893
|
|
|
852
894
|
// Composing debug string
|
|
853
|
-
let sDebugString = "???";
|
|
854
|
-
try {
|
|
855
|
-
sDebugString = "Data: " + JSON.stringify(knxRoutingInd.cEMIMessage.npdu);
|
|
856
|
-
sDebugString += " srcAddress: " + knxRoutingInd.cEMIMessage.srcAddress.toString();
|
|
857
|
-
sDebugString += " dstAddress: " + knxRoutingInd.cEMIMessage.dstAddress.toString();
|
|
858
|
-
} catch (error) { }
|
|
859
|
-
|
|
860
895
|
try {
|
|
861
|
-
if (this.sysLogger !== undefined && this.sysLogger !== null)
|
|
896
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
897
|
+
let sDebugString = "???";
|
|
898
|
+
try {
|
|
899
|
+
sDebugString = "Data: " + JSON.stringify(knxRoutingInd.cEMIMessage.npdu);
|
|
900
|
+
sDebugString += " srcAddress: " + knxRoutingInd.cEMIMessage.srcAddress.toString();
|
|
901
|
+
sDebugString += " dstAddress: " + knxRoutingInd.cEMIMessage.dstAddress.toString();
|
|
902
|
+
} catch (error) { }
|
|
903
|
+
this.sysLogger.debug("Received KNX packet: ROUTING: L_DATA_IND, " + sDebugString + " Host:" + this._options.ipAddr + ":" + this._options.ipPort);
|
|
904
|
+
}
|
|
862
905
|
} catch (error) { }
|
|
863
906
|
|
|
864
907
|
try {
|
|
@@ -915,6 +958,7 @@ class KNXClient extends EventEmitter {
|
|
|
915
958
|
} catch (error) { }
|
|
916
959
|
try {
|
|
917
960
|
this.emit(KNXClientEvents.error, e);
|
|
961
|
+
this._setDisconnected();
|
|
918
962
|
} catch (error) { }
|
|
919
963
|
|
|
920
964
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KNXConnectResponse = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
class KNXConnectResponse extends
|
|
4
|
+
const KNXConstants = require("./KNXConstants");
|
|
5
|
+
const KNXPacket = require("./KNXPacket");
|
|
6
|
+
const HPAI = require("./HPAI");
|
|
7
|
+
const CRD = require("./CRD");
|
|
8
|
+
class KNXConnectResponse extends KNXPacket.KNXPacket {
|
|
9
9
|
constructor(channelID, status, hpai, crd) {
|
|
10
|
-
super(
|
|
10
|
+
super(KNXConstants.KNX_CONSTANTS.CONNECT_RESPONSE, hpai == null ? 2 : 2 + hpai.length + crd.length);
|
|
11
11
|
this.channelID = channelID;
|
|
12
12
|
this.status = status;
|
|
13
13
|
this.hpai = hpai;
|
|
@@ -21,27 +21,27 @@ class KNXConnectResponse extends KNXPacket_1.KNXPacket {
|
|
|
21
21
|
const status = buffer.readUInt8(offset++);
|
|
22
22
|
let hpai, crd;
|
|
23
23
|
if (offset < buffer.length) {
|
|
24
|
-
hpai =
|
|
24
|
+
hpai = HPAI.HPAI.createFromBuffer(buffer, offset);
|
|
25
25
|
offset += hpai.length;
|
|
26
|
-
crd =
|
|
26
|
+
crd = CRD.CRD.createFromBuffer(buffer, offset);
|
|
27
27
|
}
|
|
28
28
|
return new KNXConnectResponse(channelID, status, hpai, crd);
|
|
29
29
|
}
|
|
30
30
|
static statusToString(status) {
|
|
31
31
|
switch (status) {
|
|
32
|
-
case
|
|
32
|
+
case KNXConstants.KNX_CONSTANTS.E_SEQUENCE_NUMBER:
|
|
33
33
|
return 'Invalid Sequence Number';
|
|
34
|
-
case
|
|
34
|
+
case KNXConstants.KNX_CONSTANTS.E_CONNECTION_TYPE:
|
|
35
35
|
return 'Invalid Connection Type';
|
|
36
|
-
case
|
|
36
|
+
case KNXConstants.KNX_CONSTANTS.E_CONNECTION_OPTION:
|
|
37
37
|
return 'Invalid Connection Option';
|
|
38
|
-
case
|
|
38
|
+
case KNXConstants.KNX_CONSTANTS.E_NO_MORE_CONNECTIONS:
|
|
39
39
|
return 'No More Connections';
|
|
40
|
-
case
|
|
40
|
+
case KNXConstants.KNX_CONSTANTS.E_DATA_CONNECTION:
|
|
41
41
|
return 'Invalid Data Connection';
|
|
42
|
-
case
|
|
42
|
+
case KNXConstants.KNX_CONSTANTS.E_KNX_CONNECTION:
|
|
43
43
|
return 'Invalid KNX Connection';
|
|
44
|
-
case
|
|
44
|
+
case KNXConstants.KNX_CONSTANTS.E_TUNNELING_LAYER:
|
|
45
45
|
return 'Invalid Tunneling Layer';
|
|
46
46
|
default:
|
|
47
47
|
return `Unknown error ${status}`;
|
|
@@ -18,6 +18,7 @@ exports.KNX_CONSTANTS = {
|
|
|
18
18
|
DEVICE_CONFIGURATION_ACK: 0x0311,
|
|
19
19
|
TUNNELING_REQUEST: 0x0420,
|
|
20
20
|
TUNNELING_ACK: 0x0421,
|
|
21
|
+
ROUTING_BUSY: 0x0532,
|
|
21
22
|
ROUTING_INDICATION: 0x0530,
|
|
22
23
|
ROUTING_LOST_MESSAGE: 0x0531,
|
|
23
24
|
DEVICE_MGMT_CONNECTION: 0x03,
|
|
@@ -61,15 +62,15 @@ exports.KNX_CONSTANTS = {
|
|
|
61
62
|
KNX_IP: '224.0.23.12',
|
|
62
63
|
IPV4_ADDRESS_LENGTH: 4,
|
|
63
64
|
// Search for KNX IP Secure Unicasts Setups
|
|
64
|
-
SECURE_SEARCH_REQUEST
|
|
65
|
-
SECURE_SEARCH_RESPONSE
|
|
65
|
+
SECURE_SEARCH_REQUEST: 0x20b,
|
|
66
|
+
SECURE_SEARCH_RESPONSE: 0x20c,
|
|
66
67
|
// KNX IP Secure
|
|
67
|
-
SECURE_WRAPPER
|
|
68
|
-
SECURE_SESSION_REQUEST
|
|
69
|
-
SECURE_SESSION_RESPONSE
|
|
70
|
-
SECURE_SESSION_AUTH
|
|
71
|
-
SECURE_SESSION_STATUS
|
|
72
|
-
SECURE_GROUP_SYNC
|
|
68
|
+
SECURE_WRAPPER: 0x0950,
|
|
69
|
+
SECURE_SESSION_REQUEST: 0x0951,
|
|
70
|
+
SECURE_SESSION_RESPONSE: 0x0952,
|
|
71
|
+
SECURE_SESSION_AUTH: 0x0953,
|
|
72
|
+
SECURE_SESSION_STATUS: 0x0954,
|
|
73
|
+
SECURE_GROUP_SYNC: 0x0955
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
var ConnectionStatus;
|
|
@@ -13,17 +13,36 @@ const splitIP = (ip, name = 'ip') => {
|
|
|
13
13
|
};
|
|
14
14
|
exports.splitIP = splitIP;
|
|
15
15
|
const validateKNXAddress = (address, isGroup = false) => {
|
|
16
|
+
// 22/12/2021 Supergiovane: https://support.knx.org/hc/en-us/articles/115003188109-Group-Addresses
|
|
16
17
|
if (typeof (address) === 'string') {
|
|
17
18
|
const digits = address.split(/[./]/);
|
|
18
19
|
if (digits.length < 2 || digits.length > 3) {
|
|
19
|
-
throw new Error(`Invalid address format: ${address}`);
|
|
20
|
+
throw new Error(`Invalid address format: ${address} Only 3 level addresses are allowed`);
|
|
20
21
|
}
|
|
22
|
+
if ((digits.length === 3 && address === "0/0/0") || (digits.length === 1 && address === "0/0")) throw new Error(`Invalid address: ${address}`);
|
|
23
|
+
|
|
21
24
|
let count = 0;
|
|
22
25
|
let newAddress = 0;
|
|
23
26
|
for (let i = digits.length - 1; i >= 0; i--, count++) {
|
|
24
27
|
const digit = Number(digits[i]);
|
|
25
|
-
if (
|
|
26
|
-
|
|
28
|
+
if (isGroup && digits.length === 3) {
|
|
29
|
+
// Validating Group Address
|
|
30
|
+
if (isNaN(digit) || (count === 2 && digit > 31) || (count === 1 && digit > 7) || (count === 0 && digit > 255)) {
|
|
31
|
+
// 22/12/2021 Supergiovane disabled digits validation
|
|
32
|
+
throw new Error(`Invalid 3 levels GA digit ${digit} inside address: ${address}`);
|
|
33
|
+
}
|
|
34
|
+
} else if (isGroup && digits.length === 2) {
|
|
35
|
+
// Validating Group Address
|
|
36
|
+
if (isNaN(digit) || (count === 1 && digit > 31) || (count === 0 && digit > 2047)) {
|
|
37
|
+
// 22/12/2021 Supergiovane disabled digits validation
|
|
38
|
+
throw new Error(`Invalid 2 levels GA digit ${digit} inside address: ${address}`);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Validating KNX Device Address
|
|
42
|
+
if (isNaN(digit) || (count > 1 && digit > 15) || (count === 0 && digit > 255)) {
|
|
43
|
+
// 22/12/2021 Supergiovane disabled digits validation
|
|
44
|
+
throw new Error(`Invalid Individual Address digit ${digit} inside address: ${address}`);
|
|
45
|
+
}
|
|
27
46
|
}
|
|
28
47
|
if (count === 0) {
|
|
29
48
|
newAddress = digit;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
physAddr: { value: "15.15.22", required: true },
|
|
9
9
|
hostProtocol: { value: "Multicast", required: false }, // TunnelUDP/TunnelTCP/Multicast
|
|
10
10
|
// enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this
|
|
11
|
-
suppressACKRequest: { value:
|
|
11
|
+
suppressACKRequest: { value: false },
|
|
12
12
|
csv: { value: "", required: false },
|
|
13
13
|
KNXEthInterface: { value: "Auto" },
|
|
14
14
|
KNXEthInterfaceManuallyInput: { value: "" },
|
|
@@ -425,19 +425,6 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
425
425
|
node.addClient = (_Node) => {
|
|
426
426
|
// Check if node already exists
|
|
427
427
|
if (node.nodeClients.filter(x => x.id === _Node.id).length === 0) {
|
|
428
|
-
// Check if the node has a valid topic and dpt
|
|
429
|
-
if (_Node.listenallga === false) {
|
|
430
|
-
if (_Node.topic === undefined || _Node.dpt === undefined) {
|
|
431
|
-
_Node.setNodeStatus({ fill: "red", shape: "dot", text: "Empty Group Addr. or datapoint.", payload: "", GA: "", dpt: "", devicename: "" })
|
|
432
|
-
return;
|
|
433
|
-
} else {
|
|
434
|
-
// topic must be in format x/x/x
|
|
435
|
-
if (_Node.topic.split("\/").length < 3) {
|
|
436
|
-
_Node.setNodeStatus({ fill: "red", shape: "dot", text: "Wrong group address (topic: " + _Node.topic + ") format.", payload: "", GA: "", dpt: "", devicename: "" })
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
428
|
// Add _Node to the clients array
|
|
442
429
|
if (node.autoReconnect) {
|
|
443
430
|
_Node.setNodeStatus({ fill: "grey", shape: "ring", text: "Node initialized.", payload: "", GA: "", dpt: "", devicename: "" });
|
|
@@ -814,7 +801,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
814
801
|
//function handleBusEvents(_evt, _src, _dest, _rawValue, _datagram, _isRepeated) {
|
|
815
802
|
function handleBusEvents(_datagram, _echoed, _CEMI) {
|
|
816
803
|
|
|
817
|
-
|
|
804
|
+
|
|
818
805
|
// _rawValue
|
|
819
806
|
try {
|
|
820
807
|
_rawValue = _datagram.cEMIMessage.npdu.dataValue;
|
|
@@ -848,7 +835,7 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
848
835
|
// I'm receiving a telegram from the BUS
|
|
849
836
|
try {
|
|
850
837
|
var iStart = _datagram._header._headerLength; //+ 4;
|
|
851
|
-
_cemiETS = _CEMI.substring(iStart*2
|
|
838
|
+
_cemiETS = _CEMI.substring(iStart * 2);
|
|
852
839
|
//_cemiETS = datagram.cEMIMessage.srcAddress.toBuffer().toString("hex") + _datagram.cEMIMessage.dstAddress.toBuffer().toString("hex") + "01" + _datagram.cEMIMessage.npdu._tpci.toString(16)
|
|
853
840
|
} catch (error) { }
|
|
854
841
|
|
|
@@ -1100,7 +1087,6 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1100
1087
|
|
|
1101
1088
|
function handleTelegramQueue() {
|
|
1102
1089
|
if (node.knxConnection !== null || node.host.toUpperCase() === "EMULATE") {
|
|
1103
|
-
//console.log("BANANA handleTelegramQueueSONO BLOCCATO ? ",node.lockHandleTelegramQueue, "CONNECTED ? ", node.linkStatus )
|
|
1104
1090
|
if (node.lockHandleTelegramQueue === true) return; // Exits if the funtion is busy
|
|
1105
1091
|
node.lockHandleTelegramQueue = true; // Lock the function. It cannot be called again until finished.
|
|
1106
1092
|
|
|
@@ -1111,6 +1097,15 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1111
1097
|
return;
|
|
1112
1098
|
}
|
|
1113
1099
|
|
|
1100
|
+
// 26/12/2021 If the KNXEngine is busy waiting for telegram's ACK, exit
|
|
1101
|
+
if (!node.knxConnection._getClearToSend()) {
|
|
1102
|
+
node.lockHandleTelegramQueue = false; // Unlock the function
|
|
1103
|
+
if (node.telegramsQueue.length > 0) {
|
|
1104
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("knxUltimate-config: handleTelegramQueue: the KNXEngine is busy or is waiting for a telegram ACK with seqNumner " + node.knxConnection._getSeqNumber() + ". Delay handling queue.");
|
|
1105
|
+
}
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1114
1109
|
// Retrieving oKNXMessage { grpaddr, payload,dpt,outputtype (write or response),nodecallerid (node caller)}. 06/03/2020 "Read" request does have the lower priority in the queue, so firstly, i search for "read" telegrams and i move it on the top of the queue pile.
|
|
1115
1110
|
var aTelegramsFiltered = [];
|
|
1116
1111
|
aTelegramsFiltered = node.telegramsQueue.filter(a => a.outputtype !== "read");
|
package/nodes/knxUltimate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
const _ = require("lodash");
|
|
3
|
+
const KNXUtils = require("./../KNXEngine/protocol/KNXUtils");
|
|
3
4
|
|
|
4
5
|
function knxUltimate(config) {
|
|
5
6
|
RED.nodes.createNode(this, config)
|
|
@@ -57,15 +58,16 @@ module.exports = function (RED) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// Check if the node has a valid topic and dpt
|
|
60
|
-
if (node.listenallga
|
|
61
|
-
if (
|
|
61
|
+
if (node.listenallga === false) {
|
|
62
|
+
if (node.topic === undefined || node.dpt === undefined) {
|
|
62
63
|
node.setNodeStatus({ fill: "red", shape: "dot", text: "Empty Group Addr. or datapoint.", payload: "", GA: "", dpt: "", devicename: "" })
|
|
63
64
|
return;
|
|
64
65
|
} else {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// Validate the Address
|
|
67
|
+
try {
|
|
68
|
+
KNXUtils.validateKNXAddress(node.topic, true)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
node.setNodeStatus({ fill: "red", shape: "dot", text: error.message, payload: "", GA: node.topic, dpt: "", devicename: "" })
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
}
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"show_date_status": "Tag und Uhrzeit des letzten Updates im Status anzeigen",
|
|
24
24
|
"show_device_status": "Gerätenamen im Status anzeigen (\"Alle Gruppenadressen abhören\" erforderlich)",
|
|
25
25
|
"show_datapoint_status": "Datenpunkt im Status anzeigen",
|
|
26
|
-
"suppress_ack": "ACK-Anfrage unterdrücken",
|
|
27
|
-
"suppress_ack_help": "Diese Option unterstützt die Kompatibilität mit der alten Firmware des IP-
|
|
26
|
+
"suppress_ack": "ACK-Anfrage unterdrücken in Tunnel Modus",
|
|
27
|
+
"suppress_ack_help": "Diese Option unterstützt die Kompatibilität mit der alten Firmware des alten IP-Interfaces.Achtung: Der Knoten kann die Trennung vom Gateway möglicherweise nicht bemerken. Verwenden Sie in diesem Fall den Watchdog im Ethernet+KNX-Twisted-Pair Modus.",
|
|
28
28
|
"ignoreTelegramsWithRepeatedFlag": "Wiederholt (R-Flag) telegramme vom BUS unterdrücken",
|
|
29
29
|
"log_level": "Loglevel",
|
|
30
30
|
"nodes_list_title": "Liste aller KNX-Ultimate Nodes in allen Flows",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"show_date_status": "Show last update day and time in status",
|
|
24
24
|
"show_device_status": "Show device name in status (require \"Listen all Group Address\" )",
|
|
25
25
|
"show_datapoint_status": "Show datapoint in status",
|
|
26
|
-
"suppress_ack": "Suppress ACK request",
|
|
27
|
-
"suppress_ack_help": "The option above helps old KNX/IP Interfaces compatibility",
|
|
26
|
+
"suppress_ack": "Suppress ACK request in tunneling mode",
|
|
27
|
+
"suppress_ack_help": "The option above helps old KNX/IP Interfaces compatibility. Warning: the node may not notice the disconnection from the gateway. In this case, use the watchdog in Ethernet+KNX twisted pair mode.",
|
|
28
28
|
"ignoreTelegramsWithRepeatedFlag": "Suppress repeated (R-Flag) telegrams fom BUS",
|
|
29
29
|
"log_level": "Loglevel",
|
|
30
30
|
"nodes_list_title": "List of your nodes in all flows",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"show_date_status": "Visualizza data e ora ultimo aggiornamento, nello stato",
|
|
24
24
|
"show_device_status": "Visualizza il nome del dispositivo nello stato (richiede \"Modalità universale\" )",
|
|
25
25
|
"show_datapoint_status": "Visualizza il DPT nello stato",
|
|
26
|
-
"suppress_ack": "Sopprimi richieste ACK",
|
|
27
|
-
"suppress_ack_help": "L'opzione ACK sopra aiuta la compatibilità con vecchie interfacce",
|
|
26
|
+
"suppress_ack": "Sopprimi richieste ACK in modalità tunnel",
|
|
27
|
+
"suppress_ack_help": "L'opzione ACK sopra aiuta la compatibilità con vecchie interfacce. Attenzione: il nodo potrebbe non accorgersi della disconnessione al gateway. In questo caso, usare il watchdog in modalità Ethernet+KNX twisted pair.",
|
|
28
28
|
"ignoreTelegramsWithRepeatedFlag": "Sopprimi telegrammi dal BUS ripetuti (Flag R)",
|
|
29
29
|
"log_level": "Livello log",
|
|
30
30
|
"nodes_list_title": "Elenco nodi in tutti i flows",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"show_date_status": "在状态中显示上次更新日期和时间",
|
|
24
24
|
"show_device_status": "在状态中显示设备的名称 (需要 \"监听所有组地址\" )",
|
|
25
25
|
"show_datapoint_status": "在状态中显示数据类型",
|
|
26
|
-
"suppress_ack": "抑制 ACK 请求",
|
|
27
|
-
"suppress_ack_help": "上面的选项有助于旧的 KNX/IP 接口兼容性",
|
|
26
|
+
"suppress_ack": "抑制 ACK 请求 in tunneling mode",
|
|
27
|
+
"suppress_ack_help": "上面的选项有助于旧的 KNX/IP 接口兼容性 Warning: the node may not notice the disconnection from the gateway. In this case, use the watchdog in Ethernet+KNX twisted pair mode.",
|
|
28
28
|
"ignoreTelegramsWithRepeatedFlag": "抑制重复的 (R-Flag) 来自总线的电报",
|
|
29
29
|
"log_level": "日志等级",
|
|
30
30
|
"nodes_list_title": "所有流中的节点列表",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.14",
|
|
4
4
|
"description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"fs": "0.0.1-security",
|