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 +34 -0
- package/edge-filter.html +58 -0
- package/edge-filter.js +58 -0
- package/locales/de/edge-filter.json +24 -0
- package/locales/en/edge-filter.json +24 -0
- package/locales/es/edge-filter.json +24 -0
- package/package.json +44 -0
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
|
package/edge-filter.html
ADDED
|
@@ -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
|
+
}
|