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 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>
@@ -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
+ }