node-red-contrib-ta-cmi-coe 1.1.1 → 1.1.2
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 +7 -1
- package/README.de.md +4 -4
- package/README.md +3 -3
- package/coe/coe-input.html +1 -1
- package/coe/coe-input.js +28 -17
- package/coe/coe-monitor.html +1 -1
- package/coe/coe-monitor.js +19 -17
- package/coe/coe-output.html +1 -1
- package/coe/coe-output.js +18 -16
- package/coe/config.js +4 -5
- package/coe/locales/de/coe-input.html +2 -2
- package/coe/locales/de/coe-input.json +2 -0
- package/coe/locales/de/coe-monitor.json +1 -0
- package/coe/locales/de/coe-output.html +1 -1
- package/coe/locales/de/coe-output.json +1 -1
- package/coe/locales/en-US/coe-input.html +2 -2
- package/coe/locales/en-US/coe-input.json +2 -0
- package/coe/locales/en-US/coe-monitor.json +1 -0
- package/coe/locales/en-US/coe-output.html +1 -1
- package/coe/locales/en-US/coe-output.json +1 -1
- package/lib/queueing.js +3 -3
- package/lib/utils.js +29 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### Version 1.1.2
|
|
4
|
+
- Fixes wrong delta supressed error
|
|
5
|
+
- Fixes early timeout error
|
|
6
|
+
- Fixes lang type error (missing config node)
|
|
7
|
+
- Added node & output number validation
|
|
8
|
+
|
|
3
9
|
### Version 1.1.1
|
|
4
|
-
-
|
|
10
|
+
- Fixes wrong V2 output number
|
|
5
11
|
|
|
6
12
|
### Version 1.1.0
|
|
7
13
|
- Change to variable message size (similar to CoE V2 approach)
|
package/README.de.md
CHANGED
|
@@ -88,13 +88,13 @@ Auf der CMI unter **Einstellungen → Ausgänge → CoE**:
|
|
|
88
88
|
- **Eingang**: CAN-Bus Eingang (z.B. CAN 1)
|
|
89
89
|
- **IP**: IP-Adresse von Node-RED
|
|
90
90
|
- **Knoten**: Knoten-Nummer des Input Nodes
|
|
91
|
-
- **Netzwerkausgang**: Nummer des Ausgangs (1-32)
|
|
91
|
+
- **Netzwerkausgang**: Nummer des Ausgangs (1-32 in CoE V1 / 1-64 in CoE V2)
|
|
92
92
|
- **Sendebedingungen**: Unterdrückung kleiner & häufiger Änderungen, Intervall für wiederholtes Senden (nach Bedarf)
|
|
93
93
|
|
|
94
94
|
#### Für Senden an CMI (CoE Output):
|
|
95
95
|
Auf dem Regler: CAN-Eingang konfigurieren
|
|
96
96
|
- **Knoten**: Wert aus "Node Number" des Output Nodes
|
|
97
|
-
- **Ausgangsnummer**: Nummer des Ausgangs (1-32)
|
|
97
|
+
- **Ausgangsnummer**: Nummer des Ausgangs (1-32 in CoE V1 / 1-64 in CoE V2)
|
|
98
98
|
- **Messgröße**: "Automatisch" für Unit von Node-RED
|
|
99
99
|
|
|
100
100
|
## Node Typen
|
|
@@ -109,7 +109,6 @@ Empfängt Werte von der CMI.
|
|
|
109
109
|
payload: 22.5, // Der Wert
|
|
110
110
|
topic: "coe/10/analog/1", // Format: coe/{node}/{type}/{output}
|
|
111
111
|
coe: {
|
|
112
|
-
timestamp: 2026-01-08T // Eingangszeit
|
|
113
112
|
sourceIP: "192.168.1.100", // IP der CMI
|
|
114
113
|
nodeNumber: 10, // CAN Knoten-Nummer
|
|
115
114
|
dataType: "analog", // Datentyp
|
|
@@ -118,7 +117,8 @@ Empfängt Werte von der CMI.
|
|
|
118
117
|
state: 22.5, // Wert oder digitaler Zustand
|
|
119
118
|
unit: 1, // Unit ID (z.B. 1 = °C)
|
|
120
119
|
unitName: "Temperatur °C", // Unit Name
|
|
121
|
-
unitSymbol: "°C°" // Unit Symbol
|
|
120
|
+
unitSymbol: "°C°", // Unit Symbol
|
|
121
|
+
timestamp: 2026-01-08T11:04 // Eingangszeit
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
```
|
package/README.md
CHANGED
|
@@ -87,13 +87,13 @@ On the CMI under **Settings → Outputs → CoE**:
|
|
|
87
87
|
- **Input**: CAN bus input (e.g., CAN 1)
|
|
88
88
|
- **IP**: IP address of Node-RED
|
|
89
89
|
- **Node**: Node Number of the input node
|
|
90
|
-
- **Network Output**: Number of the output (1-32)
|
|
90
|
+
- **Network Output**: Number of the output (1-32 in CoE V1 / 1-64 in CoE V2)
|
|
91
91
|
- **Sending Conditions**: Suppression of small & frequent changes, interval for repeated sending (as required)
|
|
92
92
|
|
|
93
93
|
#### For sending to the CMI (CoE Output):
|
|
94
94
|
On the controller: Configure the CAN input
|
|
95
95
|
- **Node**: Value from the "Node Number" of the output node
|
|
96
|
-
- **Output Number**: Number of the output (1-32)
|
|
96
|
+
- **Output Number**: Number of the output (1-32 in CoE V1 / 1-64 in CoE V2)
|
|
97
97
|
- **Measured Unit**: "Automatic" for Node-RED Unit
|
|
98
98
|
|
|
99
99
|
## Node Types
|
|
@@ -108,7 +108,6 @@ Receives values from the CMI.
|
|
|
108
108
|
payload: 22.5, // The value
|
|
109
109
|
topic: "coe/10/analog/1", // Format: coe/{node}/{type}/{output}
|
|
110
110
|
coe: {
|
|
111
|
-
timestamp: 2026-01-08T // Reception Time
|
|
112
111
|
sourceIP: "192.168.1.100", // IP of the CMI
|
|
113
112
|
nodeNumber: 10, // CAN Node Number
|
|
114
113
|
dataType: "analog", // Data Type
|
|
@@ -118,6 +117,7 @@ Receives values from the CMI.
|
|
|
118
117
|
unit: 1, // Unit ID (z.B. 1 = °C)
|
|
119
118
|
unitName: "Temperature °C", // Unit name
|
|
120
119
|
unitSymbol: "°C°", // Unit symbol
|
|
120
|
+
timestamp: 2026-01-08T11:04 // Reception Time
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
```
|
package/coe/coe-input.html
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
</div>
|
|
46
46
|
<div class="form-row">
|
|
47
47
|
<label for="node-input-outputNumber"><i class="fa fa-arrow-down"></i> <span data-i18n="coe-input.label.outputNumber"></span></label>
|
|
48
|
-
<input type="number" id="node-input-outputNumber" placeholder="1" min="1" max="
|
|
48
|
+
<input type="number" id="node-input-outputNumber" placeholder="1" min="1" max="64">
|
|
49
49
|
</div>
|
|
50
50
|
<div class="form-row">
|
|
51
51
|
<label for="node-input-dataType"><i class="fa fa-list"></i> <span data-i18n="coe-input.label.dataType"></span></label>
|
package/coe/coe-input.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
module.exports = function(RED) {
|
|
10
10
|
'use strict';
|
|
11
|
-
const { getUnitInfo, getDigitalStateKey, mergeNodeData, createEmptyState } = require('../lib/utils');
|
|
11
|
+
const { Validate, getUnitInfo, getDigitalStateKey, mergeNodeData, createEmptyState } = require('../lib/utils');
|
|
12
12
|
|
|
13
13
|
// CoE Input Node (receiving values)
|
|
14
14
|
function CoEInputNode(config) {
|
|
@@ -19,16 +19,17 @@ module.exports = function(RED) {
|
|
|
19
19
|
node.cmiConfig = RED.nodes.getNode(config.cmiconfig);
|
|
20
20
|
|
|
21
21
|
if (!node.cmiConfig) {
|
|
22
|
-
node.error("CMI
|
|
22
|
+
node.error("No CMI config assigned to CoE Input node.");
|
|
23
|
+
node.status({fill:"red", shape:"ring", text:"coe-input.status.noconfig"});
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
node.cmiAddress = node.cmiConfig.address;
|
|
27
|
+
node.cmiAddress = node.cmiConfig.address || "";
|
|
27
28
|
node.coeVersion = node.cmiConfig.coeVersion || 1;
|
|
28
|
-
node.lang = node.cmiConfig
|
|
29
|
-
node.nodeNumber =
|
|
30
|
-
node.outputNumber =
|
|
31
|
-
node.dataType = config.dataType
|
|
29
|
+
node.lang = node.cmiConfig?.lang || "en";
|
|
30
|
+
node.nodeNumber = Validate.node(config.nodeNumber, true);
|
|
31
|
+
node.outputNumber = Validate.output(config.outputNumber, node.coeVersion);
|
|
32
|
+
node.dataType = Validate.type(config.dataType);
|
|
32
33
|
|
|
33
34
|
// State management for LKGVs (Last Known Good Values) per block
|
|
34
35
|
node.canNodeState = {};
|
|
@@ -59,7 +60,7 @@ module.exports = function(RED) {
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
// Filter dataType number
|
|
62
|
-
if (canNode.dataType !== node.dataType) {
|
|
63
|
+
if (canNode.dataType !== node.dataType.long) {
|
|
63
64
|
continue;
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -81,7 +82,7 @@ module.exports = function(RED) {
|
|
|
81
82
|
continue;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
if (node.dataType === 'analog') {
|
|
85
|
+
if (node.dataType.long === 'analog') {
|
|
85
86
|
value = output.value;
|
|
86
87
|
unit = output.unit;
|
|
87
88
|
state = value;
|
|
@@ -96,17 +97,17 @@ module.exports = function(RED) {
|
|
|
96
97
|
const unitInfo = getUnitInfo(unit, node.lang);
|
|
97
98
|
const msg = {
|
|
98
99
|
payload: value,
|
|
99
|
-
topic: `coe/${node.nodeNumber || mergedCanNode.nodeNumber}/${node.dataType}/${node.outputNumber}`,
|
|
100
|
+
topic: `coe/${node.nodeNumber || mergedCanNode.nodeNumber}/${node.dataType.long}/${node.outputNumber}`,
|
|
100
101
|
coe: {
|
|
101
|
-
timestamp: received.timestamp,
|
|
102
102
|
sourceIP: received.sourceIP,
|
|
103
103
|
nodeNumber: mergedCanNode.nodeNumber,
|
|
104
|
-
dataType: node.dataType,
|
|
104
|
+
dataType: node.dataType.long,
|
|
105
105
|
outputNumber: node.outputNumber,
|
|
106
106
|
state: state,
|
|
107
107
|
unit: unit,
|
|
108
108
|
unitName: unitInfo.name,
|
|
109
|
-
unitSymbol: unitInfo.symbol
|
|
109
|
+
unitSymbol: unitInfo.symbol,
|
|
110
|
+
timestamp: received.timestamp
|
|
110
111
|
// raw: received.rawBuffer ? received.rawBuffer.toString('hex').toUpperCase() : null
|
|
111
112
|
}
|
|
112
113
|
};
|
|
@@ -115,7 +116,7 @@ module.exports = function(RED) {
|
|
|
115
116
|
node.send(msg);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
currentNodeText = `${state} ${unitInfo.symbol || ''}
|
|
119
|
+
currentNodeText = `${state} ${unitInfo.symbol || ''}` // Caching last Node text
|
|
119
120
|
|
|
120
121
|
node.status({
|
|
121
122
|
fill: "green",
|
|
@@ -131,24 +132,34 @@ module.exports = function(RED) {
|
|
|
131
132
|
};
|
|
132
133
|
|
|
133
134
|
node.cmiConfig.registerListener(listener);
|
|
135
|
+
|
|
136
|
+
resetTimeout(); // Set initial timeout
|
|
134
137
|
|
|
135
138
|
// Reset CoE Timeout
|
|
136
139
|
function resetTimeout() {
|
|
137
140
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
141
|
+
|
|
142
|
+
// Fallback status text (initial timeout)
|
|
143
|
+
const statusText = currentNodeText ? `${currentNodeText} (Timeout)` : node._("coe-input.status.initialTimeout");
|
|
144
|
+
|
|
138
145
|
timeoutTimer = setTimeout(() => {
|
|
139
|
-
node.status({ fill: "red", shape: "dot", text:
|
|
146
|
+
node.status({ fill: "red", shape: "dot", text: statusText });
|
|
140
147
|
}, timeoutMs);
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
// Status information, including if filtered
|
|
144
151
|
if (node.nodeNumber === 0) {
|
|
145
|
-
node.status({fill: "yellow", shape: "ring", text: "coe-input.status.waitingAny"});
|
|
152
|
+
node.status({fill: "yellow", shape: "ring", text: node._("coe-input.status.waitingAny") + ` [v${node.coeVersion}]`});
|
|
146
153
|
} else {
|
|
147
|
-
node.status({fill: "grey", shape: "ring", text: "coe-input.status.waiting"});
|
|
154
|
+
node.status({fill: "grey", shape: "ring", text: "ᐅ" + node.dataType.short + " " + node.nodeNumber + "/" + node.outputNumber + " " + node._("coe-input.status.waiting") + ` [v${node.coeVersion}]`});
|
|
148
155
|
}
|
|
149
156
|
|
|
150
157
|
node.on('close', function() {
|
|
151
158
|
node.cmiConfig.unregisterListener(listener);
|
|
159
|
+
|
|
160
|
+
if (timeoutTimer) {
|
|
161
|
+
clearTimeout(timeoutTimer);
|
|
162
|
+
}
|
|
152
163
|
});
|
|
153
164
|
|
|
154
165
|
}
|
package/coe/coe-monitor.html
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
<div class="form-row">
|
|
49
49
|
<label for="node-input-filterDataType"><i class="fa fa-filter"></i> <span data-i18n="coe-monitor.label.filterDataType"></span></label>
|
|
50
50
|
<select id="node-input-filterDataType">
|
|
51
|
-
<option value="" data-i18n="coe-monitor.label.all"></option>
|
|
51
|
+
<option value="all" data-i18n="coe-monitor.label.all"></option>
|
|
52
52
|
<option value="analog">Analog</span></option>
|
|
53
53
|
<option value="digital">Digital</span></option>
|
|
54
54
|
</select>
|
package/coe/coe-monitor.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
module.exports = function(RED) {
|
|
10
10
|
'use strict';
|
|
11
|
-
const { getUnitInfo, getDigitalStateKey } = require('../lib/utils')
|
|
11
|
+
const { Validate, getUnitInfo, getDigitalStateKey } = require('../lib/utils')
|
|
12
12
|
|
|
13
13
|
function CoEMonitorNode(config) {
|
|
14
14
|
RED.nodes.createNode(this, config);
|
|
@@ -16,19 +16,23 @@ module.exports = function(RED) {
|
|
|
16
16
|
node._ = RED._;
|
|
17
17
|
|
|
18
18
|
node.cmiConfig = RED.nodes.getNode(config.cmiconfig);
|
|
19
|
-
|
|
20
|
-
const lang = node.cmiConfig.lang;
|
|
21
|
-
const coeVersion = node.cmiConfig.coeVersion || 1;
|
|
22
19
|
|
|
23
20
|
if (!node.cmiConfig) {
|
|
24
|
-
node.error("CMI
|
|
21
|
+
node.error("No CMI config assigned to CoE Monitor node.");
|
|
22
|
+
node.status({fill:"red", shape:"ring", text:"coe-monitor.status.noconfig"});
|
|
25
23
|
return;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
node.
|
|
29
|
-
node.
|
|
26
|
+
node.lang = node.cmiConfig?.lang || "en";
|
|
27
|
+
node.coeVersion = node.cmiConfig.coeVersion || 1;
|
|
28
|
+
node.filterNodeNumber = Validate.node(config.filterNodeNumber, true);
|
|
29
|
+
node.filterDataType = Validate.type(config.filterDataType);
|
|
30
30
|
node.includeRaw = config.includeRaw || false;
|
|
31
31
|
|
|
32
|
+
const isFilteringByNode = (node.filterNodeNumber !== null && node.filterNodeNumber !== 0);
|
|
33
|
+
const isFilteringByDataType = node.filterDataType.isDigital !== null;
|
|
34
|
+
const filterText = (!isFilteringByNode && !isFilteringByDataType) ? "" : "ᐅ" + node.filterDataType.short + " " + node.filterNodeNumber;
|
|
35
|
+
|
|
32
36
|
let packetCount = 0;
|
|
33
37
|
let lastUpdate = Date.now();
|
|
34
38
|
|
|
@@ -41,17 +45,15 @@ module.exports = function(RED) {
|
|
|
41
45
|
for (let canNode of received.data) {
|
|
42
46
|
if (!canNode) continue;
|
|
43
47
|
|
|
44
|
-
if (
|
|
45
|
-
node.filterNodeNumber !== 0 &&
|
|
46
|
-
canNode.nodeNumber !== node.filterNodeNumber) {
|
|
48
|
+
if (isFilteringByNode && canNode.nodeNumber !== node.filterNodeNumber) {
|
|
47
49
|
continue;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
const isDigital = (canNode.dataType === 'digital'); // Filter by Data Type
|
|
51
53
|
const isAnalog = !isDigital;
|
|
52
54
|
|
|
53
|
-
if (node.filterDataType === 'analog' && !isAnalog) continue;
|
|
54
|
-
if (node.filterDataType === 'digital' && !isDigital) continue;
|
|
55
|
+
if (node.filterDataType.long === 'analog' && !isAnalog) continue;
|
|
56
|
+
if (node.filterDataType.long === 'digital' && !isDigital) continue;
|
|
55
57
|
|
|
56
58
|
packetCount++;
|
|
57
59
|
lastUpdate = Date.now();
|
|
@@ -59,12 +61,12 @@ module.exports = function(RED) {
|
|
|
59
61
|
// Build message
|
|
60
62
|
const msg = {
|
|
61
63
|
payload: {
|
|
62
|
-
timestamp: new Date().toISOString(),
|
|
63
64
|
sourceIP: received.sourceIP,
|
|
64
65
|
version: received.version,
|
|
65
66
|
nodeNumber: canNode.nodeNumber,
|
|
66
67
|
...(canNode.blockNumber && { blockNumber: canNode.blockNumber}),
|
|
67
|
-
dataType: canNode.dataType
|
|
68
|
+
dataType: canNode.dataType,
|
|
69
|
+
timestamp: new Date().toISOString()
|
|
68
70
|
},
|
|
69
71
|
topic: `coe/monitor/${canNode.nodeNumber}/block/${canNode.dataType}`
|
|
70
72
|
};
|
|
@@ -74,7 +76,7 @@ module.exports = function(RED) {
|
|
|
74
76
|
|
|
75
77
|
if (canNode.outputs) {
|
|
76
78
|
Object.entries(canNode.outputs).forEach(([outputNumber, output]) => {
|
|
77
|
-
const unitInfo = getUnitInfo(output.unit, lang);
|
|
79
|
+
const unitInfo = getUnitInfo(output.unit, node.lang);
|
|
78
80
|
const translationKey = getDigitalStateKey(output.unit, output.value, "coe-monitor.status.");
|
|
79
81
|
|
|
80
82
|
valuesDetailed[outputNumber] = {
|
|
@@ -106,7 +108,7 @@ module.exports = function(RED) {
|
|
|
106
108
|
};
|
|
107
109
|
|
|
108
110
|
node.cmiConfig.registerListener(listener);
|
|
109
|
-
node.status({fill: "grey", shape: "ring", text: node._("coe-monitor.status.monitoring") + "..."});
|
|
111
|
+
node.status({fill: "grey", shape: "ring", text: node._("coe-monitor.status.monitoring") + " " + filterText + "..."});
|
|
110
112
|
|
|
111
113
|
// Status Update Timer (shows last activity)
|
|
112
114
|
const statusTimer = setInterval(() => {
|
|
@@ -115,7 +117,7 @@ module.exports = function(RED) {
|
|
|
115
117
|
node.status({
|
|
116
118
|
fill: "yellow",
|
|
117
119
|
shape: "ring",
|
|
118
|
-
text: node._("coe-monitor.status.monitoring") + ` ${secsSinceUpdate}s - ${packetCount} Pkt. [v${coeVersion}]`
|
|
120
|
+
text: node._("coe-monitor.status.monitoring") + " " + filterText + ` ${secsSinceUpdate}s - ${packetCount} Pkt. [v${node.coeVersion}]`
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
123
|
}, 5000);
|
package/coe/coe-output.html
CHANGED
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
</div>
|
|
108
108
|
<div class="form-row">
|
|
109
109
|
<label for="node-input-outputNumber"><i class="fa fa-arrow-up"></i> <span data-i18n="coe-output.label.outputNumber"></span></label>
|
|
110
|
-
<input type="number" id="node-input-outputNumber" placeholder="1" min="1" max="
|
|
110
|
+
<input type="number" id="node-input-outputNumber" placeholder="1" min="1" max="64">
|
|
111
111
|
</div>
|
|
112
112
|
<div class="form-row">
|
|
113
113
|
<label for="node-input-dataType"><i class="fa fa-list"></i> <span data-i18n="coe-output.label.dataType"></span></label>
|
package/coe/coe-output.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
module.exports = function(RED) {
|
|
10
10
|
'use strict';
|
|
11
|
-
const { getBlockInfo } = require('../lib/utils')
|
|
11
|
+
const { Validate, getBlockInfo } = require('../lib/utils')
|
|
12
12
|
const { queueAndSend } = require('../lib/queueing');
|
|
13
13
|
|
|
14
14
|
function CoEOutputNode(config) {
|
|
@@ -19,22 +19,24 @@ module.exports = function(RED) {
|
|
|
19
19
|
node.cmiConfig = RED.nodes.getNode(config.cmiconfig);
|
|
20
20
|
|
|
21
21
|
if (!node.cmiConfig) {
|
|
22
|
-
node.error("
|
|
22
|
+
node.error("No CMI config assigned to CoE Output node.");
|
|
23
23
|
node.status({fill:"red", shape:"ring", text:"coe-output.status.noconfig"});
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
node.cmiAddress = node.cmiConfig.address;
|
|
27
|
+
node.cmiAddress = node.cmiConfig.address || "";
|
|
28
28
|
node.coeVersion = node.cmiConfig.coeVersion || 1;
|
|
29
|
-
node.nodeNumber =
|
|
30
|
-
node.outputNumber =
|
|
31
|
-
node.dataType = config.dataType
|
|
29
|
+
node.nodeNumber = Validate.node(config.nodeNumber, false);
|
|
30
|
+
node.outputNumber = Validate.output(config.outputNumber, node.coeVersion);
|
|
31
|
+
node.dataType = Validate.type(config.dataType);
|
|
32
32
|
node.unit = parseInt(config.unit) || 0;
|
|
33
33
|
|
|
34
|
+
node.readyText = "ᐅ" + node.dataType.short + " " + node.nodeNumber + "/" + node.outputNumber + " " + node._("coe-output.status.ready") + ` [v${node.coeVersion}]`
|
|
35
|
+
|
|
34
36
|
if (node.coeVersion === 2) {
|
|
35
|
-
node.queueKey = node.nodeNumber + '-' + node.dataType;
|
|
37
|
+
node.queueKey = node.nodeNumber + '-' + node.dataType.short;
|
|
36
38
|
} else {
|
|
37
|
-
node.block = getBlockInfo(node.dataType, node.outputNumber);
|
|
39
|
+
node.block = getBlockInfo(node.dataType.short, node.outputNumber);
|
|
38
40
|
node.queueKey = node.nodeNumber + '-' + node.block.number;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -52,18 +54,20 @@ module.exports = function(RED) {
|
|
|
52
54
|
node.lastInputTime = 0;
|
|
53
55
|
|
|
54
56
|
node.intervalTimer = null;
|
|
57
|
+
|
|
58
|
+
node.status({fill: "grey", shape: "ring", text: node.readyText});
|
|
55
59
|
|
|
56
60
|
node.on('input', function(msg) {
|
|
57
61
|
node.lastInputTime = Date.now();
|
|
58
62
|
const timeSinceLastSend = (node.lastInputTime - node.lastSentTime) / 1000; // Seconds
|
|
59
|
-
|
|
60
|
-
node.lastReceivedMsg = msg;
|
|
61
|
-
|
|
63
|
+
|
|
62
64
|
// Prepare last received values
|
|
63
65
|
const payloadValue = parseFloat(msg.payload);
|
|
64
66
|
node.lastReceivedValue = isNaN(payloadValue) ? 0 : payloadValue;
|
|
65
67
|
node.lastReceivedUnit = (msg.coe && msg.coe.unit !== undefined) ? parseInt(msg.coe.unit) : node.unit;
|
|
68
|
+
node.lastReceivedMsg = msg;
|
|
66
69
|
|
|
70
|
+
const isSupressed = checkSuppressCondition(node, timeSinceLastSend);
|
|
67
71
|
|
|
68
72
|
// Filter based on sending conditions
|
|
69
73
|
if (isSupressed > 0) {
|
|
@@ -101,8 +105,6 @@ module.exports = function(RED) {
|
|
|
101
105
|
node.blockingTimer = null;
|
|
102
106
|
}
|
|
103
107
|
});
|
|
104
|
-
|
|
105
|
-
node.status({fill:"grey", shape:"ring", text:node._("coe-output.status.ready") + ` [v${node.coeVersion}]`});
|
|
106
108
|
|
|
107
109
|
// Start the sending interval timer
|
|
108
110
|
function setIntervalTimer() {
|
|
@@ -129,7 +131,7 @@ module.exports = function(RED) {
|
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
setTimeout(() => {
|
|
132
|
-
node.status({fill: "grey", shape: "ring", text: node.
|
|
134
|
+
node.status({fill: "grey", shape: "ring", text: node.readyText});
|
|
133
135
|
}, 5000);
|
|
134
136
|
|
|
135
137
|
setIntervalTimer(); // Restart interval timer
|
|
@@ -179,9 +181,9 @@ module.exports = function(RED) {
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
// Check size of value change
|
|
182
|
-
if (node.dataType === 'analog') {
|
|
184
|
+
if (node.dataType.long === 'analog' && node.minChange > 0) {
|
|
183
185
|
const delta = Math.abs(node.lastReceivedValue - node.lastSentValue);
|
|
184
|
-
if (
|
|
186
|
+
if (delta < node.minChange) {
|
|
185
187
|
isSuppressed = 2;
|
|
186
188
|
blockReason = `Δ${delta.toFixed(2)} < ${node.minChange}`;
|
|
187
189
|
}
|
package/coe/config.js
CHANGED
|
@@ -19,12 +19,11 @@ module.exports = function(RED) {
|
|
|
19
19
|
RED.nodes.createNode(this, config);
|
|
20
20
|
const node = this;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// Determine backend language
|
|
23
|
+
const globalLang = (RED.settings?.lang || "en").toLowerCase();
|
|
24
|
+
if (globalLang.startsWith("de")) {
|
|
25
25
|
node.lang = "de";
|
|
26
|
-
|
|
27
|
-
default:
|
|
26
|
+
} else {
|
|
28
27
|
node.lang = "en";
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<h3>Konfiguration</h3>
|
|
4
4
|
<ul>
|
|
5
5
|
<li><b>Knoten:</b> CAN Knoten-Nummer (1-62, 0 = von beliebigem Knoten empfangen; nicht empfohlen in Produktivbetrieb)</li>
|
|
6
|
-
<li><b>Eingang:</b> Nummer des Netzwerkeingangs (1-32)</li>
|
|
7
|
-
<li><b>Datentyp:</b> Analog
|
|
6
|
+
<li><b>Eingang:</b> Nummer des Netzwerkeingangs (CoE-Version 1: 1-32, CoE-Version 2: 1-64)</li>
|
|
7
|
+
<li><b>Datentyp:</b> Analog oder Digital</li>
|
|
8
8
|
<li><b>Timeout:</b> CAN-Bus Timeout (Wert in Minuten)</li>
|
|
9
9
|
</ul>
|
|
10
10
|
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h3>Konfiguration</h3>
|
|
4
4
|
<ul>
|
|
5
5
|
<li><b>Knoten:</b> CAN Knoten-Nummer (1-62)</li>
|
|
6
|
-
<li><b>Eingang:</b> Nummer des Netzwerkausgangs (
|
|
6
|
+
<li><b>Eingang:</b> Nummer des Netzwerkausgangs (CoE-Version 1: 1-32, CoE-Version 2: 1-64)</li>
|
|
7
7
|
<li><b>Datentyp:</b> Analog (numerisch) oder Digital (boolean)</li>
|
|
8
8
|
<li><b>Einheit:</b> Messeinheit (nur für analog)</li>
|
|
9
9
|
</ul>
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<h3>Configuration</h3>
|
|
4
4
|
<ul>
|
|
5
5
|
<li><b>Node:</b> CAN node number (1-62, 0 = receive from any node; not recommended for production)</li>
|
|
6
|
-
<li><b>Input:</b> Network input number (1-32)</li>
|
|
7
|
-
<li><b>Data Type:</b> Analog
|
|
6
|
+
<li><b>Input:</b> Network input number (CoE version 1: 1-32, CoE version 2: 1-64))</li>
|
|
7
|
+
<li><b>Data Type:</b> Analog or Digital</li>
|
|
8
8
|
<li><b>Timeout:</b> CAN-Bus timeout (Value in minutes)</li>
|
|
9
9
|
</ul>
|
|
10
10
|
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h3>Configuration</h3>
|
|
4
4
|
<ul>
|
|
5
5
|
<li><b>Node:</b> CAN node number (1-62)</li>
|
|
6
|
-
<li><b>Input:</b> Network output number (
|
|
6
|
+
<li><b>Input:</b> Network output number (CoE version 1: 1-32, CoE version 2: 1-64)</li>
|
|
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>
|
package/lib/queueing.js
CHANGED
|
@@ -26,7 +26,7 @@ function getOutputState(node, queueKey) {
|
|
|
26
26
|
if (!outputStateCache[queueKey]) {
|
|
27
27
|
outputStateCache[queueKey] = {
|
|
28
28
|
nodeNumber: node.nodeNumber,
|
|
29
|
-
dataType: node.dataType,
|
|
29
|
+
dataType: node.dataType.long,
|
|
30
30
|
outputs: {}
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -93,7 +93,7 @@ function queueAndSend(node) {
|
|
|
93
93
|
|
|
94
94
|
const now = Date.now();
|
|
95
95
|
const mergedText = node._("coe-output.status.merged");
|
|
96
|
-
const readyText = node.
|
|
96
|
+
const readyText = node.readyText;
|
|
97
97
|
|
|
98
98
|
queuedState[queueKey].participating.forEach(participatingNode => {
|
|
99
99
|
const outputNumber = participatingNode.outputNumber;
|
|
@@ -108,7 +108,7 @@ function queueAndSend(node) {
|
|
|
108
108
|
text: `${mergedText} [v${coeVersion}]`
|
|
109
109
|
});
|
|
110
110
|
setTimeout(() => {
|
|
111
|
-
participatingNode.status({fill: "grey", shape: "ring", text:
|
|
111
|
+
participatingNode.status({fill: "grey", shape: "ring", text: readyText});
|
|
112
112
|
}, 5000);
|
|
113
113
|
|
|
114
114
|
// Send debug output on the node outputs: [original msg, debug info]
|
package/lib/utils.js
CHANGED
|
@@ -8,6 +8,33 @@
|
|
|
8
8
|
|
|
9
9
|
const UNITS = require ('./units.js');
|
|
10
10
|
|
|
11
|
+
// CAN Node, Output & Type validator
|
|
12
|
+
const Validate = {
|
|
13
|
+
node: function(value, allowZero) {
|
|
14
|
+
const min = allowZero ? 0 : 1;
|
|
15
|
+
const max = 62;
|
|
16
|
+
const num = parseInt(value, 10) || min;
|
|
17
|
+
return Math.min(Math.max(num, min), max);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
output: function(value, version) {
|
|
21
|
+
const max = (version === 2 || version === "2") ? 64 : 32;
|
|
22
|
+
const num = parseInt(value, 10) || 1;
|
|
23
|
+
return Math.min(Math.max(num, 1), max);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
types: {
|
|
27
|
+
"analog": { long: "analog", short: "A", isDigital: false },
|
|
28
|
+
"digital": { long: "digital", short: "D", isDigital: true },
|
|
29
|
+
"all": { long: "all", short: "", isDigital: null }
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Return a type object
|
|
33
|
+
type: function(input) {
|
|
34
|
+
return this.types[input] || this.types["analog"];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
11
38
|
// Utilities for unit conversion
|
|
12
39
|
function convertRawToValue(rawValue, unitId, coeVersion) {
|
|
13
40
|
const unitDecimals = getUnitDecimals(unitId, coeVersion);
|
|
@@ -87,21 +114,18 @@ function getUnitLanguage(lang) {
|
|
|
87
114
|
|
|
88
115
|
// Translate output number to block position (CoE V1)
|
|
89
116
|
function getBlockInfo(dataType, outputNumber) {
|
|
90
|
-
let blockNumber, position
|
|
117
|
+
let blockNumber, position;
|
|
91
118
|
outputNumber = parseInt(outputNumber);
|
|
92
119
|
|
|
93
120
|
if (isNaN(outputNumber) || outputNumber < 1) { // Default to block 1 position 0
|
|
94
121
|
blockNumber = 1;
|
|
95
122
|
position = 0;
|
|
96
|
-
dType = 'a';
|
|
97
123
|
} else {
|
|
98
124
|
if (dataType === 'analog') {
|
|
99
125
|
// Analog: Outputs 1..32 → Blocks 1..8 (4 Outputs each)
|
|
100
126
|
blockNumber = Math.floor((outputNumber - 1) / 4) + 1; // 1..8
|
|
101
127
|
position = (outputNumber - 1) % 4; // 0..3
|
|
102
|
-
dType = 'a';
|
|
103
128
|
} else {
|
|
104
|
-
dType = 'd';
|
|
105
129
|
// Digital: Outputs 1..16 → Block 0, 17..32 → Block 9
|
|
106
130
|
if (outputNumber <= 16) {
|
|
107
131
|
blockNumber = 0;
|
|
@@ -157,6 +181,7 @@ function createEmptyState(incomingBlock) {
|
|
|
157
181
|
}
|
|
158
182
|
|
|
159
183
|
module.exports = {
|
|
184
|
+
Validate,
|
|
160
185
|
convertRawToValue,
|
|
161
186
|
convertValueToRaw,
|
|
162
187
|
getUnitInfo,
|