@strato-automation/node-red-contrib-strato-automation
Version:
Strato Automation Official Node Red Library
267 lines (243 loc) • 11.3 kB
HTML
<!--
Copyright 2016 Colin Law
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/javascript">
RED.nodes.registerType('strato-pid', {
category: 'Strato Automation',
color: '#f3b567',
defaults: {
name: { value: "" },
setpoint: { value: 50.0 },
pb: { value: 1, required: true }, // 1/kp
ti: { value: 10000, required: true }, // 1/ki
td: { value: 0, required: true }, // kd
kp: { value: 1, required: true },
ki: { value: 0, required: true },
kd: { value: 0, required: true },
integral_default: { value: 0.5 },
bias: { value: 0, required: true },
enable: { value: 1, required: true },
disabled_op: { value: 0 },
k_mode: { value: false, required: true },
deadband: { value: 0, required: true },
min_output: { value: 0 },
max_output: { value: 1 },
exec_time: { value: 10 },
reverse_action: { value: false, required: true },
initialized: { value: false }
},
inputs: 1,
outputs: 1,
icon: "function.png",
paletteLabel: function () {
return "PID";
},
label: function () {
return this.name || "PID";
},
oneditprepare: function () {
var node = this;
$("#node-input-pb").on("change", function () {
$("#node-input-kp").val(1 / this.value);
});
$("#node-input-ti").on("change", function () {
$("#node-input-ki").val(1 / this.value);
});
$("#node-input-td").on("change", function () {
$("#node-input-kd").val(this.value);
});
$("#node-input-kp").on("change", function () {
$("#node-input-pb").val(1 / this.value);
});
$("#node-input-ki").on("change", function () {
$("#node-input-ti").val(1 / this.value);
});
$("#node-input-kd").on("change", function () {
$("#node-input-td").val(this.value);
});
},
});
RED.events.on("nodes:add", function (node) {
if (node.type == "strato-pid" && !node.initialized) {
// Get node
var node = RED.nodes.node(node.id);
// Timeout to make sure the node is initialized and ready
setTimeout(() => {
setpt_node = addNode(RED, node, {
type: 'input-topic',
name: "Setpoint",
topic: "in1"
}, -200, 50)
in_node = addNode(RED, node, {
type: 'input-topic',
name: "Input",
topic: "default"
}, -200, -50)
groupNodes([node, setpt_node, in_node])
}, 200);
node.initialized = true;
}
});
</script>
<script type="text/html" data-template-name="strato-pid">
<!-- Basic Settings -->
<div class="form-tips" style="margin-bottom: 20px;">
<b>Basic Settings</b>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-bookmark"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Optional name for this PID controller">
</div>
<div class="form-row">
<label for="node-input-setpoint"><i class="fa fa-bullseye"></i> Fallback Setpoint</label>
<input type="text" id="node-input-setpoint" placeholder="Initial setpoint value">
</div>
<!-- PID Parameters -->
<div class="form-tips" style="margin: 20px 0;">
<b>PID Parameters</b>
</div>
<div style="padding-left: 10px; border-left: 3px solid #ddd;">
<div class="form-row">
<label for="node-input-pb"><i class="fa fa-sliders"></i> Proportional Band</label>
<input type="text" id="node-input-pb" placeholder="Process value range for full power">
</div>
<div class="form-row">
<label for="node-input-ti"><i class="fa fa-clock-o"></i> Integral Time (s)</label>
<input type="text" id="node-input-ti" placeholder="Time constant for integral effect">
</div>
<div class="form-row">
<label for="node-input-td"><i class="fa fa-clock-o"></i> Derivative Time (s)</label>
<input type="text" id="node-input-td" placeholder="Time constant for derivative effect">
</div>
<div class="form-row">
<label for="node-input-kp"><i class="fa fa-line-chart"></i> Proportional Gain</label>
<input type="text" id="node-input-kp" placeholder="Proportional gain (Kp)">
</div>
<div class="form-row">
<label for="node-input-ki"><i class="fa fa-area-chart"></i> Integral Gain</label>
<input type="text" id="node-input-ki" placeholder="Integral gain (Ki)">
</div>
<div class="form-row">
<label for="node-input-kd"><i class="fa fa-bar-chart"></i> Derivative Gain</label>
<input type="text" id="node-input-kd" placeholder="Derivative gain (Kd)">
</div>
</div>
<!-- Output Settings -->
<div class="form-tips" style="margin: 20px 0;">
<b>Output Settings</b>
</div>
<div style="padding-left: 10px; border-left: 3px solid #ddd;">
<div class="form-row">
<label for="node-input-bias"><i class="fa fa-balance-scale"></i> Bias</label>
<input type="text" id="node-input-bias" placeholder="Output bias value">
</div>
<div class="form-row">
<label for="node-input-min_output"><i class="fa fa-arrow-down"></i> Minimum Output</label>
<input type="text" id="node-input-min_output" placeholder="Minimum output value">
</div>
<div class="form-row">
<label for="node-input-max_output"><i class="fa fa-arrow-up"></i> Maximum Output</label>
<input type="text" id="node-input-max_output" placeholder="Maximum output value">
</div>
<div class="form-row">
<label for="node-input-deadband"><i class="fa fa-arrows-h"></i> Deadband</label>
<input type="text" id="node-input-deadband" placeholder="Control deadband">
</div>
</div>
<!-- Control Settings -->
<div class="form-tips" style="margin: 20px 0;">
<b>Control Settings</b>
</div>
<div style="padding-left: 10px; border-left: 3px solid #ddd;">
<div class="form-row">
<label for="node-input-enable"><i class="fa fa-power-off"></i> Enable State</label>
<input type="text" id="node-input-enable" placeholder="1 for enabled, 0 for disabled">
</div>
<div class="form-row">
<label for="node-input-disabled_op"><i class="fa fa-stop"></i> Disabled Output</label>
<input type="text" id="node-input-disabled_op" placeholder="Output when disabled">
</div>
<div class="form-row">
<label for="node-input-exec_time"><i class="fa fa-hourglass-half"></i> Execution Time (s)</label>
<input type="text" id="node-input-exec_time" placeholder="Time between executions">
</div>
<div class="form-row">
<label for="node-input-reverse_action"><i class="fa fa-exchange"></i> Reverse Action</label>
<input type="checkbox" id="node-input-reverse_action" style="width: auto;">
</div>
</div>
<div class="form-tips" style="margin-top: 20px;">
<p><i class="fa fa-info-circle"></i> Tip: All parameters can be dynamically updated through messages. Use appropriate msg.topic values like 'setpoint', 'prop_band', etc.</p>
</div>
</script>
<script type="text/html" data-help-name="strato-pid">
<p>A sophisticated PID (Proportional-Integral-Derivative) controller node for precise control of real-world processes.</p>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd>The current process value (PV) to be controlled</dd>
<dt class="optional">topic <span class="property-type">string</span></dt>
<dd>
Special topics for parameter updates:
<ul>
<li><code>setpoint</code> or <code>in1</code> - Update setpoint</li>
<li><code>prop_band</code> - Update proportional band</li>
<li><code>t_integral</code> - Update integral time</li>
<li><code>t_derivative</code> - Update derivative time</li>
<li><code>enable</code> - Enable/disable control</li>
</ul>
</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">number</span></dt>
<dd>The calculated output value (0-1 or min_output to max_output)</dd>
<dt>integral <span class="property-type">number</span></dt>
<dd>Current integral term value (for persistence)</dd>
<dt>derivative <span class="property-type">number</span></dt>
<dd>Current derivative term value</dd>
</dl>
<h3>Details</h3>
<p>This node implements a complete PID control algorithm with features including:</p>
<ul>
<li>Configurable P, I, and D terms</li>
<li>Anti-windup protection</li>
<li>Adjustable output limits</li>
<li>Deadband control</li>
<li>Reverse action option</li>
<li>Enable/disable control</li>
<li>Configurable execution interval</li>
</ul>
<h3>Parameter Tuning</h3>
<p><b>Quick Start Guide:</b></p>
<ol>
<li>Start with P control only (set Ti to max and Td to 0)</li>
<li>Adjust Proportional Band for stable operation</li>
<li>Add Integral control by reducing Ti</li>
<li>Finally, add Derivative if needed (typically 15-25% of Ti)</li>
</ol>
<h3>Status Indicators</h3>
<dl class="message-properties">
<dt>Green dot</dt>
<dd>Normal operation with PV near setpoint</dd>
<dt>Blue dot</dt>
<dd>Normal operation with PV in deadband</dd>
<dt>Yellow dot</dt>
<dd>Controller disabled or integral locked</dd>
<dt>Red dot</dt>
<dd>Error condition (bad PV or timing)</dd>
</dl>
<h3>Persistence</h3>
<p>The integral term can be persisted across restarts using the following formula:</p>
<pre>integral_default = 0.5 - (cached_integral/prop_band)</pre>
<p>Set this value before the first PV arrives to maintain control consistency.</p>
</script>