node-red-contrib-adder 1.0.0
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/README.md +33 -0
- package/adder.html +75 -0
- package/adder.js +85 -0
- package/locales/de/adder.json +19 -0
- package/locales/en/adder.json +19 -0
- package/locales/es/adder.json +19 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# node-red-contrib-rpo-adder
|
|
2
|
+
|
|
3
|
+
A Node-RED node: **Adder**
|
|
4
|
+
|
|
5
|
+
Collects numeric values from named topics and outputs their sum or weighted average.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install node-red-contrib-rpo-adder
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or via the Node-RED Palette Manager.
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
- **payload** (number): Value for the slot identified by `msg.topic`
|
|
18
|
+
|
|
19
|
+
## Outputs
|
|
20
|
+
|
|
21
|
+
- **Output 1**: Sum or weighted average; `msg.values` contains per-topic values
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
| Parameter | Description |
|
|
26
|
+
|-----------|-------------|
|
|
27
|
+
| Topics / weights | Comma-separated list of `topic:weight` pairs |
|
|
28
|
+
| Mode | Sum or weighted average |
|
|
29
|
+
| Min. required topics | Minimum topics with values before outputting |
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT
|
package/adder.html
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('adder', {
|
|
3
|
+
category: 'rpo',
|
|
4
|
+
color: '#D4600A',
|
|
5
|
+
defaults: {
|
|
6
|
+
"name": {
|
|
7
|
+
"value": ""
|
|
8
|
+
},
|
|
9
|
+
"mode": {
|
|
10
|
+
"value": "sum"
|
|
11
|
+
},
|
|
12
|
+
"topics": {
|
|
13
|
+
"value": [
|
|
14
|
+
{
|
|
15
|
+
"topic": "a",
|
|
16
|
+
"label": "A",
|
|
17
|
+
"weight": 1
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"topic": "b",
|
|
21
|
+
"label": "B",
|
|
22
|
+
"weight": 1
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"minTopics": {
|
|
27
|
+
"value": 1
|
|
28
|
+
},
|
|
29
|
+
"decimals": {
|
|
30
|
+
"value": 2
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
inputs: 1,
|
|
34
|
+
outputs: 1,
|
|
35
|
+
icon: 'font-awesome/fa-plus',
|
|
36
|
+
label: function() { return this.name || 'adder'; },
|
|
37
|
+
labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<script type="text/html" data-template-name="adder">
|
|
42
|
+
<div class="form-row">
|
|
43
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
44
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
45
|
+
</div>
|
|
46
|
+
<div class="form-row">
|
|
47
|
+
<label for="node-input-mode"><i class="fa fa-calculator"></i> Mode</label>
|
|
48
|
+
<select id="node-input-mode" style="width:70%">
|
|
49
|
+
<option value="sum">Sum (Σ w×v)</option>
|
|
50
|
+
<option value="average">Weighted Average</option>
|
|
51
|
+
</select>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="form-row">
|
|
54
|
+
<label><i class="fa fa-list"></i> Topics</label>
|
|
55
|
+
<ol id="node-input-topics-list"></ol>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="form-row">
|
|
58
|
+
<label for="node-input-minTopics"><i class="fa fa-hourglass"></i> Min Topics</label>
|
|
59
|
+
<input type="number" id="node-input-minTopics" min="1" style="width:70%">
|
|
60
|
+
</div>
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-input-decimals"><i class="fa fa-sort-numeric-asc"></i> Decimal Places</label>
|
|
63
|
+
<input type="number" id="node-input-decimals" min="0" max="10" style="width:70%">
|
|
64
|
+
</div>
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<script type="text/html" data-help-name="adder">
|
|
68
|
+
<p>Sums multiple numeric inputs with optional weighting. Can also compute weighted average.</p>
|
|
69
|
+
<p>Inputs are identified by <code>msg.topic</code>. Configure topics and weights in the editor.</p>
|
|
70
|
+
<h3>Output</h3>
|
|
71
|
+
<dl class="message-properties">
|
|
72
|
+
<dt>payload <span class="property-type">number</span></dt><dd>Sum or weighted average</dd>
|
|
73
|
+
<dt>adder <span class="property-type">object</span></dt><dd>{ mode, result, count, inputs, weights, decimals }</dd>
|
|
74
|
+
</dl>
|
|
75
|
+
</script>
|
package/adder.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function AdderNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
var node = this;
|
|
5
|
+
|
|
6
|
+
node.mode = config.mode || 'sum';
|
|
7
|
+
node.minTopics = parseInt(config.minTopics, 10) || 1;
|
|
8
|
+
|
|
9
|
+
if (Array.isArray(config.topics)) {
|
|
10
|
+
node.topics = config.topics;
|
|
11
|
+
} else if (typeof config.topics === 'string') {
|
|
12
|
+
try {
|
|
13
|
+
node.topics = JSON.parse(config.topics);
|
|
14
|
+
} catch(e) {
|
|
15
|
+
node.topics = config.topics.split(',').map(function(t) {
|
|
16
|
+
var parts = t.trim().split(':');
|
|
17
|
+
return { topic:parts[0].trim(), label:parts[0].trim(), weight:parseFloat(parts[1])||1 };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
node.topics = [{ topic:'a', label:'A', weight:1 }, { topic:'b', label:'B', weight:1 }];
|
|
22
|
+
}
|
|
23
|
+
node.decimals = parseInt(config.decimals, 10);
|
|
24
|
+
if (isNaN(node.decimals) || node.decimals < 0) node.decimals = 2;
|
|
25
|
+
|
|
26
|
+
// Build lookup map
|
|
27
|
+
node.topicMap = {};
|
|
28
|
+
node.topics.forEach(function(t) { node.topicMap[t.topic] = t; });
|
|
29
|
+
|
|
30
|
+
node.state = {};
|
|
31
|
+
updateStatus(node);
|
|
32
|
+
|
|
33
|
+
node.on('input', function(msg) {
|
|
34
|
+
if (msg.reset === true) {
|
|
35
|
+
node.state = {};
|
|
36
|
+
updateStatus(node);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var topic = (msg.topic !== undefined) ? String(msg.topic) : '';
|
|
41
|
+
if (!(topic in node.topicMap)) {
|
|
42
|
+
node.warn('Unknown topic: ' + topic);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
var val = parseFloat(msg.payload);
|
|
46
|
+
if (isNaN(val)) { node.warn('Input is not a number: ' + msg.payload); return; }
|
|
47
|
+
|
|
48
|
+
node.state[topic] = val;
|
|
49
|
+
|
|
50
|
+
var knownCount = Object.keys(node.state).length;
|
|
51
|
+
if (knownCount < node.minTopics) {
|
|
52
|
+
node.status({ fill:'grey', shape:'ring', text:'Waiting (' + knownCount + '/' + node.minTopics + ' topics)' });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var weightedSum = 0, totalWeight = 0;
|
|
57
|
+
var inputs = {}, weights = {};
|
|
58
|
+
node.topics.forEach(function(t) {
|
|
59
|
+
if (t.topic in node.state) {
|
|
60
|
+
var w = parseFloat(t.weight) || 1;
|
|
61
|
+
weightedSum += node.state[t.topic] * w;
|
|
62
|
+
totalWeight += w;
|
|
63
|
+
inputs[t.topic] = node.state[t.topic];
|
|
64
|
+
weights[t.topic] = w;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
var result = node.mode === 'average' ? weightedSum / totalWeight : weightedSum;
|
|
69
|
+
result = parseFloat(result.toFixed(node.decimals));
|
|
70
|
+
|
|
71
|
+
msg.payload = result;
|
|
72
|
+
msg.adder = { mode:node.mode, result:result, count:knownCount, inputs:inputs, weights:weights, decimals:node.decimals };
|
|
73
|
+
|
|
74
|
+
node.status({ fill:'green', shape:'dot', text:node.mode + ': ' + result + ' (' + knownCount + ' inputs)' });
|
|
75
|
+
node.send(msg);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function updateStatus(n) {
|
|
79
|
+
var k = Object.keys(n.state).length;
|
|
80
|
+
if (k === 0) { n.status({ fill:'grey', shape:'ring', text:'Waiting for inputs...' }); }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
RED.nodes.registerType('adder', AdderNode);
|
|
85
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adder": {
|
|
3
|
+
"label": "Addierer",
|
|
4
|
+
"topics": "Topics / Gewichte",
|
|
5
|
+
"mode": "Modus",
|
|
6
|
+
"mode_opt": {
|
|
7
|
+
"sum": "Summe",
|
|
8
|
+
"avg": "Gewichteter Durchschnitt"
|
|
9
|
+
},
|
|
10
|
+
"minTopics": "Min. benötigte Topics",
|
|
11
|
+
"tip": "Sammelt Werte von benannten Topics und gibt Summe oder gewichteten Durchschnitt aus.",
|
|
12
|
+
"input": {
|
|
13
|
+
"payload": "Numerischer Wert (Topic bestimmt den Slot)"
|
|
14
|
+
},
|
|
15
|
+
"output": {
|
|
16
|
+
"payload": "Summe oder gewichteter Durchschnitt aller Topic-Werte"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adder": {
|
|
3
|
+
"label": "Adder",
|
|
4
|
+
"topics": "Topics / weights",
|
|
5
|
+
"mode": "Mode",
|
|
6
|
+
"mode_opt": {
|
|
7
|
+
"sum": "Sum",
|
|
8
|
+
"avg": "Weighted average"
|
|
9
|
+
},
|
|
10
|
+
"minTopics": "Min. required topics",
|
|
11
|
+
"tip": "Collects values from named topics, then outputs sum or weighted average.",
|
|
12
|
+
"input": {
|
|
13
|
+
"payload": "Numeric value (topic determines which slot is filled)"
|
|
14
|
+
},
|
|
15
|
+
"output": {
|
|
16
|
+
"payload": "Sum or weighted average of all received topic values"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adder": {
|
|
3
|
+
"label": "Sumador",
|
|
4
|
+
"topics": "Topics / pesos",
|
|
5
|
+
"mode": "Modo",
|
|
6
|
+
"mode_opt": {
|
|
7
|
+
"sum": "Suma",
|
|
8
|
+
"avg": "Promedio ponderado"
|
|
9
|
+
},
|
|
10
|
+
"minTopics": "Mín. topics requeridos",
|
|
11
|
+
"tip": "Recoge valores de topics nombrados, luego genera suma o promedio ponderado.",
|
|
12
|
+
"input": {
|
|
13
|
+
"payload": "Valor numérico (el topic determina el slot)"
|
|
14
|
+
},
|
|
15
|
+
"output": {
|
|
16
|
+
"payload": "Suma o promedio ponderado de todos los valores de topic"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-adder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Multi-input adder with optional weighting - sum or weighted average of inputs",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/Wobi848/node-red-contrib-rpo-suite.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/Wobi848/node-red-contrib-rpo-suite/issues"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"adder.js",
|
|
14
|
+
"adder.html",
|
|
15
|
+
"locales",
|
|
16
|
+
"examples"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"node-red",
|
|
20
|
+
"adder",
|
|
21
|
+
"sum",
|
|
22
|
+
"average",
|
|
23
|
+
"weighted"
|
|
24
|
+
],
|
|
25
|
+
"author": "sr.rpo",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"node-red": {
|
|
28
|
+
"version": "1.0.0",
|
|
29
|
+
"nodes": {
|
|
30
|
+
"adder": "adder.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "mocha \"test/**/*_spec.js\" --exit"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"mocha": "^10.2.0",
|
|
41
|
+
"node-red": "^3.1.0",
|
|
42
|
+
"node-red-node-test-helper": "^0.3.6"
|
|
43
|
+
}
|
|
44
|
+
}
|