node-red-contrib-limit-counter 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 +35 -0
- package/limit-counter.html +65 -0
- package/limit-counter.js +88 -0
- package/locales/de/limit-counter.json +18 -0
- package/locales/en/limit-counter.json +18 -0
- package/locales/es/limit-counter.json +18 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# node-red-contrib-rpo-limit-counter
|
|
2
|
+
|
|
3
|
+
A Node-RED node: **Limit Counter**
|
|
4
|
+
|
|
5
|
+
Counts rising-edge crossings of a threshold (with hysteresis) and triggers an alarm when a configurable count is reached.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install node-red-contrib-rpo-limit-counter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or via the Node-RED Palette Manager.
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
- **payload** (number): Value to compare against threshold
|
|
18
|
+
- **msg.reset=true**: Reset counter
|
|
19
|
+
|
|
20
|
+
## Outputs
|
|
21
|
+
|
|
22
|
+
- **Output 1**: Current crossing count
|
|
23
|
+
- **Output 2**: `true` when alarm count reached, `false` otherwise
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
| Parameter | Description |
|
|
28
|
+
|-----------|-------------|
|
|
29
|
+
| Threshold | Crossing trigger level |
|
|
30
|
+
| Hysteresis | Dead-band to prevent chatter |
|
|
31
|
+
| Alarm count | Count at which alarm fires |
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('limit-counter', {
|
|
3
|
+
category: 'rpo',
|
|
4
|
+
color: '#D4600A',
|
|
5
|
+
defaults: {
|
|
6
|
+
"name": {
|
|
7
|
+
"value": ""
|
|
8
|
+
},
|
|
9
|
+
"threshold": {
|
|
10
|
+
"value": 0
|
|
11
|
+
},
|
|
12
|
+
"limit": {
|
|
13
|
+
"value": 3
|
|
14
|
+
},
|
|
15
|
+
"hysteresis": {
|
|
16
|
+
"value": 0
|
|
17
|
+
},
|
|
18
|
+
"persistent": {
|
|
19
|
+
"value": false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
inputs: 1,
|
|
23
|
+
outputs: 2,
|
|
24
|
+
icon: 'font-awesome/fa-exclamation-triangle',
|
|
25
|
+
outputLabels: ["Count + info","Alarm boolean"],
|
|
26
|
+
label: function() { return this.name || 'limit-counter'; },
|
|
27
|
+
labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script type="text/html" data-template-name="limit-counter">
|
|
32
|
+
<div class="form-row">
|
|
33
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
34
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
35
|
+
</div>
|
|
36
|
+
<div class="form-row">
|
|
37
|
+
<label for="node-input-threshold"><i class="fa fa-level-up"></i> Threshold</label>
|
|
38
|
+
<input type="number" id="node-input-threshold" style="width:70%">
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-row">
|
|
41
|
+
<label for="node-input-limit"><i class="fa fa-exclamation-triangle"></i> Alarm Limit</label>
|
|
42
|
+
<input type="number" id="node-input-limit" min="1" style="width:70%">
|
|
43
|
+
</div>
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-input-hysteresis"><i class="fa fa-arrows-h"></i> Hysteresis</label>
|
|
46
|
+
<input type="number" id="node-input-hysteresis" min="0" step="0.1" style="width:70%">
|
|
47
|
+
</div>
|
|
48
|
+
<div class="form-row">
|
|
49
|
+
<label for="node-input-persistent"><i class="fa fa-hdd-o"></i> Persistent</label>
|
|
50
|
+
<input type="checkbox" id="node-input-persistent" style="width:auto;margin-top:4px">
|
|
51
|
+
</div>
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<script type="text/html" data-help-name="limit-counter">
|
|
55
|
+
<p>Counts threshold crossings and outputs an alarm when the count reaches the configured limit.</p>
|
|
56
|
+
<h3>Output 1</h3>
|
|
57
|
+
<dl class="message-properties">
|
|
58
|
+
<dt>payload <span class="property-type">number</span></dt><dd>Current crossing count</dd>
|
|
59
|
+
<dt>limitCounter <span class="property-type">object</span></dt><dd>{ count, limit, threshold, hysteresis, alarm, lastValue }</dd>
|
|
60
|
+
</dl>
|
|
61
|
+
<h3>Output 2</h3>
|
|
62
|
+
<dl class="message-properties">
|
|
63
|
+
<dt>payload <span class="property-type">boolean</span></dt><dd>true = alarm (count >= limit)</dd>
|
|
64
|
+
</dl>
|
|
65
|
+
</script>
|
package/limit-counter.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function LimitCounterNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
var node = this;
|
|
5
|
+
|
|
6
|
+
node.threshold = parseFloat(config.threshold) || 0;
|
|
7
|
+
node.limit = parseInt(config.limit, 10) || 3;
|
|
8
|
+
var _hy = parseFloat(config.hysteresis);
|
|
9
|
+
node.hysteresis = isNaN(_hy) ? 0 : _hy;
|
|
10
|
+
node.persistent = config.persistent === true;
|
|
11
|
+
|
|
12
|
+
if (node.persistent) {
|
|
13
|
+
node.count = node.context().get('count') || 0;
|
|
14
|
+
node.aboveThreshold = node.context().get('aboveThreshold') || false;
|
|
15
|
+
} else {
|
|
16
|
+
node.count = 0;
|
|
17
|
+
node.aboveThreshold = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
updateStatus(node);
|
|
21
|
+
|
|
22
|
+
node.on('input', function(msg) {
|
|
23
|
+
if (msg.reset === true) {
|
|
24
|
+
node.count = 0;
|
|
25
|
+
node.aboveThreshold = false;
|
|
26
|
+
if (node.persistent) {
|
|
27
|
+
node.context().set('count', 0);
|
|
28
|
+
node.context().set('aboveThreshold', false);
|
|
29
|
+
}
|
|
30
|
+
updateStatus(node);
|
|
31
|
+
sendOutputs(node, msg);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var val = parseFloat(msg.payload);
|
|
36
|
+
if (isNaN(val)) { node.warn('Input is not a number: ' + msg.payload); return; }
|
|
37
|
+
|
|
38
|
+
var wasAbove = node.aboveThreshold;
|
|
39
|
+
var isAbove = val >= node.threshold;
|
|
40
|
+
// Hysteresis: to go from above to below, must drop below (threshold - hysteresis)
|
|
41
|
+
if (wasAbove && val >= (node.threshold - node.hysteresis)) {
|
|
42
|
+
isAbove = true;
|
|
43
|
+
}
|
|
44
|
+
node.aboveThreshold = isAbove;
|
|
45
|
+
|
|
46
|
+
// Rising edge detection
|
|
47
|
+
if (!wasAbove && isAbove) {
|
|
48
|
+
node.count++;
|
|
49
|
+
if (node.persistent) node.context().set('count', node.count);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (node.persistent) node.context().set('aboveThreshold', node.aboveThreshold);
|
|
53
|
+
|
|
54
|
+
updateStatus(node);
|
|
55
|
+
sendOutputs(node, msg);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function sendOutputs(n, msg) {
|
|
59
|
+
var alarm = n.count >= n.limit;
|
|
60
|
+
var out1 = Object.assign({}, msg, {
|
|
61
|
+
payload: n.count,
|
|
62
|
+
limitCounter: {
|
|
63
|
+
count: n.count,
|
|
64
|
+
limit: n.limit,
|
|
65
|
+
threshold: n.threshold,
|
|
66
|
+
hysteresis: n.hysteresis,
|
|
67
|
+
alarm: alarm,
|
|
68
|
+
lastValue: parseFloat(msg.payload) || 0
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
var out2 = { payload: alarm };
|
|
72
|
+
n.send([out1, out2]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function updateStatus(n) {
|
|
76
|
+
var alarm = n.count >= n.limit;
|
|
77
|
+
if (alarm) {
|
|
78
|
+
n.status({ fill:'red', shape:'dot', text:'ALARM Count: '+n.count+'/'+n.limit });
|
|
79
|
+
} else if (n.count > 0) {
|
|
80
|
+
n.status({ fill:'yellow', shape:'dot', text:'Count: '+n.count+'/'+n.limit+' (threshold: '+n.threshold+')' });
|
|
81
|
+
} else {
|
|
82
|
+
n.status({ fill:'green', shape:'dot', text:'Count: 0/'+n.limit+' (threshold: '+n.threshold+')' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
RED.nodes.registerType('limit-counter', LimitCounterNode);
|
|
88
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"limit-counter": {
|
|
3
|
+
"label": "Grenzzähler",
|
|
4
|
+
"threshold": "Schwellenwert",
|
|
5
|
+
"hysteresis": "Hysterese",
|
|
6
|
+
"alarmCount": "Alarmzähler",
|
|
7
|
+
"tip": "Zählt steigende Flanken-Schwellenwertüberschreitungen und löst bei Alarmzähler aus.",
|
|
8
|
+
"input": {
|
|
9
|
+
"payload": "Numerischer Wert zum Vergleich mit dem Schwellenwert"
|
|
10
|
+
},
|
|
11
|
+
"output1": {
|
|
12
|
+
"payload": "Aktueller Überschreitungszähler"
|
|
13
|
+
},
|
|
14
|
+
"output2": {
|
|
15
|
+
"payload": "true wenn Alarmzähler erreicht, sonst false"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"limit-counter": {
|
|
3
|
+
"label": "Limit Counter",
|
|
4
|
+
"threshold": "Threshold",
|
|
5
|
+
"hysteresis": "Hysteresis",
|
|
6
|
+
"alarmCount": "Alarm count",
|
|
7
|
+
"tip": "Counts rising-edge threshold crossings and triggers alarm at alarm count.",
|
|
8
|
+
"input": {
|
|
9
|
+
"payload": "Numeric value to compare against threshold"
|
|
10
|
+
},
|
|
11
|
+
"output1": {
|
|
12
|
+
"payload": "Current crossing count"
|
|
13
|
+
},
|
|
14
|
+
"output2": {
|
|
15
|
+
"payload": "true when alarm count reached, false otherwise"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"limit-counter": {
|
|
3
|
+
"label": "Contador de límite",
|
|
4
|
+
"threshold": "Umbral",
|
|
5
|
+
"hysteresis": "Histéresis",
|
|
6
|
+
"alarmCount": "Conteo de alarma",
|
|
7
|
+
"tip": "Cuenta cruces de umbral por flanco ascendente y activa alarma al conteo de alarma.",
|
|
8
|
+
"input": {
|
|
9
|
+
"payload": "Valor numérico a comparar con el umbral"
|
|
10
|
+
},
|
|
11
|
+
"output1": {
|
|
12
|
+
"payload": "Conteo de cruces actual"
|
|
13
|
+
},
|
|
14
|
+
"output2": {
|
|
15
|
+
"payload": "true cuando se alcanza el conteo de alarma, false si no"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-limit-counter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Limit counter - counts threshold crossings and outputs alarm at configured limit",
|
|
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
|
+
"limit-counter.js",
|
|
14
|
+
"limit-counter.html",
|
|
15
|
+
"locales",
|
|
16
|
+
"examples"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"node-red",
|
|
20
|
+
"counter",
|
|
21
|
+
"limit",
|
|
22
|
+
"alarm",
|
|
23
|
+
"threshold"
|
|
24
|
+
],
|
|
25
|
+
"author": "sr.rpo",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"node-red": {
|
|
28
|
+
"version": "1.0.0",
|
|
29
|
+
"nodes": {
|
|
30
|
+
"limit-counter": "limit-counter.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
|
+
}
|