node-red-contrib-pwm 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,33 @@
1
+ # node-red-contrib-rpo-pwm
2
+
3
+ A Node-RED node: **PWM**
4
+
5
+ Converts a duty cycle (0–100 %) into a PWM signal by toggling a 0/1 output at the configured frequency.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install node-red-contrib-rpo-pwm
11
+ ```
12
+
13
+ Or via the Node-RED Palette Manager.
14
+
15
+ ## Inputs
16
+
17
+ - **payload** (number): Duty cycle 0–100 %
18
+ - **msg.bypass** (boolean): If true, pass raw value through
19
+
20
+ ## Outputs
21
+
22
+ - **Output 1**: 1 (ON) or 0 (OFF) according to PWM cycle
23
+
24
+ ## Configuration
25
+
26
+ | Parameter | Description |
27
+ |-----------|-------------|
28
+ | Frequency (Hz) | PWM switching frequency |
29
+ | Min ON time (ms) | Minimum pulse width to avoid very short pulses |
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,14 @@
1
+ {
2
+ "pwm": {
3
+ "label": "PWM",
4
+ "frequency": "Frequenz (Hz)",
5
+ "minOnTime": "Min. Einschaltdauer (ms)",
6
+ "tip": "Payload 0–100 setzt den Tastgrad. msg.bypass=true leitet Rohwert weiter.",
7
+ "input": {
8
+ "payload": "Tastgrad 0–100 % (Zahl)"
9
+ },
10
+ "output": {
11
+ "payload": "1 (EIN) oder 0 (AUS) entsprechend dem PWM-Zyklus"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "pwm": {
3
+ "label": "PWM",
4
+ "frequency": "Frequency (Hz)",
5
+ "minOnTime": "Min ON time (ms)",
6
+ "tip": "Payload 0–100 sets duty cycle. msg.bypass=true outputs raw value.",
7
+ "input": {
8
+ "payload": "Duty cycle 0–100 % (number)"
9
+ },
10
+ "output": {
11
+ "payload": "1 (ON) or 0 (OFF) according to PWM cycle"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "pwm": {
3
+ "label": "PWM",
4
+ "frequency": "Frecuencia (Hz)",
5
+ "minOnTime": "Tiempo mín. ON (ms)",
6
+ "tip": "Payload 0–100 establece el ciclo de trabajo. msg.bypass=true pasa el valor directo.",
7
+ "input": {
8
+ "payload": "Ciclo de trabajo 0–100 % (número)"
9
+ },
10
+ "output": {
11
+ "payload": "1 (ON) o 0 (OFF) según ciclo PWM"
12
+ }
13
+ }
14
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "node-red-contrib-pwm",
3
+ "version": "1.0.0",
4
+ "description": "PWM generator - converts 0-100% analog value to digital pulse-width signal",
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
+ "pwm.js",
14
+ "pwm.html",
15
+ "locales",
16
+ "examples"
17
+ ],
18
+ "keywords": [
19
+ "node-red",
20
+ "pwm",
21
+ "pulse",
22
+ "duty-cycle",
23
+ "automation"
24
+ ],
25
+ "author": "sr.rpo",
26
+ "license": "MIT",
27
+ "node-red": {
28
+ "version": "1.0.0",
29
+ "nodes": {
30
+ "pwm": "pwm.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
+ }
package/pwm.html ADDED
@@ -0,0 +1,60 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('pwm', {
3
+ category: 'rpo',
4
+ color: '#D4600A',
5
+ defaults: {
6
+ "name": {
7
+ "value": ""
8
+ },
9
+ "period": {
10
+ "value": 10
11
+ },
12
+ "periodUnit": {
13
+ "value": "s"
14
+ },
15
+ "invert": {
16
+ "value": false
17
+ }
18
+ },
19
+ inputs: 1,
20
+ outputs: 1,
21
+ icon: 'font-awesome/fa-signal',
22
+ label: function() { return this.name || 'pwm'; },
23
+ labelStyle: function() { return this.name ? 'node_label_italic' : ''; }
24
+ });
25
+ </script>
26
+
27
+ <script type="text/html" data-template-name="pwm">
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-period"><i class="fa fa-clock-o"></i> Period</label>
34
+ <input type="number" id="node-input-period" min="1" style="width:50%">
35
+ <select id="node-input-periodUnit" 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-invert"><i class="fa fa-exchange"></i> Invert</label>
43
+ <input type="checkbox" id="node-input-invert" style="width:auto;margin-top:4px">
44
+ </div>
45
+ </script>
46
+
47
+ <script type="text/html" data-help-name="pwm">
48
+ <p>PWM generator: converts a 0–100% analog value to a digital pulse-width modulation signal.</p>
49
+ <h3>Input</h3>
50
+ <dl class="message-properties">
51
+ <dt>payload <span class="property-type">number</span></dt><dd>Duty cycle 0–100%</dd>
52
+ <dt class="optional">bypass <span class="property-type">boolean</span></dt><dd>Force ON/OFF</dd>
53
+ <dt class="optional">clearBypass <span class="property-type">boolean</span></dt><dd>Return to PWM</dd>
54
+ </dl>
55
+ <h3>Output</h3>
56
+ <dl class="message-properties">
57
+ <dt>payload <span class="property-type">boolean</span></dt><dd>PWM state (fires on state change only)</dd>
58
+ <dt>pwm <span class="property-type">object</span></dt><dd>{ input, output, dutyOn, dutyOff, period, bypass, inverted }</dd>
59
+ </dl>
60
+ </script>
package/pwm.js ADDED
@@ -0,0 +1,95 @@
1
+ module.exports = function(RED) {
2
+ function PWMNode(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+
6
+ var pRaw = parseFloat(config.period) || 10;
7
+ var pUnit = config.periodUnit || 's';
8
+ node.period = pUnit === 'min' ? pRaw * 60000 : pUnit === 'ms' ? pRaw : pRaw * 1000;
9
+ node.invert = config.invert === true;
10
+ node.input = 0;
11
+ node.bypass = null;
12
+ node.output = false;
13
+ node.timer = null;
14
+
15
+ node.status({ fill:'grey', shape:'dot', text:'OFF 0%' });
16
+ scheduleCycle(node);
17
+
18
+ node.on('input', function(msg) {
19
+ if (msg.clearBypass === true) { node.bypass = null; return; }
20
+ if (msg.bypass !== undefined) {
21
+ node.bypass = (msg.bypass === true || msg.bypass === 'true') ? true : false;
22
+ updateStatus(node);
23
+ return;
24
+ }
25
+ var val = parseFloat(msg.payload);
26
+ if (isNaN(val)) { node.warn('Input is not a number: ' + msg.payload); return; }
27
+ node.input = Math.max(0, Math.min(100, val));
28
+ if (node.timer) clearTimeout(node.timer);
29
+ scheduleCycle(node);
30
+ });
31
+
32
+ node.on('close', function() {
33
+ if (node.timer) clearTimeout(node.timer);
34
+ });
35
+
36
+ function scheduleCycle(n) {
37
+ var pct = n.input / 100;
38
+ var onTime = pct * n.period;
39
+ var offTime = n.period - onTime;
40
+ var bypass = n.bypass;
41
+
42
+ if (bypass !== null) {
43
+ setOutput(n, bypass, onTime, offTime);
44
+ n.timer = setTimeout(function() { scheduleCycle(n); }, n.period);
45
+ return;
46
+ }
47
+
48
+ if (pct <= 0) {
49
+ setOutput(n, false, onTime, offTime);
50
+ n.timer = setTimeout(function() { scheduleCycle(n); }, n.period);
51
+ } else if (pct >= 1) {
52
+ setOutput(n, true, onTime, offTime);
53
+ n.timer = setTimeout(function() { scheduleCycle(n); }, n.period);
54
+ } else {
55
+ setOutput(n, true, onTime, offTime);
56
+ n.timer = setTimeout(function() {
57
+ setOutput(n, false, onTime, offTime);
58
+ n.timer = setTimeout(function() { scheduleCycle(n); }, offTime);
59
+ }, onTime);
60
+ }
61
+ }
62
+
63
+ function setOutput(n, rawState, onTime, offTime) {
64
+ var state = n.invert ? !rawState : rawState;
65
+ if (state === n.output) return;
66
+ n.output = state;
67
+ var msg = {
68
+ payload: state ? 1 : 0,
69
+ pwm: {
70
+ input: n.input,
71
+ output: state,
72
+ dutyOn: Math.round(onTime),
73
+ dutyOff: Math.round(offTime),
74
+ period: n.period,
75
+ bypass: n.bypass !== null ? n.bypass : false,
76
+ inverted: n.invert
77
+ }
78
+ };
79
+ updateStatus(n);
80
+ n.send(msg);
81
+ }
82
+
83
+ function updateStatus(n) {
84
+ if (n.bypass !== null) {
85
+ n.status({ fill:'yellow', shape:'dot', text:'BYPASS ' + (n.bypass ? 'ON' : 'OFF') });
86
+ } else if (n.output) {
87
+ n.status({ fill:'green', shape:'dot', text:'ON ' + n.input + '%' });
88
+ } else {
89
+ n.status({ fill:'grey', shape:'dot', text:'OFF ' + n.input + '%' });
90
+ }
91
+ }
92
+ }
93
+
94
+ RED.nodes.registerType('pwm', PWMNode);
95
+ };