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.
Files changed (39) hide show
  1. package/CHANGELOG.md +31 -17
  2. package/README.de.md +225 -0
  3. package/README.md +134 -187
  4. package/coe/coe-input.js +49 -36
  5. package/coe/coe-monitor.html +1 -1
  6. package/coe/coe-monitor.js +32 -41
  7. package/coe/coe-output.html +67 -28
  8. package/coe/coe-output.js +159 -24
  9. package/coe/config.js +14 -7
  10. package/coe/locales/de/coe-input.html +2 -2
  11. package/coe/locales/de/coe-input.json +8 -2
  12. package/coe/locales/de/coe-monitor.html +1 -2
  13. package/coe/locales/de/coe-monitor.json +9 -4
  14. package/coe/locales/de/coe-output.html +8 -2
  15. package/coe/locales/de/coe-output.json +11 -5
  16. package/coe/locales/de/config.html +3 -3
  17. package/coe/locales/de/config.json +1 -1
  18. package/coe/locales/en-US/coe-input.html +2 -2
  19. package/coe/locales/en-US/coe-input.json +8 -2
  20. package/coe/locales/en-US/coe-monitor.html +1 -2
  21. package/coe/locales/en-US/coe-monitor.json +9 -4
  22. package/coe/locales/en-US/coe-output.html +8 -2
  23. package/coe/locales/en-US/coe-output.json +10 -4
  24. package/coe/locales/en-US/config.html +3 -3
  25. package/coe/locales/en-US/config.json +1 -1
  26. package/coe/units-config.js +2 -1
  27. package/examples/in-out.json +56 -0
  28. package/examples/monitor.json +42 -0
  29. package/lib/coe-v1.js +152 -0
  30. package/lib/coe-v2.js +80 -119
  31. package/lib/protocol.js +19 -0
  32. package/lib/queueing.js +92 -137
  33. package/lib/units.js +7 -7
  34. package/lib/utils.js +71 -66
  35. package/package.json +17 -3
  36. package/__tests__/blockinfo.test.js +0 -24
  37. package/__tests__/conversion.test.js +0 -22
  38. package/__tests__/udp.test.js +0 -46
  39. package/lib/coe.js +0 -109
@@ -2,7 +2,7 @@
2
2
  "config": {
3
3
  "label": {
4
4
  "name": "Name",
5
- "localip": "Lokale IP",
5
+ "localip": "IP-Bereich",
6
6
  "address": "CMI Adresse",
7
7
  "coeVersion": "CoE Version"
8
8
  }
@@ -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, not recommended for production)</li>
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 value in Minutes</li>
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 Config",
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 Config",
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
- "idle": "Idle",
17
- "node": "Node"
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>Notes</h3>
13
- <p>Values are buffered for 50ms to reduce message frequency.</p>
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 Config",
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": "ready",
15
- "merged": "sent (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> Local IP:</b> IP Address range the UDP Port is listening on (Common values are 0.0.0.0 or 127.0.0.1)</li>
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>V1:</b> Default setting. Value range: ±32,767</li>
10
- <li><b>V2:</b> Extended, requires CMI FW ≥v1.43.1. Value range: ±2,147,483,647</li>
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>
@@ -2,7 +2,7 @@
2
2
  "config": {
3
3
  "label": {
4
4
  "name": "Name",
5
- "localip": "Local IP",
5
+ "localip": "IP Range",
6
6
  "address": "CMI Address",
7
7
  "coeVersion": "CoE Version"
8
8
  }
@@ -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
+ };