node-red-contrib-ta-cmi-coe 1.0.0 → 1.1.1
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 +31 -17
- package/README.de.md +225 -0
- package/README.md +134 -187
- package/coe/coe-input.js +49 -36
- package/coe/coe-monitor.html +1 -1
- package/coe/coe-monitor.js +32 -41
- package/coe/coe-output.html +67 -28
- package/coe/coe-output.js +159 -24
- package/coe/config.js +14 -7
- package/coe/locales/de/coe-input.html +2 -2
- package/coe/locales/de/coe-input.json +8 -2
- package/coe/locales/de/coe-monitor.html +1 -2
- package/coe/locales/de/coe-monitor.json +9 -4
- package/coe/locales/de/coe-output.html +8 -2
- package/coe/locales/de/coe-output.json +11 -5
- package/coe/locales/de/config.html +3 -3
- package/coe/locales/de/config.json +1 -1
- package/coe/locales/en-US/coe-input.html +2 -2
- package/coe/locales/en-US/coe-input.json +8 -2
- package/coe/locales/en-US/coe-monitor.html +1 -2
- package/coe/locales/en-US/coe-monitor.json +9 -4
- package/coe/locales/en-US/coe-output.html +8 -2
- package/coe/locales/en-US/coe-output.json +10 -4
- package/coe/locales/en-US/config.html +3 -3
- package/coe/locales/en-US/config.json +1 -1
- package/coe/units-config.js +2 -1
- package/examples/in-out.json +56 -0
- package/examples/monitor.json +42 -0
- package/lib/coe-v1.js +152 -0
- package/lib/coe-v2.js +80 -119
- package/lib/protocol.js +19 -0
- package/lib/queueing.js +92 -137
- package/lib/units.js +7 -7
- package/lib/utils.js +71 -66
- package/package.json +17 -3
- package/__tests__/blockinfo.test.js +0 -24
- package/__tests__/conversion.test.js +0 -22
- package/__tests__/udp.test.js +0 -46
- package/lib/coe.js +0 -109
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<p>Receives values from a TA CMI via CoE (CAN over Ethernet).</p>
|
|
3
3
|
<h3>Configuration</h3>
|
|
4
4
|
<ul>
|
|
5
|
-
<li><b>Node:</b> CAN node number (1-62, 0 = receive from any node
|
|
5
|
+
<li><b>Node:</b> CAN node number (1-62, 0 = receive from any node; not recommended for production)</li>
|
|
6
6
|
<li><b>Input:</b> Network input number (1-32)</li>
|
|
7
7
|
<li><b>Data Type:</b> Analog (1-32) or Digital (1-32)</li>
|
|
8
|
-
<li><b>Timeout:</b> CAN-Bus timeout
|
|
8
|
+
<li><b>Timeout:</b> CAN-Bus timeout (Value in minutes)</li>
|
|
9
9
|
</ul>
|
|
10
10
|
</script>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"coe-input": {
|
|
3
3
|
"label": {
|
|
4
4
|
"name": "Name",
|
|
5
|
-
"cmiconfig": "CMI
|
|
5
|
+
"cmiconfig": "CMI",
|
|
6
6
|
"nodeNumber": "Node",
|
|
7
7
|
"outputNumber": "Input",
|
|
8
8
|
"dataType": "Data Type",
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"status": {
|
|
15
15
|
"waiting": "waiting",
|
|
16
|
-
"waitingAny": "waiting (any node)"
|
|
16
|
+
"waitingAny": "waiting (any node)",
|
|
17
|
+
"open": "Open",
|
|
18
|
+
"closed": "Closed",
|
|
19
|
+
"on": "On",
|
|
20
|
+
"off": "Off",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No"
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script type="text/html" data-help-name="coe-monitor">
|
|
2
|
-
<p>Receives and monitors CoE (CAN over Ethernet) packets from all sources.</p>
|
|
3
|
-
<p>Ideal for debugging, logging, and monitoring total CoE traffic.</p>
|
|
2
|
+
<p>Receives and monitors CoE (CAN over Ethernet) packets from all sources. Ideal for debugging, logging, and monitoring total CoE traffic.</p>
|
|
4
3
|
<h3>Configuration</h3>
|
|
5
4
|
<ul>
|
|
6
5
|
<li>Optional: Filter packets from a specific CAN node only (0 = all)</li>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"coe-monitor": {
|
|
3
3
|
"label": {
|
|
4
4
|
"name": "Name",
|
|
5
|
-
"cmiconfig": "CMI
|
|
5
|
+
"cmiconfig": "CMI",
|
|
6
6
|
"filterNodeNumber": "Filter Node",
|
|
7
7
|
"filterDataType": "Filter Type",
|
|
8
8
|
"includeRaw": "Include Raw Data",
|
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
"filterNodeNumber": "0 = all"
|
|
13
13
|
},
|
|
14
14
|
"status": {
|
|
15
|
-
"monitoring": "Monitoring
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"monitoring": "Monitoring",
|
|
16
|
+
"node": "Node",
|
|
17
|
+
"open": "Open",
|
|
18
|
+
"closed": "Closed",
|
|
19
|
+
"on": "On",
|
|
20
|
+
"off": "Off",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No"
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
}
|
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
<li><b>Data Type:</b> Analog (numeric) or Digital (boolean)</li>
|
|
8
8
|
<li><b>Unit:</b> Measurement unit (analog only)</li>
|
|
9
9
|
</ul>
|
|
10
|
+
<h3>Sending Conditions</h3>
|
|
11
|
+
<ul>
|
|
12
|
+
<li><b>If change >:</b> Retransmit only, if the current value has changed by more than this value (analog values)</li>
|
|
13
|
+
<li><b>Blocking Time:</b> Retransmit only, if time has passed (seconds)</li>
|
|
14
|
+
<li><b>Interval Time:</b> If value has not changed, retransmit after interval (minutes)</li>
|
|
15
|
+
</ul>
|
|
10
16
|
<h3>Outputs</h3>
|
|
11
17
|
<p><b>2 outputs:</b> [0] Message passthrough, [1] Debug info (Hex, Block-State, Units)</p>
|
|
12
|
-
<h3>
|
|
13
|
-
<p>Values are buffered for
|
|
18
|
+
<h3>Note</h3>
|
|
19
|
+
<p>Values are buffered for 50 ms to reduce message frequency.</p>
|
|
14
20
|
</script>
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
"coe-output": {
|
|
3
3
|
"label": {
|
|
4
4
|
"name": "Name",
|
|
5
|
-
"cmiconfig": "CMI
|
|
5
|
+
"cmiconfig": "CMI",
|
|
6
6
|
"nodeNumber": "Node",
|
|
7
7
|
"outputNumber": "Output",
|
|
8
8
|
"dataType": "Data Type",
|
|
9
|
-
"unit": "Unit"
|
|
9
|
+
"unit": "Unit",
|
|
10
|
+
"sendConditions": "Sending Conditions:",
|
|
11
|
+
"minChange": "If change >",
|
|
12
|
+
"blockingTime": "Blocking Time (Sec)",
|
|
13
|
+
"maxInterval": "Interval Time (Min)"
|
|
10
14
|
},
|
|
11
15
|
"status": {
|
|
12
16
|
"noconfig": "No config",
|
|
13
17
|
"queued": "queued",
|
|
14
|
-
"ready": "
|
|
15
|
-
"merged": "
|
|
18
|
+
"ready": "Ready",
|
|
19
|
+
"merged": "Sent",
|
|
20
|
+
"retransmit": "Resent",
|
|
21
|
+
"blocked": "Blocked"
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<script type="text/html" data-help-name="cmiconfig">
|
|
2
2
|
<p><strong>Configuration:</strong></p>
|
|
3
3
|
<ul>
|
|
4
|
-
<li><b>
|
|
4
|
+
<li><b> IP Range:</b> IP Address range the UDP Port is listening on (Common values are 0.0.0.0 or 127.0.0.1)</li>
|
|
5
5
|
<li><b> Address:</b> IP Address of TA C.M.I used for sending CoE messages</li>
|
|
6
6
|
</ul>
|
|
7
7
|
<p><strong>CoE Version:</strong></p>
|
|
8
8
|
<ul>
|
|
9
|
-
<li><b>
|
|
10
|
-
<li><b>
|
|
9
|
+
<li><b>Version 1:</b> Default setting. Value range: ± 32,767</li>
|
|
10
|
+
<li><b>Version 2:</b> Extended, requires CMI FW ≥ v1.43.1. Value range: ± 2,147,483,647</li>
|
|
11
11
|
</ul>
|
|
12
12
|
<p>See Settings > CAN in the CMI menu.</p>
|
|
13
13
|
</script>
|
package/coe/units-config.js
CHANGED
|
@@ -26,7 +26,8 @@ module.exports = function(RED) {
|
|
|
26
26
|
localizedUnits[key] = {
|
|
27
27
|
name: lang === "de" ? unit.name_de : unit.name_en,
|
|
28
28
|
symb: lang === "de" ? unit.symb_de : unit.symb_en,
|
|
29
|
-
decimals: unit.decimals
|
|
29
|
+
decimals: unit.decimals,
|
|
30
|
+
digital: unit.digital ? true : false
|
|
30
31
|
};
|
|
31
32
|
});
|
|
32
33
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "mycmi",
|
|
4
|
+
"type": "cmiconfig",
|
|
5
|
+
"name": "My CMI",
|
|
6
|
+
"localip": "0.0.0.0",
|
|
7
|
+
"address": "192.168.0.100",
|
|
8
|
+
"coeVersion": 1
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "input_example",
|
|
12
|
+
"type": "coe-input",
|
|
13
|
+
"name": "Temperature Sensor",
|
|
14
|
+
"cmiconfig": "mycmi",
|
|
15
|
+
"nodeNumber": 10,
|
|
16
|
+
"outputNumber": 1,
|
|
17
|
+
"dataType": "analog",
|
|
18
|
+
"timeout": 20,
|
|
19
|
+
"x": 150,
|
|
20
|
+
"y": 100,
|
|
21
|
+
"wires": [["debug123"]]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "debug123",
|
|
25
|
+
"type": "debug",
|
|
26
|
+
"name": "Message",
|
|
27
|
+
"x": 350,
|
|
28
|
+
"y": 100
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "inject1",
|
|
32
|
+
"type": "inject",
|
|
33
|
+
"name": "Set Target Value",
|
|
34
|
+
"payload": "22.5",
|
|
35
|
+
"payloadType": "num",
|
|
36
|
+
"repeat": "",
|
|
37
|
+
"x": 150,
|
|
38
|
+
"y": 200,
|
|
39
|
+
"wires": [["output_example"]]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "output_example",
|
|
43
|
+
"type": "coe-output",
|
|
44
|
+
"name": "Target Value Heating",
|
|
45
|
+
"cmiconfig": "mycmi",
|
|
46
|
+
"nodeNumber": 11,
|
|
47
|
+
"outputNumber": 5,
|
|
48
|
+
"dataType": "analog",
|
|
49
|
+
"unit": 1,
|
|
50
|
+
"minChange": 1,
|
|
51
|
+
"blockingTime": 10,
|
|
52
|
+
"maxInterval": 5,
|
|
53
|
+
"x": 350,
|
|
54
|
+
"y": 200
|
|
55
|
+
}
|
|
56
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "monitor_example",
|
|
4
|
+
"type": "coe-monitor",
|
|
5
|
+
"name": "CoE Monitor",
|
|
6
|
+
"cmiconfig": "mycmi",
|
|
7
|
+
"filterNodeNumber": 0,
|
|
8
|
+
"filterDataType": "all",
|
|
9
|
+
"includeRaw": false,
|
|
10
|
+
"x": 250,
|
|
11
|
+
"y": 100,
|
|
12
|
+
"wires": [
|
|
13
|
+
[
|
|
14
|
+
"monitor_debug"
|
|
15
|
+
]
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "monitor_debug",
|
|
20
|
+
"type": "debug",
|
|
21
|
+
"name": "Debug Monitor",
|
|
22
|
+
"active": true,
|
|
23
|
+
"tosidebar": true,
|
|
24
|
+
"console": false,
|
|
25
|
+
"tostatus": false,
|
|
26
|
+
"complete": "payload",
|
|
27
|
+
"targetType": "msg",
|
|
28
|
+
"statusVal": "",
|
|
29
|
+
"statusType": "auto",
|
|
30
|
+
"x": 450,
|
|
31
|
+
"y": 100,
|
|
32
|
+
"wires": []
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "mycmi",
|
|
36
|
+
"type": "cmiconfig",
|
|
37
|
+
"name": "My CMI",
|
|
38
|
+
"localip": "0.0.0.0",
|
|
39
|
+
"address": "192.168.0.100",
|
|
40
|
+
"coeVersion": 1
|
|
41
|
+
}
|
|
42
|
+
]
|
package/lib/coe-v1.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoE V1 Protocol Support Module
|
|
3
|
+
*
|
|
4
|
+
* Copyright 2025 Florian Mayrhofer
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { getOutputsfromBlock, getBlockInfo, convertRawToValue, convertValueToRaw } = require('./utils');
|
|
10
|
+
|
|
11
|
+
// CoE V1 Parse message from buffer
|
|
12
|
+
function parsePacket(buffer) {
|
|
13
|
+
|
|
14
|
+
if (buffer.length !== 14) {
|
|
15
|
+
console.warn(`Received CoE packet with incorrect length of ${buffer.length} bytes (v1).`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Parse header
|
|
20
|
+
const nodeNumber = buffer.readUInt8(0);
|
|
21
|
+
const blockNumber = buffer.readUInt8(1);
|
|
22
|
+
|
|
23
|
+
let values = [];
|
|
24
|
+
let units = [];
|
|
25
|
+
|
|
26
|
+
if (blockNumber === 0 || blockNumber === 9) {
|
|
27
|
+
// digital
|
|
28
|
+
const bitField = buffer.readUInt16LE(2);
|
|
29
|
+
const unitId = buffer.readUInt8(11); // Block-wise unit id → byte 11
|
|
30
|
+
for (let i = 0; i < 16; i++) {
|
|
31
|
+
values.push((bitField >> i) & 1);
|
|
32
|
+
units.push(unitId);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// analog
|
|
36
|
+
for (let i = 0; i < 4; i++) {
|
|
37
|
+
const value = buffer.readInt16LE(2 + i * 2);
|
|
38
|
+
const unitId = buffer.readUInt8(10 + i);
|
|
39
|
+
|
|
40
|
+
const convertedValue = convertRawToValue(value, unitId, 1); // V1 decimals
|
|
41
|
+
values.push(convertedValue);
|
|
42
|
+
units.push(unitId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const valueBlock = {
|
|
47
|
+
nodeNumber: nodeNumber,
|
|
48
|
+
blockNumber: blockNumber,
|
|
49
|
+
values: values,
|
|
50
|
+
units: units
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return convertV1ToUniformFormat(valueBlock);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Convert V1 data into Uniform format
|
|
57
|
+
function convertV1ToUniformFormat(valueBlock) {
|
|
58
|
+
const blockNumber = valueBlock.blockNumber;
|
|
59
|
+
const dataType = (blockNumber === 0 || blockNumber === 9) ? 'digital' : 'analog';
|
|
60
|
+
const outputNumbers = getOutputsfromBlock(blockNumber, dataType);
|
|
61
|
+
|
|
62
|
+
// Create outputs object
|
|
63
|
+
const outputs = {};
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < outputNumbers.length; i++) {
|
|
66
|
+
const outputNumber = outputNumbers[i];
|
|
67
|
+
|
|
68
|
+
outputs[outputNumber] = {
|
|
69
|
+
value: valueBlock.values[i],
|
|
70
|
+
unit: valueBlock.units[i]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [{ nodeNumber: valueBlock.nodeNumber, blockNumber, dataType, outputs }];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create CoE Packet from values
|
|
78
|
+
function createPacket(nodeNumber, dataType, outputs) {
|
|
79
|
+
let buffer;
|
|
80
|
+
const blockState = convertUniformToV1Format(dataType, outputs);
|
|
81
|
+
const blockNumber = parseInt(Object.keys(blockState)[0], 10);
|
|
82
|
+
const blockData = blockState[Object.keys(blockState)[0]];
|
|
83
|
+
|
|
84
|
+
if (dataType === 'digital') {
|
|
85
|
+
buffer = Buffer.alloc(14);
|
|
86
|
+
buffer.writeUInt8(nodeNumber, 0);
|
|
87
|
+
buffer.writeUInt8(blockNumber, 1);
|
|
88
|
+
|
|
89
|
+
let bitField = 0;
|
|
90
|
+
for (let i = 0; i < 16; i++) {
|
|
91
|
+
if (blockData.values[i]) {
|
|
92
|
+
bitField |= (1 << i);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
buffer.writeUInt16LE(bitField, 2);
|
|
96
|
+
buffer.fill(0, 4, buffer.length);
|
|
97
|
+
|
|
98
|
+
} else { // analog
|
|
99
|
+
buffer = Buffer.alloc(14);
|
|
100
|
+
buffer.writeUInt8(nodeNumber, 0);
|
|
101
|
+
buffer.writeUInt8(blockNumber, 1);
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < 4; i++) {
|
|
104
|
+
const unitId = blockData.units ? blockData.units[i] : 0;
|
|
105
|
+
const rawValue = convertValueToRaw(blockData.values[i], unitId, 1); // V1 Decimal places
|
|
106
|
+
|
|
107
|
+
if (rawValue > 32767 || rawValue < -32768) {
|
|
108
|
+
console.warn(`Value ${blockData.values[i]} exceeds V1 limits. Consider using V2.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
buffer.writeInt16LE(Math.max(-32768, Math.min(32767, rawValue)), 2 + i * 2);
|
|
112
|
+
buffer.writeUInt8(unitId, 10 + i);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return buffer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Convert Uniform into V1 format
|
|
120
|
+
function convertUniformToV1Format(dataType, outputs) {
|
|
121
|
+
// Group Outputs by block
|
|
122
|
+
const blockState = {};
|
|
123
|
+
|
|
124
|
+
Object.entries(outputs).forEach(([outputKey, output]) => {
|
|
125
|
+
const isDigital = dataType === 'digital';
|
|
126
|
+
const outputNumber = parseInt(outputKey);
|
|
127
|
+
|
|
128
|
+
// Determine block number and position
|
|
129
|
+
const block = getBlockInfo(dataType, outputNumber);
|
|
130
|
+
const blockKey = block.number;
|
|
131
|
+
|
|
132
|
+
if (!blockState[blockKey]) {
|
|
133
|
+
blockState[blockKey] = {
|
|
134
|
+
values: isDigital ? new Array(16).fill(undefined) : new Array(4).fill(undefined),
|
|
135
|
+
units: isDigital ? new Array(16).fill(undefined) : new Array(4).fill(undefined)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Convert value & insert
|
|
140
|
+
blockState[blockKey].values[block.position] = isDigital ? (output.value ? 1 : 0) : output.value;
|
|
141
|
+
|
|
142
|
+
if (blockState[blockKey].units) {
|
|
143
|
+
blockState[blockKey].units[block.position] = output.unit;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return blockState;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
parsePacket,
|
|
151
|
+
createPacket
|
|
152
|
+
};
|