node-red-contrib-pt1 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,38 @@
1
+ # node-red-contrib-pt1
2
+
3
+ A Node-RED PT1 first-order low-pass filter for smoothing noisy measurement values.
4
+
5
+ Formula: y(t) = y(t-1) + (dt / (T1 + dt)) * (x(t) - y(t-1))
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install node-red-contrib-pt1
11
+ ```
12
+
13
+ ## Inputs
14
+
15
+ - **payload** `number`: Raw measurement value to filter.
16
+ - **T1** _(optional)_ `number`: Override T1 in seconds for this message only.
17
+ - **reset** _(optional)_ `boolean`: Reset filter state; next input passes through directly.
18
+
19
+ ## Outputs
20
+
21
+ - **payload** `number`: Filtered (smoothed) value.
22
+ - **pt1** `object`: { input, output, T1, dt, decimals, initialized }
23
+
24
+ ## Configuration
25
+
26
+ - **Time Constant T1**: Smoothing constant with unit s/min/h. Larger = more smoothing, slower response.
27
+ - **Decimal Places**: Output precision 0-4 (default: 2).
28
+
29
+ ## Tips
30
+
31
+ - First message always passes through unfiltered (initialization).
32
+ - T1 = 0 disables filtering (passthrough mode).
33
+ - Uses actual elapsed time between messages - no fixed interval required.
34
+ - Tip: use the 24h moving average as input for best predictive results.
35
+
36
+ ## License
37
+
38
+ MIT (c) sr.rpo
@@ -0,0 +1,8 @@
1
+ {
2
+ "pt1": {
3
+ "label": {
4
+ "T1": "Zeitkonstante T1",
5
+ "decimals": "Dezimalstellen"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "pt1": {
3
+ "label": {
4
+ "T1": "Time Constant T1",
5
+ "decimals": "Decimal Places"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "pt1": {
3
+ "label": {
4
+ "T1": "Constante de tiempo T1",
5
+ "decimals": "Decimales"
6
+ }
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "node-red-contrib-pt1",
3
+ "version": "1.0.0",
4
+ "description": "PT1 first-order low-pass filter for smoothing noisy measurement values",
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
+ "pt1.js",
14
+ "pt1.html",
15
+ "locales",
16
+ "examples"
17
+ ],
18
+ "keywords": [
19
+ "node-red",
20
+ "pt1",
21
+ "low-pass",
22
+ "filter",
23
+ "smooth",
24
+ "lag",
25
+ "measurement"
26
+ ],
27
+ "author": "sr.rpo",
28
+ "license": "MIT",
29
+ "node-red": {
30
+ "version": "1.0.0",
31
+ "nodes": {
32
+ "pt1": "pt1.js"
33
+ }
34
+ },
35
+ "engines": {
36
+ "node": ">=14.0.0"
37
+ },
38
+ "scripts": {
39
+ "test": "mocha \"test/**/*_spec.js\" --exit"
40
+ },
41
+ "devDependencies": {
42
+ "mocha": "^10.2.0",
43
+ "node-red": "^3.1.0",
44
+ "node-red-node-test-helper": "^0.3.6"
45
+ }
46
+ }
package/pt1.html ADDED
@@ -0,0 +1,70 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('pt1', {
3
+ category: 'rpo',
4
+ color: '#D4600A',
5
+ defaults: {
6
+ name: { value: '' },
7
+ T1: { value: 60 },
8
+ T1Unit: { value: 's' },
9
+ decimals: { value: 2 }
10
+ },
11
+ inputs: 1,
12
+ outputs: 1,
13
+ icon: 'font-awesome/fa-filter',
14
+ label: function() { return this.name || ('PT1 T1=' + this.T1 + this.T1Unit); },
15
+ labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
16
+ });
17
+ </script>
18
+
19
+ <script type="text/html" data-template-name="pt1">
20
+ <div class="form-row">
21
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
22
+ <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
23
+ </div>
24
+ <div class="form-row">
25
+ <label for="node-input-T1"><i class="fa fa-clock-o"></i> <span data-i18n="pt1.label.T1"></span></label>
26
+ <input type="number" id="node-input-T1" min="0" style="width:50%">
27
+ <select id="node-input-T1Unit" style="width:18%; margin-left:4px">
28
+ <option value="s">s</option>
29
+ <option value="min">min</option>
30
+ <option value="h">h</option>
31
+ </select>
32
+ </div>
33
+ <div class="form-row">
34
+ <label for="node-input-decimals"><i class="fa fa-sort-numeric-asc"></i> <span data-i18n="pt1.label.decimals"></span></label>
35
+ <input type="number" id="node-input-decimals" min="0" max="4" style="width:70%">
36
+ </div>
37
+ </script>
38
+
39
+ <script type="text/html" data-help-name="pt1">
40
+ <p>PT1 first-order low-pass filter. Smooths noisy measurement values with a configurable time constant <strong>T1</strong>.</p>
41
+
42
+ <h3>Formula</h3>
43
+ <pre>y(t) = y(t-1) + (dt / (T1 + dt)) × (x(t) - y(t-1))</pre>
44
+
45
+ <h3>Input</h3>
46
+ <dl class="message-properties">
47
+ <dt>payload <span class="property-type">number</span></dt>
48
+ <dd>Raw measurement value to filter.</dd>
49
+ <dt class="optional">T1 <span class="property-type">number</span></dt>
50
+ <dd>Override time constant (in seconds) for this message only.</dd>
51
+ <dt class="optional">reset <span class="property-type">boolean</span></dt>
52
+ <dd>Set to <code>true</code> to reset filter state — next input passes through directly.</dd>
53
+ </dl>
54
+
55
+ <h3>Output</h3>
56
+ <dl class="message-properties">
57
+ <dt>payload <span class="property-type">number</span></dt>
58
+ <dd>Filtered (smoothed) value.</dd>
59
+ <dt>pt1 <span class="property-type">object</span></dt>
60
+ <dd><code>{ input, output, T1, dt, decimals, initialized }</code></dd>
61
+ </dl>
62
+
63
+ <h3>Tips</h3>
64
+ <ul>
65
+ <li>The first message always passes through unfiltered (no previous state).</li>
66
+ <li>Set T1 = 0 to disable filtering (passthrough mode).</li>
67
+ <li>Larger T1 = more smoothing but slower response to changes.</li>
68
+ <li>The node uses actual elapsed time (dt) between messages — no fixed interval needed.</li>
69
+ </ul>
70
+ </script>
package/pt1.js ADDED
@@ -0,0 +1,77 @@
1
+ module.exports = function(RED) {
2
+ function PT1Node(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+
6
+ var _T1raw = parseFloat(config.T1);
7
+ var T1raw = isNaN(_T1raw) ? 60 : _T1raw;
8
+ var unit = config.T1Unit || 's';
9
+ node.decimals = parseInt(config.decimals, 10);
10
+ if (isNaN(node.decimals) || node.decimals < 0) node.decimals = 2;
11
+
12
+ // Convert T1 to seconds
13
+ node.T1 = toSeconds(T1raw, unit);
14
+
15
+ node.lastOutput = undefined;
16
+ node.lastTime = undefined;
17
+
18
+ node.status({ fill: 'grey', shape: 'ring', text: 'Initializing...' });
19
+
20
+ node.on('input', function(msg) {
21
+ if (msg.reset === true) {
22
+ node.lastOutput = undefined;
23
+ node.lastTime = undefined;
24
+ node.status({ fill: 'grey', shape: 'ring', text: 'Initializing...' });
25
+ return;
26
+ }
27
+
28
+ var x = parseFloat(msg.payload);
29
+ if (isNaN(x)) { node.warn('Input is not a number: ' + msg.payload); return; }
30
+
31
+ var T1 = (msg.T1 !== undefined) ? parseFloat(msg.T1) : node.T1;
32
+ if (isNaN(T1) || T1 < 0) T1 = node.T1;
33
+
34
+ var now = Date.now();
35
+ var output;
36
+ var initialized = true;
37
+
38
+ if (node.lastOutput === undefined || node.lastTime === undefined) {
39
+ output = x;
40
+ initialized = false;
41
+ } else {
42
+ var dt = (now - node.lastTime) / 1000;
43
+ if (T1 === 0) {
44
+ output = x;
45
+ } else {
46
+ var alpha = dt / (T1 + dt);
47
+ output = node.lastOutput + alpha * (x - node.lastOutput);
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.pt1 = {
57
+ input: x,
58
+ output: output,
59
+ T1: T1,
60
+ dt: node.lastTime ? Math.round((now - (node.lastTime === now ? now : node.lastTime)) / 10) / 100 : 0,
61
+ decimals: node.decimals,
62
+ initialized: initialized
63
+ };
64
+
65
+ node.status({ fill: 'green', shape: 'dot', text: 'in: ' + x + ' → out: ' + output + ' (T1: ' + T1 + 's)' });
66
+ node.send(msg);
67
+ });
68
+
69
+ function toSeconds(val, unit) {
70
+ if (unit === 'min') return val * 60;
71
+ if (unit === 'h') return val * 3600;
72
+ return val;
73
+ }
74
+ }
75
+
76
+ RED.nodes.registerType('pt1', PT1Node);
77
+ };