node-red-contrib-knx-ultimate 1.4.6 → 1.4.7
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/.prettierignore +8 -0
- package/CHANGELOG.md +4 -0
- package/KNXEngine/KNXClient.js +29 -29
- package/KNXEngine/dptlib/dpt21.js +2 -2
- package/KNXEngine/dptlib/dpt60001.js +276 -0
- package/KNXEngine/dptlib/dpt9.js +2 -2
- package/KNXEngine/dptlib/index.js +1 -1
- package/nodes/knxUltimate-config.js +18 -18
- package/nodes/knxUltimateViewer.js +2 -2
- package/nodes/knxUltimateWatchDog.js +4 -4
- package/package.json +1 -1
package/.prettierignore
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 1.4.7</b> - November 2022<br/>
|
|
11
|
+
- NEW: added "Griesser Object" Custom Datapoint 6001.001. Thanks @croghostrider<br/>
|
|
12
|
+
</p>
|
|
9
13
|
<p>
|
|
10
14
|
<b>Version 1.4.6</b> - November 2022<br/>
|
|
11
15
|
- NEW: added Airflow Datapoint 9.009.<br/>
|
package/KNXEngine/KNXClient.js
CHANGED
|
@@ -40,7 +40,7 @@ const SocketEvents = {
|
|
|
40
40
|
data: 'data',
|
|
41
41
|
close: 'close'
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
let KNXClientEvents;
|
|
44
44
|
(function (KNXClientEvents) {
|
|
45
45
|
KNXClientEvents.error = 'error'
|
|
46
46
|
KNXClientEvents.disconnected = 'disconnected'
|
|
@@ -84,7 +84,7 @@ const optionsDefaults = {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
class KNXClient extends EventEmitter {
|
|
87
|
-
constructor(options) {
|
|
87
|
+
constructor (options) {
|
|
88
88
|
if (options === undefined) {
|
|
89
89
|
options = optionsDefaults
|
|
90
90
|
}
|
|
@@ -188,7 +188,7 @@ class KNXClient extends EventEmitter {
|
|
|
188
188
|
this._numFailedTelegramACK = 0 // 25/12/2021 Keep count of the failed tunnelig ACK telegrams
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
get channelID() {
|
|
191
|
+
get channelID () {
|
|
192
192
|
return this._channelID
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -215,7 +215,7 @@ class KNXClient extends EventEmitter {
|
|
|
215
215
|
// DPT19: null,
|
|
216
216
|
// DPT20: null
|
|
217
217
|
// };
|
|
218
|
-
getKNXDataBuffer(_data, _dptid) {
|
|
218
|
+
getKNXDataBuffer (_data, _dptid) {
|
|
219
219
|
const adpu = {}
|
|
220
220
|
DPTLib.populateAPDU(_data, adpu, _dptid)
|
|
221
221
|
const iDatapointType = parseInt(_dptid.substr(0, _dptid.indexOf('.')))
|
|
@@ -253,7 +253,7 @@ class KNXClient extends EventEmitter {
|
|
|
253
253
|
// }
|
|
254
254
|
// });
|
|
255
255
|
// }
|
|
256
|
-
send(knxPacket) {
|
|
256
|
+
send (knxPacket) {
|
|
257
257
|
// Logging
|
|
258
258
|
if (this.sysLogger !== undefined && this.sysLogger !== null) {
|
|
259
259
|
try {
|
|
@@ -320,7 +320,7 @@ class KNXClient extends EventEmitter {
|
|
|
320
320
|
* @param {KNXDataBuffer} data
|
|
321
321
|
*/
|
|
322
322
|
// sendWriteRequest(dstAddress, data) {
|
|
323
|
-
write(dstAddress, data, dptid) {
|
|
323
|
+
write (dstAddress, data, dptid) {
|
|
324
324
|
if (this._connectionState !== STATE.CONNECTED) throw new Error('The socket is not connected. Unable to access the KNX BUS')
|
|
325
325
|
|
|
326
326
|
// Get the Data Buffer from the plain value
|
|
@@ -363,7 +363,7 @@ class KNXClient extends EventEmitter {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
// sendResponseRequest
|
|
366
|
-
respond(dstAddress, data, dptid) {
|
|
366
|
+
respond (dstAddress, data, dptid) {
|
|
367
367
|
if (this._connectionState !== STATE.CONNECTED) throw new Error('The socket is not connected. Unable to access the KNX BUS')
|
|
368
368
|
|
|
369
369
|
// Get the Data Buffer from the plain value
|
|
@@ -405,7 +405,7 @@ class KNXClient extends EventEmitter {
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// sendReadRequest
|
|
408
|
-
read(dstAddress) {
|
|
408
|
+
read (dstAddress) {
|
|
409
409
|
if (this._connectionState !== STATE.CONNECTED) throw new Error('The socket is not connected. Unable to access the KNX BUS')
|
|
410
410
|
|
|
411
411
|
if (typeof dstAddress === 'string') dstAddress = KNXAddress.createFromString(dstAddress, KNXAddress.TYPE_GROUP)
|
|
@@ -443,7 +443,7 @@ class KNXClient extends EventEmitter {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
writeRaw(dstAddress, _rawDataBuffer, bitlength) {
|
|
446
|
+
writeRaw (dstAddress, _rawDataBuffer, bitlength) {
|
|
447
447
|
// bitlength is unused and only for backward compatibility
|
|
448
448
|
|
|
449
449
|
if (this._connectionState !== STATE.CONNECTED) throw new Error('The socket is not connected. Unable to access the KNX BUS')
|
|
@@ -500,14 +500,14 @@ class KNXClient extends EventEmitter {
|
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
startHeartBeat() {
|
|
503
|
+
startHeartBeat () {
|
|
504
504
|
this.stopHeartBeat()
|
|
505
505
|
this._heartbeatFailures = 0
|
|
506
506
|
this._heartbeatRunning = true
|
|
507
507
|
this._runHeartbeat()
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
stopHeartBeat() {
|
|
510
|
+
stopHeartBeat () {
|
|
511
511
|
if (this._heartbeatTimer !== null) {
|
|
512
512
|
this._heartbeatRunning = false
|
|
513
513
|
clearTimeout(this._heartbeatTimer)
|
|
@@ -543,7 +543,7 @@ class KNXClient extends EventEmitter {
|
|
|
543
543
|
// this._awaitingResponseType = KNXConstants.KNX_CONSTANTS.DESCRIPTION_RESPONSE;
|
|
544
544
|
// this._sendDescriptionRequestMessage(host, port);
|
|
545
545
|
// }
|
|
546
|
-
Connect(knxLayer = TunnelCRI.TunnelTypes.TUNNEL_LINKLAYER) {
|
|
546
|
+
Connect (knxLayer = TunnelCRI.TunnelTypes.TUNNEL_LINKLAYER) {
|
|
547
547
|
if (this._clientSocket == null) {
|
|
548
548
|
throw new Error('No client socket defined')
|
|
549
549
|
}
|
|
@@ -611,7 +611,7 @@ class KNXClient extends EventEmitter {
|
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
getConnectionStatus() {
|
|
614
|
+
getConnectionStatus () {
|
|
615
615
|
if (this._clientSocket == null) {
|
|
616
616
|
throw new Error('No client socket defined')
|
|
617
617
|
}
|
|
@@ -641,7 +641,7 @@ class KNXClient extends EventEmitter {
|
|
|
641
641
|
} catch (error) { }
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
Disconnect() {
|
|
644
|
+
Disconnect () {
|
|
645
645
|
if (this._clientSocket === null) {
|
|
646
646
|
throw new Error('No client socket defined')
|
|
647
647
|
}
|
|
@@ -669,11 +669,11 @@ class KNXClient extends EventEmitter {
|
|
|
669
669
|
}, 2000)
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
isConnected() {
|
|
672
|
+
isConnected () {
|
|
673
673
|
return this._connectionState === STATE.CONNECTED
|
|
674
674
|
}
|
|
675
675
|
|
|
676
|
-
_setDisconnected(_sReason = '') {
|
|
676
|
+
_setDisconnected (_sReason = '') {
|
|
677
677
|
try {
|
|
678
678
|
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.debug('KNXClient: called _setDisconnected ' + this._options.ipAddr + ':' + this._options.ipPort + ' ' + _sReason)
|
|
679
679
|
} catch (error) {
|
|
@@ -698,7 +698,7 @@ class KNXClient extends EventEmitter {
|
|
|
698
698
|
this._clearToSend = true // 26/12/2021 allow to send
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
-
_runHeartbeat() {
|
|
701
|
+
_runHeartbeat () {
|
|
702
702
|
if (this._heartbeatRunning) {
|
|
703
703
|
this.getConnectionStatus()
|
|
704
704
|
const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
|
|
@@ -707,16 +707,16 @@ class KNXClient extends EventEmitter {
|
|
|
707
707
|
}
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
-
_getSeqNumber() {
|
|
710
|
+
_getSeqNumber () {
|
|
711
711
|
return this._clientTunnelSeqNumber
|
|
712
712
|
}
|
|
713
713
|
|
|
714
714
|
// 26/12/2021 Handle the busy state, for example while waiting for ACK
|
|
715
|
-
_getClearToSend() {
|
|
715
|
+
_getClearToSend () {
|
|
716
716
|
return (this._clearToSend !== undefined ? this._clearToSend : true)
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
-
_incSeqNumber() {
|
|
719
|
+
_incSeqNumber () {
|
|
720
720
|
this._clientTunnelSeqNumber++
|
|
721
721
|
if (this._clientTunnelSeqNumber > 255) {
|
|
722
722
|
this._clientTunnelSeqNumber = 0
|
|
@@ -727,7 +727,7 @@ class KNXClient extends EventEmitter {
|
|
|
727
727
|
// _keyFromCEMIMessage(cEMIMessage) {
|
|
728
728
|
// return cEMIMessage.dstAddress.toString();
|
|
729
729
|
// }
|
|
730
|
-
_setTimerWaitingForACK(knxTunnelingRequest) {
|
|
730
|
+
_setTimerWaitingForACK (knxTunnelingRequest) {
|
|
731
731
|
this._clearToSend = false // 26/12/2021 stop sending until ACK received
|
|
732
732
|
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'}`)
|
|
733
733
|
if (this._timerWaitingForACK !== null) clearTimeout(this._timerWaitingForACK)
|
|
@@ -754,7 +754,7 @@ class KNXClient extends EventEmitter {
|
|
|
754
754
|
}, KNXConstants.KNX_CONSTANTS.TUNNELING_REQUEST_TIMEOUT * 1000)
|
|
755
755
|
}
|
|
756
756
|
|
|
757
|
-
_processInboundMessage(msg, rinfo) {
|
|
757
|
+
_processInboundMessage (msg, rinfo) {
|
|
758
758
|
try {
|
|
759
759
|
// Composing debug string
|
|
760
760
|
try {
|
|
@@ -998,31 +998,31 @@ class KNXClient extends EventEmitter {
|
|
|
998
998
|
}
|
|
999
999
|
}
|
|
1000
1000
|
|
|
1001
|
-
_sendDescriptionRequestMessage() {
|
|
1001
|
+
_sendDescriptionRequestMessage () {
|
|
1002
1002
|
this.send(KNXProtocol.KNXProtocol.newKNXDescriptionRequest(new HPAI.HPAI(this._options.localIPAddress)))
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
-
_sendSearchRequestMessage() {
|
|
1005
|
+
_sendSearchRequestMessage () {
|
|
1006
1006
|
// this.send(KNXProtocol.KNXProtocol.newKNXSearchRequest(new HPAI.HPAI(this._options.localIPAddress, this._localPort)), KNXConstants.KNX_CONSTANTS.KNX_PORT, KNXConstants.KNX_CONSTANTS.KNX_IP);
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
|
-
_sendConnectRequestMessage(cri) {
|
|
1009
|
+
_sendConnectRequestMessage (cri) {
|
|
1010
1010
|
this.send(KNXProtocol.KNXProtocol.newKNXConnectRequest(cri))
|
|
1011
1011
|
}
|
|
1012
1012
|
|
|
1013
|
-
_sendConnectionStateRequestMessage(channelID) {
|
|
1013
|
+
_sendConnectionStateRequestMessage (channelID) {
|
|
1014
1014
|
this.send(KNXProtocol.KNXProtocol.newKNXConnectionStateRequest(channelID))
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
-
_sendDisconnectRequestMessage(channelID) {
|
|
1017
|
+
_sendDisconnectRequestMessage (channelID) {
|
|
1018
1018
|
this.send(KNXProtocol.KNXProtocol.newKNXDisconnectRequest(channelID))
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
|
-
_sendDisconnectResponseMessage(channelID, status = KNXConstants.ConnectionStatus.E_NO_ERROR) {
|
|
1021
|
+
_sendDisconnectResponseMessage (channelID, status = KNXConstants.ConnectionStatus.E_NO_ERROR) {
|
|
1022
1022
|
this.send(KNXProtocol.KNXProtocol.newKNXDisconnectResponse(channelID, status))
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
-
_sendSecureSessionRequestMessage(cri) {
|
|
1025
|
+
_sendSecureSessionRequestMessage (cri) {
|
|
1026
1026
|
const oHPAI = new HPAI.HPAI('0.0.0.0', 0, this._options.hostProtocol === 'TunnelTCP' ? KNXConstants.KNX_CONSTANTS.IPV4_TCP : KNXConstants.KNX_CONSTANTS.IPV4_UDP)
|
|
1027
1027
|
this.send(KNXProtocol.KNXProtocol.newKNXSecureSessionRequest(cri, oHPAI))
|
|
1028
1028
|
}
|
|
@@ -38,8 +38,8 @@ exports.fromBuffer = function (buf) {
|
|
|
38
38
|
knxLog.get().error('DPT21: Buffer should be 8 bit long, got', buf.length)
|
|
39
39
|
return null
|
|
40
40
|
}
|
|
41
|
-
const sBit =Array.from((parseInt(buf.toString('hex').toUpperCase(), 16).toString(2)).padStart(8, '0')) // Get bit from hex
|
|
42
|
-
const ret = { outOfService: sBit[7] ==='1'
|
|
41
|
+
const sBit = Array.from((parseInt(buf.toString('hex').toUpperCase(), 16).toString(2)).padStart(8, '0')) // Get bit from hex
|
|
42
|
+
const ret = { outOfService: sBit[7] === '1', fault: sBit[6] === '1', overridden: sBit[5] === '1', inAlarm: sBit[4] === '1', alarmUnAck: sBit[3] === '1' }
|
|
43
43
|
return ret
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
const knxLog = require('./../KnxLog')
|
|
2
|
+
|
|
3
|
+
function toRadix (value, radix) {
|
|
4
|
+
if (!Number.isSafeInteger(value)) {
|
|
5
|
+
knxLog.get().error('value must be a safe integer')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const digits = Math.ceil(64 / Math.log2(radix))
|
|
9
|
+
const twosComplement = value < 0
|
|
10
|
+
? BigInt(radix) ** BigInt(digits) + BigInt(value)
|
|
11
|
+
: value
|
|
12
|
+
|
|
13
|
+
return twosComplement.toString(radix).padStart(digits, '0')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function griesserSectorCode (Byte0, Byte1) {
|
|
17
|
+
const SectorCode = Byte0 + (Byte1 & 3) * 256
|
|
18
|
+
return SectorCode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function griesserCommandCode (Byte1) {
|
|
22
|
+
const command = (Byte1 / 4) >> 0
|
|
23
|
+
return command
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function griesserParameter (command, Byte2, Byte3, Byte4, Byte5) {
|
|
27
|
+
let commandOperation
|
|
28
|
+
switch (command) {
|
|
29
|
+
case 1: // drive command
|
|
30
|
+
switch (Byte2 & 31) {
|
|
31
|
+
case 0: return 'no driving movement'
|
|
32
|
+
case 1: return 'upper end position'
|
|
33
|
+
case 2: return 'lower end position'
|
|
34
|
+
case 3:
|
|
35
|
+
if ((Byte3 >= 1) & (Byte3 <= 4)) {
|
|
36
|
+
return 'fixed position P' + Byte3 + ' approach'
|
|
37
|
+
} else {
|
|
38
|
+
return 'Unknown value for Pn ' + Byte3
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
return 'Unknown drive command' + Byte2 & 31
|
|
42
|
+
}
|
|
43
|
+
case 4: // set/delete lock
|
|
44
|
+
if (Byte2 === 0) {
|
|
45
|
+
return 'no lock'
|
|
46
|
+
} else {
|
|
47
|
+
switch (Byte2 & 3) {
|
|
48
|
+
case 1: return 'driving command'
|
|
49
|
+
case 2: return 'button lock'
|
|
50
|
+
case 3: return 'driving command- and button lock'
|
|
51
|
+
}
|
|
52
|
+
if (Byte3 === 0) {
|
|
53
|
+
return 'delete lock'
|
|
54
|
+
} else {
|
|
55
|
+
return 'set lock'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
case 5: // operation code
|
|
59
|
+
if (Byte2 <= 6) {
|
|
60
|
+
commandOperation = 'groupoperation'
|
|
61
|
+
} else if (Byte2 >= 128 && Byte2 <= 134) {
|
|
62
|
+
commandOperation = 'localoperation'
|
|
63
|
+
} else {
|
|
64
|
+
commandOperation = 'unknown command Byte3 for ' + Byte2
|
|
65
|
+
}
|
|
66
|
+
if ((Byte2 & 127) === 0) {
|
|
67
|
+
return [commandOperation, 'long up']
|
|
68
|
+
} else if ((Byte2 & 127) === 1) {
|
|
69
|
+
return [commandOperation, 'long down']
|
|
70
|
+
} else if ((Byte2 & 127) === 2) {
|
|
71
|
+
return [commandOperation, 'short up']
|
|
72
|
+
} else if ((Byte2 & 127) === 3) {
|
|
73
|
+
return [commandOperation, 'short down']
|
|
74
|
+
} else if ((Byte2 & 127) === 4) {
|
|
75
|
+
return [commandOperation, 'stop']
|
|
76
|
+
} else if ((Byte2 & 127) === 5) {
|
|
77
|
+
return [commandOperation, 'long-short up']
|
|
78
|
+
} else if ((Byte2 & 127) === 6) {
|
|
79
|
+
return [commandOperation, 'long-short down']
|
|
80
|
+
} else {
|
|
81
|
+
return [commandOperation, 'unknown command Byte3 for ' + Byte2]
|
|
82
|
+
}
|
|
83
|
+
case 22: // driving range limits for automatic button commands
|
|
84
|
+
return ['min. angle: ' + Byte2, 'max. angle: ' + Byte3, 'min. height: ' + Byte4, 'max. height: ' + Byte5]
|
|
85
|
+
default: return 'unknown value for command: ' + command
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function griesserSectors (SectorCode) {
|
|
90
|
+
let SectorMin, SectorMax, dA, a, SectorCodeMin, SectorCodeMax
|
|
91
|
+
dA = 1
|
|
92
|
+
a = SectorCode
|
|
93
|
+
if (a > 0) {
|
|
94
|
+
while ((a & 1) === 0) {
|
|
95
|
+
a = (a / 2) >> 0
|
|
96
|
+
dA = dA * 2
|
|
97
|
+
}
|
|
98
|
+
dA = dA - 1
|
|
99
|
+
SectorMin = SectorCode - dA
|
|
100
|
+
SectorMax = SectorCode + dA
|
|
101
|
+
} else {
|
|
102
|
+
SectorMin = 0
|
|
103
|
+
SectorMax = 0
|
|
104
|
+
}
|
|
105
|
+
if (SectorMin === 0) {
|
|
106
|
+
SectorCodeMin = 0
|
|
107
|
+
} else {
|
|
108
|
+
SectorCodeMin = (((SectorMin - 1) / 2) >> 0) + 1
|
|
109
|
+
}
|
|
110
|
+
if (SectorMax === 0) {
|
|
111
|
+
SectorCodeMax = 0
|
|
112
|
+
} else {
|
|
113
|
+
SectorCodeMax = (((SectorMax - 1) / 2) >> 0) + 1
|
|
114
|
+
}
|
|
115
|
+
if (SectorCodeMax === SectorCodeMin) {
|
|
116
|
+
return [SectorCodeMin]
|
|
117
|
+
} else {
|
|
118
|
+
const Sectors = []
|
|
119
|
+
for (let i = SectorCodeMin; i <= SectorCodeMax; i++) {
|
|
120
|
+
Sectors.push(i)
|
|
121
|
+
}
|
|
122
|
+
return Sectors
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function griesserSectorToSectorCode (sectors) {
|
|
127
|
+
if (sectors.length === 1) {
|
|
128
|
+
return sectors[0] + sectors[0] - 1
|
|
129
|
+
} else {
|
|
130
|
+
return Math.min(...sectors) + Math.max(...sectors) - 1
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function griesserCommandToCommandCode (command) {
|
|
135
|
+
switch (command) {
|
|
136
|
+
case 'operation code': return 5
|
|
137
|
+
default: knxLog.get().error('not implemented yet: ' + command)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function griesserCommandToCommandCodeP1 (command) {
|
|
142
|
+
switch (command) {
|
|
143
|
+
case 'long up': return 128
|
|
144
|
+
case 'long down': return 129
|
|
145
|
+
case 'short up': return 130
|
|
146
|
+
case 'short down': return 131
|
|
147
|
+
case 'stop': return 132
|
|
148
|
+
case 'long-short up': return 133
|
|
149
|
+
case 'long-short down': return 134
|
|
150
|
+
default: knxLog.get().error('unknown command: ' + command)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function griesserCommand (command) {
|
|
155
|
+
switch (command) {
|
|
156
|
+
case 1: return 'drive command'
|
|
157
|
+
case 2: return 'value correction'
|
|
158
|
+
case 3: return 'automatic state'
|
|
159
|
+
case 4: return 'set/delete lock'
|
|
160
|
+
case 5: return 'operation code'
|
|
161
|
+
case 6: return 'set scene'
|
|
162
|
+
case 7: return 'special command'
|
|
163
|
+
case 8: return 'date'
|
|
164
|
+
case 9: return 'sync time'
|
|
165
|
+
case 10: return 'sensor reading notification'
|
|
166
|
+
case 11: return 'bus monitoring'
|
|
167
|
+
case 16: return 'driving range limits for safety drive commands'
|
|
168
|
+
case 17: return 'driving range limits for safety drive commands'
|
|
169
|
+
case 19: return 'driving range limits for safety drive commands'
|
|
170
|
+
case 20: return 'driving range limits for safety drive commands'
|
|
171
|
+
case 22: return 'driving range limits for automatic drive commands'
|
|
172
|
+
case 23: return 'driving range limits for automatic drive commands'
|
|
173
|
+
case 24: return 'driving range limits for automatic drive commands'
|
|
174
|
+
default: return 'unknown value for function: ' + command
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function griesserPrio (prio, command) {
|
|
179
|
+
const prioCommand = ((command & 224) / 32) >> 0
|
|
180
|
+
if ((((prio & 252) / 4) >> 0) === 0) {
|
|
181
|
+
switch (prioCommand) {
|
|
182
|
+
case 0: return 'border command'
|
|
183
|
+
case 1: return 'automatic command'
|
|
184
|
+
case 3: return 'priority command'
|
|
185
|
+
case 4: return 'warning command'
|
|
186
|
+
case 5: return 'security command'
|
|
187
|
+
case 6: return 'danger command'
|
|
188
|
+
default: return 'unknown priority' + prioCommand
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
return '-'
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Send to BUS
|
|
196
|
+
exports.formatAPDU = function (value) {
|
|
197
|
+
if (!value) {
|
|
198
|
+
knxLog.get().error('DPT60001: cannot write null value')
|
|
199
|
+
} else {
|
|
200
|
+
if (typeof value === 'object' &&
|
|
201
|
+
Object.prototype.hasOwnProperty.call(value, 'command') &&
|
|
202
|
+
Object.prototype.hasOwnProperty.call(value, 'data') &&
|
|
203
|
+
Object.prototype.hasOwnProperty.call(value, 'sectors') &&
|
|
204
|
+
value.data[0] === 'localoperation'
|
|
205
|
+
) {
|
|
206
|
+
const sectorCode = griesserSectorToSectorCode(value.sectors)
|
|
207
|
+
const commandCode = griesserCommandToCommandCode(value.command)
|
|
208
|
+
const p1 = griesserCommandToCommandCodeP1(value.data[1])
|
|
209
|
+
const bufferTotal = Buffer.alloc(6)
|
|
210
|
+
bufferTotal[0] = parseInt(toRadix(sectorCode, 2).slice(-8), 2)
|
|
211
|
+
bufferTotal[1] = parseInt(toRadix(commandCode, 2).slice(-6) + toRadix(sectorCode, 2).slice(-10, -8), 2)
|
|
212
|
+
bufferTotal[2] = parseInt(toRadix(p1, 2).slice(-8), 2)
|
|
213
|
+
return bufferTotal
|
|
214
|
+
} else {
|
|
215
|
+
knxLog.get().error('DPT60001: Must supply an value {command:"operation code", data:["localoperation", "long up"], sectors:[159]}')
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// RX from BUS
|
|
221
|
+
exports.fromBuffer = function (buf) {
|
|
222
|
+
if (buf.length !== 6) {
|
|
223
|
+
knxLog
|
|
224
|
+
.get()
|
|
225
|
+
.warn(
|
|
226
|
+
'DPTGriesser.fromBuffer: buf should be 6 bytes long (got %d bytes)',
|
|
227
|
+
buf.length
|
|
228
|
+
)
|
|
229
|
+
return null
|
|
230
|
+
} else {
|
|
231
|
+
const hexToDecimal = (hex) => parseInt(hex, 16)
|
|
232
|
+
const bufTotale = buf.toString('hex')
|
|
233
|
+
const Byte0 = hexToDecimal(bufTotale.slice(0, 2))
|
|
234
|
+
const Byte1 = hexToDecimal(bufTotale.slice(2, 4))
|
|
235
|
+
const Byte2 = hexToDecimal(bufTotale.slice(4, 6))
|
|
236
|
+
const Byte3 = hexToDecimal(bufTotale.slice(6, 8))
|
|
237
|
+
const Byte4 = hexToDecimal(bufTotale.slice(8, 10))
|
|
238
|
+
const Byte5 = hexToDecimal(bufTotale.slice(10, 12))
|
|
239
|
+
const sectorCode = griesserSectorCode(Byte0, Byte1)
|
|
240
|
+
const commandCode = griesserCommandCode(Byte1, Byte2)
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
Byte0,
|
|
244
|
+
Byte1,
|
|
245
|
+
Byte2,
|
|
246
|
+
Byte3,
|
|
247
|
+
Byte4,
|
|
248
|
+
Byte5,
|
|
249
|
+
sectorCode,
|
|
250
|
+
commandCode,
|
|
251
|
+
sectors: griesserSectors(sectorCode),
|
|
252
|
+
prio: griesserPrio(Byte1, Byte2),
|
|
253
|
+
command: griesserCommand(commandCode),
|
|
254
|
+
data: griesserParameter(commandCode, Byte2, Byte3, Byte4, Byte5)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// DPT Griesser Object basetype info
|
|
260
|
+
exports.basetype = {
|
|
261
|
+
bitlength: 4 * 8 + 2 * 6 + 1 * 10,
|
|
262
|
+
valuetype: 'composite',
|
|
263
|
+
desc: 'Commands for solar shading actors',
|
|
264
|
+
help: `// Sample of 60001.
|
|
265
|
+
// Now are only the local operation implemented.
|
|
266
|
+
// For example, for 60001, set the sector 42 localy up.
|
|
267
|
+
msg.payload = { command: "operation code", data: ["localoperation", "long up"], sectors: [42] };
|
|
268
|
+
return msg;`
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
exports.subtypes = {
|
|
272
|
+
'001': {
|
|
273
|
+
desc: 'DPT_Griesser_Object',
|
|
274
|
+
name: 'Griesser Object'
|
|
275
|
+
}
|
|
276
|
+
}
|
package/KNXEngine/dptlib/dpt9.js
CHANGED
|
@@ -11,7 +11,7 @@ const knxLog = require('./../KnxLog')
|
|
|
11
11
|
|
|
12
12
|
const util = require('util')
|
|
13
13
|
// kudos to http://croquetweak.blogspot.gr/2014/08/deconstructing-floats-frexp-and-ldexp.html
|
|
14
|
-
function ldexp(mantissa, exponent) {
|
|
14
|
+
function ldexp (mantissa, exponent) {
|
|
15
15
|
return exponent > 1023 // avoid multiplying by infinity
|
|
16
16
|
? mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023)
|
|
17
17
|
: exponent < -1074 // avoid multiplying by zero
|
|
@@ -19,7 +19,7 @@ function ldexp(mantissa, exponent) {
|
|
|
19
19
|
: mantissa * Math.pow(2, exponent)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function frexp(value) {
|
|
22
|
+
function frexp (value) {
|
|
23
23
|
if (value === 0) return [value, 0]
|
|
24
24
|
const data = new DataView(new ArrayBuffer(8))
|
|
25
25
|
data.setFloat64(0, value)
|
|
@@ -108,7 +108,7 @@ return msg;`,
|
|
|
108
108
|
res.json(jRet)
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
-
function knxUltimateConfigNode(config) {
|
|
111
|
+
function knxUltimateConfigNode (config) {
|
|
112
112
|
RED.nodes.createNode(this, config)
|
|
113
113
|
const node = this
|
|
114
114
|
node.host = config.host
|
|
@@ -179,7 +179,7 @@ return msg;`,
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
node.setAllClientsStatus = (_status, _color, _text) => {
|
|
182
|
-
function nextStatus(_oClient) {
|
|
182
|
+
function nextStatus (_oClient) {
|
|
183
183
|
let oClient = RED.nodes.getNode(_oClient.id)
|
|
184
184
|
oClient.setNodeStatus({ fill: _color, shape: 'dot', text: _status + ' ' + _text, payload: '', GA: oClient.topic, dpt: '', devicename: '' })
|
|
185
185
|
oClient = null
|
|
@@ -225,7 +225,7 @@ return msg;`,
|
|
|
225
225
|
// 04/04/2021 Supergiovane, creates the service paths where the persistent files are created.
|
|
226
226
|
// The values file is stored only upon disconnection/close
|
|
227
227
|
// ************************
|
|
228
|
-
function setupDirectory(_aPath) {
|
|
228
|
+
function setupDirectory (_aPath) {
|
|
229
229
|
if (!fs.existsSync(_aPath)) {
|
|
230
230
|
// Create the path
|
|
231
231
|
try {
|
|
@@ -245,7 +245,7 @@ return msg;`,
|
|
|
245
245
|
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info('KNXUltimate-config: payload cache set to ' + path.join(node.userDir, 'knxpersistvalues'))
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function saveExposedGAs() {
|
|
248
|
+
function saveExposedGAs () {
|
|
249
249
|
const sFile = path.join(node.userDir, 'knxpersistvalues', 'knxpersist' + node.id + '.json')
|
|
250
250
|
try {
|
|
251
251
|
if (node.exposedGAs.length > 0) {
|
|
@@ -256,7 +256,7 @@ return msg;`,
|
|
|
256
256
|
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error('KNXUltimate-config: unable to write peristent values to the file ' + sFile + ' ' + err.message)
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
-
function loadExposedGAs() {
|
|
259
|
+
function loadExposedGAs () {
|
|
260
260
|
const sFile = path.join(node.userDir, 'knxpersistvalues', 'knxpersist' + node.id + '.json')
|
|
261
261
|
try {
|
|
262
262
|
node.exposedGAs = JSON.parse(fs.readFileSync(sFile, 'utf8'))
|
|
@@ -479,7 +479,7 @@ return msg;`,
|
|
|
479
479
|
}
|
|
480
480
|
|
|
481
481
|
// 17/02/2020 Do initial read (called by node.timerDoInitialRead timer)
|
|
482
|
-
function DoInitialReadFromKNXBusOrFile() {
|
|
482
|
+
function DoInitialReadFromKNXBusOrFile () {
|
|
483
483
|
if (node.linkStatus !== 'connected') return // 29/08/2019 If not connected, exit
|
|
484
484
|
loadExposedGAs() // 04/04/2021 load the current values of GA payload
|
|
485
485
|
try {
|
|
@@ -586,14 +586,14 @@ return msg;`,
|
|
|
586
586
|
if (typeof _Protocol !== 'undefined') node.hostProtocol = _Protocol
|
|
587
587
|
if (typeof _CSV !== 'undefined' && _CSV !== '') {
|
|
588
588
|
try {
|
|
589
|
-
const sTemp = readCSV(_CSV) // 27/09/2022 Set the new CSV
|
|
589
|
+
const sTemp = readCSV(_CSV) // 27/09/2022 Set the new CSV
|
|
590
590
|
node.csv = sTemp
|
|
591
591
|
} catch (error) {
|
|
592
592
|
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info('Node\'s main config setting error. ' + error.message || '')
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
|
-
|
|
596
|
-
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("Node's main config setting has been changed. New config: IP " + node.host + ' Port ' + node.port + ' PhysicalAddress ' + node.physAddr + ' BindToInterface ' + node.KNXEthInterface + ((typeof _CSV !== 'undefined' && _CSV !== '') ? '. A new group address CSV has been imported.':''))
|
|
595
|
+
|
|
596
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info("Node's main config setting has been changed. New config: IP " + node.host + ' Port ' + node.port + ' PhysicalAddress ' + node.physAddr + ' BindToInterface ' + node.KNXEthInterface + ((typeof _CSV !== 'undefined' && _CSV !== '') ? '. A new group address CSV has been imported.' : ''))
|
|
597
597
|
|
|
598
598
|
try {
|
|
599
599
|
node.Disconnect()
|
|
@@ -839,7 +839,7 @@ return msg;`,
|
|
|
839
839
|
|
|
840
840
|
// Handle BUS events
|
|
841
841
|
// ---------------------------------------------------------------------------------------
|
|
842
|
-
function handleBusEvents(_datagram, _echoed) {
|
|
842
|
+
function handleBusEvents (_datagram, _echoed) {
|
|
843
843
|
// console.time('handleBusEvents');
|
|
844
844
|
|
|
845
845
|
let _rawValue = null
|
|
@@ -1107,7 +1107,7 @@ return msg;`,
|
|
|
1107
1107
|
node.telegramsQueue.unshift(_clonedMessage) // Add _clonedMessage as first in the queue pile
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
-
function handleTelegramQueue() {
|
|
1110
|
+
function handleTelegramQueue () {
|
|
1111
1111
|
if (node.knxConnection !== null || node.host.toUpperCase() === 'EMULATE') {
|
|
1112
1112
|
if (node.lockHandleTelegramQueue === true) return // Exits if the funtion is busy
|
|
1113
1113
|
node.lockHandleTelegramQueue = true // Lock the function. It cannot be called again until finished.
|
|
@@ -1250,14 +1250,14 @@ return msg;`,
|
|
|
1250
1250
|
}
|
|
1251
1251
|
|
|
1252
1252
|
// 14/08/2019 If the node has payload same as the received telegram, return false
|
|
1253
|
-
function checkRBEInputFromKNXBusAllowSend(_node, _KNXTelegramPayload) {
|
|
1253
|
+
function checkRBEInputFromKNXBusAllowSend (_node, _KNXTelegramPayload) {
|
|
1254
1254
|
if (_node.inputRBE !== true) return true
|
|
1255
1255
|
|
|
1256
1256
|
return !_.isEqual(_node.currentPayload, _KNXTelegramPayload)
|
|
1257
1257
|
}
|
|
1258
1258
|
|
|
1259
1259
|
// 26/10/2019 Try to figure out the datapoint type from raw value
|
|
1260
|
-
function tryToFigureOutDataPointFromRawValue(_rawValue) {
|
|
1260
|
+
function tryToFigureOutDataPointFromRawValue (_rawValue) {
|
|
1261
1261
|
// 25/10/2019 Try some Datapoints
|
|
1262
1262
|
if (_rawValue === null) return '1.001'
|
|
1263
1263
|
if (_rawValue.length === 1) {
|
|
@@ -1303,7 +1303,7 @@ return msg;`,
|
|
|
1303
1303
|
}
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
1306
|
-
function buildInputMessage({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode }) {
|
|
1306
|
+
function buildInputMessage ({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode }) {
|
|
1307
1307
|
let sPayloadmeasureunit = 'unknown'
|
|
1308
1308
|
let sDptdesc = 'unknown'
|
|
1309
1309
|
let sPayloadsubtypevalue = 'unknown'
|
|
@@ -1430,7 +1430,7 @@ return msg;`,
|
|
|
1430
1430
|
}
|
|
1431
1431
|
};
|
|
1432
1432
|
|
|
1433
|
-
function readCSV(_csvText) {
|
|
1433
|
+
function readCSV (_csvText) {
|
|
1434
1434
|
// 24/02/2020, in the middle of Coronavirus emergency in Italy. Check if it a CSV ETS Export of group addresses, or if it's an EFS
|
|
1435
1435
|
if (_csvText.split('\n')[0].toUpperCase().indexOf('"') == -1) return readESF(_csvText)
|
|
1436
1436
|
|
|
@@ -1511,7 +1511,7 @@ return msg;`,
|
|
|
1511
1511
|
}
|
|
1512
1512
|
}
|
|
1513
1513
|
|
|
1514
|
-
function readESF(_esfText) {
|
|
1514
|
+
function readESF (_esfText) {
|
|
1515
1515
|
// 24/02/2020 must do an EIS to DPT conversion.
|
|
1516
1516
|
// https://www.loxone.com/dede/kb/eibknx-datentypen/
|
|
1517
1517
|
// Format: Attuatori luci.Luci primo piano.0/0/1 Luce camera da letto EIS 1 'Switching' (1 Bit) Low
|
|
@@ -1607,7 +1607,7 @@ return msg;`,
|
|
|
1607
1607
|
}
|
|
1608
1608
|
|
|
1609
1609
|
// 23/08/2019 Delete unwanted CRLF in the GA description
|
|
1610
|
-
function correctCRLFInCSV(_csv) {
|
|
1610
|
+
function correctCRLFInCSV (_csv) {
|
|
1611
1611
|
let sOut = '' // fixed output text to return
|
|
1612
1612
|
let sChar = ''
|
|
1613
1613
|
let bStart = false
|
|
@@ -1640,7 +1640,7 @@ return msg;`,
|
|
|
1640
1640
|
}
|
|
1641
1641
|
|
|
1642
1642
|
// 26/02/2021 Used to send the messages if the node gateway is in EMULATION mode
|
|
1643
|
-
function sendEmulatedTelegram(_msg) {
|
|
1643
|
+
function sendEmulatedTelegram (_msg) {
|
|
1644
1644
|
// INPUT IS
|
|
1645
1645
|
// _msg = {
|
|
1646
1646
|
// grpaddr: '5/0/1',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const KNXAddress = require('./../KNXEngine/protocol/KNXAddress').KNXAddress
|
|
2
2
|
|
|
3
3
|
module.exports = function (RED) {
|
|
4
|
-
function knxUltimateViewer(config) {
|
|
4
|
+
function knxUltimateViewer (config) {
|
|
5
5
|
RED.nodes.createNode(this, config)
|
|
6
6
|
const node = this
|
|
7
7
|
node.server = RED.nodes.getNode(config.server)
|
|
@@ -96,7 +96,7 @@ module.exports = function (RED) {
|
|
|
96
96
|
} else if (typeof element.payload === 'object') {
|
|
97
97
|
// Is maybe a JSON?
|
|
98
98
|
try {
|
|
99
|
-
//sPayload += '<td>' + JSON.stringify(element.payload) + '</td>'
|
|
99
|
+
// sPayload += '<td>' + JSON.stringify(element.payload) + '</td>'
|
|
100
100
|
sPayload += '<td><i>' + element.rawPayload + '</i></td>'
|
|
101
101
|
} catch (error) {
|
|
102
102
|
sPayload += '<td>' + element.payload + '</td>'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const ping = require('ping')
|
|
2
2
|
|
|
3
3
|
module.exports = function (RED) {
|
|
4
|
-
function knxUltimateWatchDog(config) {
|
|
4
|
+
function knxUltimateWatchDog (config) {
|
|
5
5
|
RED.nodes.createNode(this, config)
|
|
6
6
|
const node = this
|
|
7
7
|
node.server = RED.nodes.getNode(config.server)
|
|
@@ -26,7 +26,7 @@ module.exports = function (RED) {
|
|
|
26
26
|
node.isWatchDog = true
|
|
27
27
|
node.checkLevel = config.checkLevel !== undefined ? config.checkLevel : 'Ethernet'
|
|
28
28
|
node.icountMessageInWindow = 0
|
|
29
|
-
node.alreadyNotifiedArray = new Array // 20/10/2022 array of already notified errors generated by every single KNX-Ultimate node. This prevents floading with repeated errors, in case of disconnection
|
|
29
|
+
node.alreadyNotifiedArray = new Array() // 20/10/2022 array of already notified errors generated by every single KNX-Ultimate node. This prevents floading with repeated errors, in case of disconnection
|
|
30
30
|
|
|
31
31
|
// Used to call the status update from the config node.
|
|
32
32
|
node.setNodeStatus = ({ fill, shape, text, payload, GA, dpt, devicename }) => {
|
|
@@ -42,7 +42,7 @@ module.exports = function (RED) {
|
|
|
42
42
|
|
|
43
43
|
if (!node.server) return
|
|
44
44
|
|
|
45
|
-
function handleTheDog() {
|
|
45
|
+
function handleTheDog () {
|
|
46
46
|
node.beatNumber += 1
|
|
47
47
|
if (node.beatNumber > node.maxRetry) {
|
|
48
48
|
// Confirmed connection error
|
|
@@ -110,7 +110,7 @@ module.exports = function (RED) {
|
|
|
110
110
|
|
|
111
111
|
// 22/10/2022 Find in the array of notified nodes. If found and equal, don't send.
|
|
112
112
|
// Warning: to be optimized: the watchdog will not emit any "red" message anymore, unless the error's description changes.
|
|
113
|
-
|
|
113
|
+
const oMsg = node.alreadyNotifiedArray.find(a => a.nodeid === msg.nodeid)
|
|
114
114
|
if (oMsg === undefined) {
|
|
115
115
|
node.alreadyNotifiedArray.push({ nodeid: msg.nodeid, description: msg.description })
|
|
116
116
|
node.send(msg)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
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
|
"mkdirp": "1.0.4",
|