node-red-contrib-edge-filter 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,34 @@
1
+ # node-red-contrib-rpo-edge-filter
2
+
3
+ A Node-RED node: **Edge Filter**
4
+
5
+ Passes the first message in a time window and blocks all subsequent messages until the window expires.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install node-red-contrib-rpo-edge-filter
11
+ ```
12
+
13
+ Or via the Node-RED Palette Manager.
14
+
15
+ ## Inputs
16
+
17
+ - **payload**: Any value
18
+ - **msg.reset=true**: Reset the filter immediately
19
+
20
+ ## Outputs
21
+
22
+ - **Output 1**: First value within the time window
23
+
24
+ ## Configuration
25
+
26
+ | Parameter | Description |
27
+ |-----------|-------------|
28
+ | Window size | Duration of the blocking window |
29
+ | Window unit | ms, s, or min |
30
+ | Filter mode | Time window or on-change |
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,58 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('edge-filter', {
3
+ category: 'rpo',
4
+ color: '#D4600A',
5
+ defaults: {
6
+ "name": {
7
+ "value": ""
8
+ },
9
+ "window": {
10
+ "value": 5
11
+ },
12
+ "windowUnit": {
13
+ "value": "s"
14
+ },
15
+ "mode": {
16
+ "value": "time"
17
+ }
18
+ },
19
+ inputs: 1,
20
+ outputs: 1,
21
+ icon: 'font-awesome/fa-hand-stop-o',
22
+ label: function() { return this.name || 'edge-filter'; },
23
+ labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
24
+ });
25
+ </script>
26
+
27
+ <script type="text/html" data-template-name="edge-filter">
28
+ <div class="form-row">
29
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
30
+ <input type="text" id="node-input-name" placeholder="Name">
31
+ </div>
32
+ <div class="form-row">
33
+ <label for="node-input-window"><i class="fa fa-clock-o"></i> Window</label>
34
+ <input type="number" id="node-input-window" min="1" style="width:50%">
35
+ <select id="node-input-windowUnit" style="width:18%; margin-left:4px">
36
+ <option value="ms">ms</option>
37
+ <option value="s">s</option>
38
+ <option value="min">min</option>
39
+ </select>
40
+ </div>
41
+ <div class="form-row">
42
+ <label for="node-input-mode"><i class="fa fa-sliders"></i> Mode</label>
43
+ <select id="node-input-mode" style="width:70%">
44
+ <option value="time">Time window</option>
45
+ <option value="changed">Changed value only</option>
46
+ </select>
47
+ </div>
48
+ </script>
49
+
50
+ <script type="text/html" data-help-name="edge-filter">
51
+ <p>Forwards only the first event within a configurable time window. Subsequent events are blocked (re-trigger protection).</p>
52
+ <h3>Modes</h3>
53
+ <ul>
54
+ <li><b>Time</b>: first message forwarded, rest blocked for window duration</li>
55
+ <li><b>Changed</b>: additionally blocks if value hasn't changed</li>
56
+ </ul>
57
+ <p>Send <code>msg.reset = true</code> to immediately reopen the window.</p>
58
+ </script>
package/edge-filter.js ADDED
@@ -0,0 +1,58 @@
1
+ module.exports = function(RED) {
2
+ function EdgeFilterNode(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+
6
+ var wRaw = parseFloat(config.window) || 5;
7
+ var wUnit = config.windowUnit || 's';
8
+ node.window = wUnit === 'min' ? wRaw * 60000 : wUnit === 'ms' ? wRaw : wRaw * 1000;
9
+ node.mode = config.mode || 'time';
10
+
11
+ node.blocked = false;
12
+ node.timer = null;
13
+ node.lastValue = undefined;
14
+
15
+ node.status({ fill:'green', shape:'dot', text:'Ready (window: ' + wRaw + wUnit + ')' });
16
+
17
+ node.on('input', function(msg) {
18
+ if (msg.reset === true) {
19
+ node.blocked = false;
20
+ if (node.timer) { clearTimeout(node.timer); node.timer = null; }
21
+ node.lastValue = undefined;
22
+ node.status({ fill:'green', shape:'dot', text:'Ready (reset)' });
23
+ return;
24
+ }
25
+
26
+ if (node.mode === 'changed') {
27
+ var cur = msg.payload;
28
+ var changed = (node.lastValue === undefined) ||
29
+ (typeof cur === 'object' ? JSON.stringify(cur) !== JSON.stringify(node.lastValue) : cur !== node.lastValue);
30
+ if (!changed) {
31
+ node.status({ fill:'grey', shape:'dot', text:'Blocked (no change)' });
32
+ return;
33
+ }
34
+ node.lastValue = cur;
35
+ }
36
+
37
+ if (node.blocked) {
38
+ node.status({ fill:'red', shape:'dot', text:'Blocked (window active)' });
39
+ return;
40
+ }
41
+
42
+ node.blocked = true;
43
+ node.status({ fill:'green', shape:'dot', text:'Forwarded (window: ' + wRaw + wUnit + ')' });
44
+ node.send(msg);
45
+
46
+ node.timer = setTimeout(function() {
47
+ node.blocked = false;
48
+ node.status({ fill:'green', shape:'dot', text:'Ready (window: ' + wRaw + wUnit + ')' });
49
+ }, node.window);
50
+ });
51
+
52
+ node.on('close', function() {
53
+ if (node.timer) clearTimeout(node.timer);
54
+ });
55
+ }
56
+
57
+ RED.nodes.registerType('edge-filter', EdgeFilterNode);
58
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "edge-filter": {
3
+ "label": "Flankenfilter",
4
+ "window": "Fenstergröße",
5
+ "windowUnit": "Fenstereinheit",
6
+ "windowUnit_opt": {
7
+ "ms": "ms",
8
+ "s": "s",
9
+ "min": "min"
10
+ },
11
+ "mode": "Filtermodus",
12
+ "mode_opt": {
13
+ "time": "Zeitfenster",
14
+ "changed": "Bei Änderung"
15
+ },
16
+ "tip": "Blockiert wiederholte Nachrichten innerhalb des Fensters.",
17
+ "input": {
18
+ "payload": "Beliebiger Wert"
19
+ },
20
+ "output": {
21
+ "payload": "Weitergeleiteter Wert (erster im Fenster)"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "edge-filter": {
3
+ "label": "Edge Filter",
4
+ "window": "Window size",
5
+ "windowUnit": "Window unit",
6
+ "windowUnit_opt": {
7
+ "ms": "ms",
8
+ "s": "s",
9
+ "min": "min"
10
+ },
11
+ "mode": "Filter mode",
12
+ "mode_opt": {
13
+ "time": "Time window",
14
+ "changed": "On change"
15
+ },
16
+ "tip": "Blocks repeated messages within the window. Changed mode also blocks identical values.",
17
+ "input": {
18
+ "payload": "Any value"
19
+ },
20
+ "output": {
21
+ "payload": "Forwarded value (first within window)"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "edge-filter": {
3
+ "label": "Filtro de flanco",
4
+ "window": "Tamaño de ventana",
5
+ "windowUnit": "Unidad de ventana",
6
+ "windowUnit_opt": {
7
+ "ms": "ms",
8
+ "s": "s",
9
+ "min": "min"
10
+ },
11
+ "mode": "Modo de filtro",
12
+ "mode_opt": {
13
+ "time": "Ventana de tiempo",
14
+ "changed": "Al cambiar"
15
+ },
16
+ "tip": "Bloquea mensajes repetidos dentro de la ventana.",
17
+ "input": {
18
+ "payload": "Cualquier valor"
19
+ },
20
+ "output": {
21
+ "payload": "Valor reenviado (primero en ventana)"
22
+ }
23
+ }
24
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "node-red-contrib-edge-filter",
3
+ "version": "1.0.0",
4
+ "description": "Edge filter - forwards only first event in a time window (re-trigger protection)",
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
+ "edge-filter.js",
14
+ "edge-filter.html",
15
+ "locales",
16
+ "examples"
17
+ ],
18
+ "keywords": [
19
+ "node-red",
20
+ "edge",
21
+ "filter",
22
+ "window",
23
+ "retrigger"
24
+ ],
25
+ "author": "sr.rpo",
26
+ "license": "MIT",
27
+ "node-red": {
28
+ "version": "1.0.0",
29
+ "nodes": {
30
+ "edge-filter": "edge-filter.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
+ }