node-red-contrib-rate-limiter 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,32 @@
1
+ # node-red-contrib-rpo-rate-limiter
2
+
3
+ A Node-RED node: **Rate Limiter**
4
+
5
+ Limits the maximum rate of change of a numeric value. Useful for slowly ramping setpoints or protecting actuators.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install node-red-contrib-rpo-rate-limiter
11
+ ```
12
+
13
+ Or via the Node-RED Palette Manager.
14
+
15
+ ## Inputs
16
+
17
+ - **payload** (number): Input value
18
+
19
+ ## Outputs
20
+
21
+ - **Output 1**: Rate-limited numeric value
22
+
23
+ ## Configuration
24
+
25
+ | Parameter | Description |
26
+ |-----------|-------------|
27
+ | Max rate | Maximum allowed change per time unit |
28
+ | Unit | /s, /min, or /h |
29
+
30
+ ## License
31
+
32
+ MIT
@@ -0,0 +1,19 @@
1
+ {
2
+ "rate-limiter": {
3
+ "label": "Ratenbegren­zung",
4
+ "maxRate": "Maximale Rate",
5
+ "unit": "Einheit",
6
+ "unit_opt": {
7
+ "per_s": "/s",
8
+ "per_min": "/min",
9
+ "per_h": "/h"
10
+ },
11
+ "tip": "Begrenzt die maximale Änderungsrate pro Zeiteinheit.",
12
+ "input": {
13
+ "payload": "Numerischer Eingabewert"
14
+ },
15
+ "output": {
16
+ "payload": "Ratenbegrenzter numerischer Wert"
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "rate-limiter": {
3
+ "label": "Rate Limiter",
4
+ "maxRate": "Max rate",
5
+ "unit": "Unit",
6
+ "unit_opt": {
7
+ "per_s": "/s",
8
+ "per_min": "/min",
9
+ "per_h": "/h"
10
+ },
11
+ "tip": "Limits the maximum rate of change per time unit.",
12
+ "input": {
13
+ "payload": "Numeric value to rate-limit"
14
+ },
15
+ "output": {
16
+ "payload": "Rate-limited numeric value"
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "rate-limiter": {
3
+ "label": "Limitador de tasa",
4
+ "maxRate": "Tasa máxima",
5
+ "unit": "Unidad",
6
+ "unit_opt": {
7
+ "per_s": "/s",
8
+ "per_min": "/min",
9
+ "per_h": "/h"
10
+ },
11
+ "tip": "Limita la tasa máxima de cambio por unidad de tiempo.",
12
+ "input": {
13
+ "payload": "Valor numérico de entrada"
14
+ },
15
+ "output": {
16
+ "payload": "Valor numérico con tasa limitada"
17
+ }
18
+ }
19
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "node-red-contrib-rate-limiter",
3
+ "version": "1.0.0",
4
+ "description": "Rate limiter - limits rate of change of numeric values per second/min/hour",
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
+ "rate-limiter.js",
14
+ "rate-limiter.html",
15
+ "locales",
16
+ "examples"
17
+ ],
18
+ "keywords": [
19
+ "node-red",
20
+ "rate",
21
+ "limiter",
22
+ "ramp",
23
+ "dynamics"
24
+ ],
25
+ "author": "sr.rpo",
26
+ "license": "MIT",
27
+ "node-red": {
28
+ "version": "1.0.0",
29
+ "nodes": {
30
+ "rate-limiter": "rate-limiter.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
+ }
@@ -0,0 +1,54 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('rate-limiter', {
3
+ category: 'rpo',
4
+ color: '#D4600A',
5
+ defaults: {
6
+ "name": {
7
+ "value": ""
8
+ },
9
+ "maxRate": {
10
+ "value": 1
11
+ },
12
+ "maxRateUnit": {
13
+ "value": "per_s"
14
+ },
15
+ "decimals": {
16
+ "value": 2
17
+ }
18
+ },
19
+ inputs: 1,
20
+ outputs: 1,
21
+ icon: 'font-awesome/fa-tachometer',
22
+ label: function() { return this.name || 'rate-limiter'; },
23
+ labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
24
+ });
25
+ </script>
26
+
27
+ <script type="text/html" data-template-name="rate-limiter">
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-maxRate"><i class="fa fa-tachometer"></i> Max Rate</label>
34
+ <input type="number" id="node-input-maxRate" min="0" step="0.1" style="width:50%">
35
+ <select id="node-input-maxRateUnit" style="width:18%; margin-left:4px">
36
+ <option value="per_s">per s</option>
37
+ <option value="per_min">per min</option>
38
+ <option value="per_h">per h</option>
39
+ </select>
40
+ </div>
41
+ <div class="form-row">
42
+ <label for="node-input-decimals"><i class="fa fa-sort-numeric-asc"></i> Decimal Places</label>
43
+ <input type="number" id="node-input-decimals" min="0" max="4" style="width:70%">
44
+ </div>
45
+ </script>
46
+
47
+ <script type="text/html" data-help-name="rate-limiter">
48
+ <p>Limits the rate of change of a numeric value. Output cannot change faster than the configured maximum rate.</p>
49
+ <h3>Output</h3>
50
+ <dl class="message-properties">
51
+ <dt>payload <span class="property-type">number</span></dt><dd>Rate-limited value</dd>
52
+ <dt>rateLimiter <span class="property-type">object</span></dt><dd>{ input, output, lastOutput, maxRate, dt, limited, decimals }</dd>
53
+ </dl>
54
+ </script>
@@ -0,0 +1,76 @@
1
+ module.exports = function(RED) {
2
+ function RateLimiterNode(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+
6
+ var rRaw = parseFloat(config.maxRate);
7
+ node.maxRate = isNaN(rRaw) ? 1.0 : rRaw;
8
+ var rUnit = config.maxRateUnit || 'per_s';
9
+ node.maxRatePerS = rUnit === 'per_min' ? node.maxRate / 60 : rUnit === 'per_h' ? node.maxRate / 3600 : node.maxRate;
10
+
11
+ node.decimals = parseInt(config.decimals, 10);
12
+ if (isNaN(node.decimals) || node.decimals < 0) node.decimals = 2;
13
+
14
+ node.lastOutput = undefined;
15
+ node.lastTime = undefined;
16
+
17
+ node.status({ fill:'grey', shape:'ring', text:'Waiting...' });
18
+
19
+ node.on('input', function(msg) {
20
+ if (msg.reset === true) {
21
+ node.lastOutput = undefined;
22
+ node.lastTime = undefined;
23
+ node.status({ fill:'grey', shape:'ring', text:'Waiting...' });
24
+ return;
25
+ }
26
+
27
+ var x = parseFloat(msg.payload);
28
+ if (isNaN(x)) { node.warn('Input is not a number: ' + msg.payload); return; }
29
+
30
+ var maxRatePerS = (msg.maxRate !== undefined) ? parseFloat(msg.maxRate) : node.maxRatePerS;
31
+
32
+ var now = Date.now();
33
+ var output, limited;
34
+
35
+ if (node.lastOutput === undefined) {
36
+ output = x;
37
+ limited = false;
38
+ } else {
39
+ var dt = (now - node.lastTime) / 1000;
40
+ var maxDelta = maxRatePerS * dt;
41
+ var delta = x - node.lastOutput;
42
+ if (Math.abs(delta) > maxDelta) {
43
+ output = node.lastOutput + (delta > 0 ? 1 : -1) * maxDelta;
44
+ limited = true;
45
+ } else {
46
+ output = x;
47
+ limited = false;
48
+ }
49
+ }
50
+
51
+ output = parseFloat(output.toFixed(node.decimals));
52
+ node.lastOutput = output;
53
+ node.lastTime = now;
54
+
55
+ msg.payload = output;
56
+ msg.rateLimiter = {
57
+ input: x,
58
+ output: output,
59
+ lastOutput: node.lastOutput,
60
+ maxRate: node.maxRate,
61
+ dt: node.lastTime ? Math.round((now - now) * 100) / 100 : 0,
62
+ limited: limited,
63
+ decimals: node.decimals
64
+ };
65
+
66
+ if (limited) {
67
+ node.status({ fill:'yellow', shape:'dot', text:output + ' → ' + x + ' (limited)' });
68
+ } else {
69
+ node.status({ fill:'green', shape:'dot', text:output + ' (at target)' });
70
+ }
71
+ node.send(msg);
72
+ });
73
+ }
74
+
75
+ RED.nodes.registerType('rate-limiter', RateLimiterNode);
76
+ };