npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-red-contrib-hardloop-pid-controller

v1.0.0

Published

A hard-loop PID controller node for Node-RED on soft-PLC platforms. Accepts raw ADC counts as PV input, runs PID in engineering units, and outputs AO counts for direct hardware register writes. Built for Kunbus RevPi, Siemens IOT2050, Wago PFC200, Advante

Readme

node-red-contrib-hardloop-pid-controller

License npm version Node-RED simple-pid-controller

A hard-loop PID controller node for Node-RED on soft-PLC platforms where Node-RED is the control runtime — not a supervisory overlay.

Built on simple-pid-controller v2.0.1.

Author: Harshad Joshi @ Bufferstack.IO Analytics Technology LLP, Pune
License: Apache-2.0


Table of Contents


Design Philosophy

Most Node-RED PID nodes — including this author's own easy-pid-controller — are designed as supervisory controllers. They receive process values in engineering units, compute a PID output in engineering units, and publish it for dashboards, historians, and SCADA displays. The actual hardware driving (count-level I/O) is left to a PLC running beneath.

This is the correct architecture when a PLC is present.

However, a growing class of industrial hardware runs Node-RED as the PLC runtime itself:

  • Kunbus RevPi — Raspberry Pi CM in DIN-rail housing with industrial I/O modules
  • Siemens SIMATIC IOT2050 — Linux industrial gateway with Node-RED built in
  • Wago PFC200 — IEC 61131-3 soft-PLC with Node-RED runtime
  • Advantech ADAM-3600 — Linux edge controller with Node-RED
  • Beckhoff CX series — TwinCAT soft-PLC, Node-RED runs alongside
  • Any Linux-based soft-PLC running Codesys runtime + Node-RED on the same hardware

On these platforms, there is no PLC beneath. Node-RED reads raw ADC counts directly from hardware registers and must write raw DAC counts back to hardware registers to drive physical actuators. Engineering unit conversion, PID computation, and hardware I/O are all owned by Node-RED.

hardloop-pid-controller is built for exactly this scenario.

Core Principles

  1. Counts in, counts out — PV arrives as a raw integer from the AI hardware register. AO_count leaves as a raw integer ready to write to the AO hardware register. No external conversion nodes required.

  2. PID runs in engineering units — The scaling happens inside the node, not in the flow. The engineer configures AI count range and EU range once in the node editor. The PID always operates in physically meaningful values (°C, bar, RPM) regardless of the card resolution.

  3. AI and AO cards are configured independently — In real installations, the input card and output card often have different resolutions (e.g. AI: 0–4000, AO: 0–4000, or AI: 0–16000, AO: 0–8000). Both are configured separately to match the actual card datasheet.

  4. Watchdog is mandatory, not optional — In supervisory mode, a Node-RED crash means the dashboard goes dark. In hard-loop mode, a crash means the physical process runs uncontrolled. The built-in watchdog forces a safe AO count output if PV stops arriving, and stops the loop until the fault is cleared.

  5. Derivative filtering for ADC noise — Integer counts produce a staircase waveform. Without filtering, the derivative term amplifies every LSB step into a spike. A configurable first-order low-pass filter on the D term is included as a first-class parameter, not an afterthought.

  6. Honest about timing limits — Node-RED runs on the V8 event loop. It is not a real-time OS. This node is suitable for slow-to-medium processes (temperature, level, pressure, flow) with loop times ≥100ms. It is not suitable for fast loops (position, speed, torque). This is stated clearly in the node help and this README, not buried in small print.


Use Cases

Suitable

| Process | Typical loop time | Notes | |---|---|---| | Temperature control (oven, tank, reactor) | 500ms – 2000ms | Classic PID application, ideal fit | | Liquid level control | 500ms – 2000ms | Slow dynamics, excellent fit | | Pressure control (vessel, pipeline) | 200ms – 1000ms | Depends on vessel volume | | Flow control (liquid) | 100ms – 500ms | Acceptable at lower end of range | | Humidity control | 1000ms – 5000ms | Very slow, ideal fit | | pH / conductivity control | 500ms – 2000ms | Chemical dosing processes |

Not Suitable

| Process | Reason | |---|---| | Servo position control | Requires <1ms loop, hard real-time OS needed | | VFD speed control (closed loop) | Requires <10ms, use drive's internal PID | | Torque control | Sub-millisecond, requires dedicated drive firmware | | CNC axis control | Hard real-time, LinuxCNC or dedicated controller |


How It Differs from easy-pid-controller

| | easy-pid-controller | hardloop-pid-controller | |---|---|---| | Role | Supervisory / analytical | Soft-PLC hard control loop | | PV input | Engineering units (float) | Raw ADC counts (integer) | | SV input | Engineering units | Engineering units | | PID domain | Engineering units | Engineering units (converted internally) | | Primary output | Signal float (mA or V) for tracing | AO_count integer for register write | | Secondary output | Signal for dashboard | Signal for tracing + AO_count for hardware | | Count scaling | None (PLC handles it) | Built in, both AI and AO sides | | AI/AO independent config | N/A | Yes — separate count ranges | | Watchdog | Not needed | Mandatory, configurable timeout | | Safe state output | N/A | safe_ao_count on watchdog trip | | Derivative filter | Not needed | Built in (ADC noise mitigation) | | Scan time monitoring | No | scan_ms field every cycle | | Fault field in output | No | Yes — fault boolean | | Target platforms | Any Node-RED, any hardware | Soft-PLC platforms with direct hardware I/O | | Failure impact | Dashboard goes dark | Writes safe state, stops loop |

Both nodes use the same simple-pid-controller v2.0.0 core, so all v2.0.0 features (anti-windup, output clamping, derivative on measurement, bumpless transfer, runtime gain tuning, reset, EventEmitter) are present in both.


The Signal Chain

Physical Sensor           Soft-PLC Hardware          Node-RED
───────────────           ─────────────────────────  ─────────────────────────────────
4-20mA / 0-10V    →       AI card ADC                msg.topic  = 'PV'
(pressure, temp,           samples signal             msg.payload = 2731  (raw count)
 flow, level)              stores integer count   →
                           in hardware register
                                                           ↓ countToEU()
                                                       2731 counts → 68.27°C
                                                           ↓
                                                       PID controller (EU domain)
                                                       SV=75°C, PV=68.27°C
                                                       output = 4.2°C equivalent
                                                           ↓ euToAOCount()
                                                       4.2°C → 1680 counts
                                                           ↓
                           AO card DAC            ←   msg.payload.AO_count = 1680
                           count → mA/V               (write to AO register via
                           drives valve/VFD/heater     Modbus TCP / SLMP / OPC UA)

Installation

npm install node-red-contrib-hardloop-pid-controller

Or via the Node-RED palette manager: search hardloop-pid.


Quick Start

Minimum working flow

[Inject PV count]  →  [hardloop-pid-controller]  →  [Modbus write AO register]
[Inject SV]        ↗
[Inject auto:true] ↗

Step by step

  1. Add a hardloop-pid-controller node to your flow.
  2. Configure it:
    • Set Kp, Ki, Kd for your process.
    • Set AI count range to match your AI card (e.g. 0–4000 for Mitsubishi Q64AD).
    • Set AO count range to match your AO card (e.g. 0–4000 for Q62DA).
    • Set EU range (e.g. 0–100 for 0–100°C sensor).
    • Set watchdog timeout (recommend 3× your loop dt in ms).
    • Set safe AO count (0 = valve closed, or a safe mid-position count).
  3. Wire a Modbus TCP read / SLMP read / OPC UA read node to publish PV counts with msg.topic = "PV".
  4. Send the setpoint: msg.topic = "SV", msg.payload = 75 (engineering units).
  5. Start the loop: msg.topic = "auto", msg.payload = true.
  6. Wire the output to a Modbus TCP write / SLMP write / OPC UA write node targeting your AO register, using msg.payload.AO_count.

Node Configuration

PID Gains

| Field | Default | Description | |---|---|---| | Kp | 1.2 | Proportional gain | | Ki | 1.0 | Integral gain | | Kd | 0.01 | Derivative gain | | Loop dt (s) | 1.0 | Target scan interval. Minimum 0.05s (50ms) recommended. |

AI Input — Count Range

| Field | Default | Description | |---|---|---| | AI Count Min | 0 | Minimum count from AI card (0 for 0-10V full range; check datasheet for 4-20mA live-zero offset) | | AI Count Max | 4000 | Maximum count. Common values: 4000, 8000, 16000 — check your card datasheet |

AO Output — Count Range

| Field | Default | Description | |---|---|---| | AO Count Min | 0 | Minimum count for AO card | | AO Count Max | 4000 | Maximum count for AO card. Configure from AO card datasheet independently of AI |

Engineering Unit Range

| Field | Default | Description | |---|---|---| | EU Min | 0 | Engineering unit value at count_min (e.g. 0°C, 0 bar) | | EU Max | 100 | Engineering unit value at count_max (e.g. 100°C, 10 bar) | | Signal Type | 4-20mA | Physical signal standard — affects Signal output field and documentation only. Count scaling is purely linear. |

PID Safety Limits

| Field | Default | Description | |---|---|---| | Output Min (EU) | 0 | Lower clamp on PID output in engineering units | | Output Max (EU) | 100 | Upper clamp on PID output in engineering units | | Integral Min | -500 | Anti-windup lower clamp on integral accumulator | | Integral Max | 500 | Anti-windup upper clamp on integral accumulator | | Deadband (EU) | 0 | Suppress output when |error| ≤ deadband. Set 0 to disable | | Settled Tolerance (EU) | 0 | Stop loop when |error| ≤ tolerance. Set 0 to disable | | Derivative Filter α | 1.0 | Low-pass filter on D term. 1.0 = no filter. 0.3 = moderate. 0.1 = heavy. |

Watchdog & Safe State

| Field | Default | Description | |---|---|---| | Watchdog (ms) | 3000 | Max time between PV updates before fault. Recommend 3× loop dt | | Safe AO Count | 0 | Count written to output on watchdog trip. Typically 0 (valve closed) |


Input Topics

| msg.topic | msg.payload | Description | |---|---|---| | PV | integer (raw AI count) | Current process variable as raw count from hardware register | | SV | number (EU) | Target setpoint in engineering units. Resets integral and derivative state. | | auto | true / false | true — start loop. false — stop loop, switch to manual mode | | gains | { k_p, k_i, k_d } | Update PID gains at runtime without restarting | | mode | "auto" or "manual" | Switch mode. manual→auto performs bumpless transfer | | manual_output | number (EU) | Fixed EU output for manual mode. Seeds bumpless transfer. | | reset | any | Clear integral, derivative, fault state, timestamps |


Output Payload

Every cycle in auto mode, msg.payload contains:

| Field | Type | Description | |---|---|---| | AO_count | integer | Write this to your AO hardware register. Clamped to [ao_count_min, ao_count_max]. | | PV_count | integer | Raw AI count as received | | PV_eu | number | PV converted to engineering units | | SV_eu | number | Current setpoint in engineering units | | error_eu | number | SV_eu − PV_eu | | P | number | Proportional component (EU) | | I | number | Integral component (EU, anti-windup clamped) | | D | number | Derivative component (EU, filtered) | | output_eu | number | Raw clamped PID output in engineering units | | Signal | number | Output in physical signal units (mA or V) for tracing | | fault | boolean | true if watchdog has tripped | | watchdog_ok | boolean | true on every healthy cycle | | scan_ms | number | Actual scan execution time in ms (monitor for jitter) | | mode | string | "auto", "manual", or "fault" |

On watchdog trip or fault, the output contains:

| Field | Value | |---|---| | AO_count | safe_ao_count | | fault | true | | watchdog_ok | false | | fault_reason | human-readable string | | mode | "fault" |


Watchdog Behaviour

The watchdog is a safety mechanism for hard-loop deployments where Node-RED is the control system. It works as follows:

Every valid PV message  →  watchdog timer resets (countdown restarts)
PV stops arriving       →  countdown expires
Timeout fires           →  AO_count = safe_ao_count sent immediately
                           PID loop stopped
                           Node status: FAULT
                           fault=true in all subsequent outputs

Recovery:
  1. Fix the PV data source (SLMP/Modbus read / sensor)
  2. Send a valid PV message (this clears the fault flag)
  3. Send  auto: true  to restart the loop

Recommended watchdog timeout: 3× your loop dt. For a 1-second loop, set watchdog to 3000ms.

Safe AO count selection:

  • For fail-closed valves: 0 (minimum count = valve closed)
  • For fail-open valves: ao_count_max
  • For fail-in-place: use a mid-scale value that matches the last known good position (advanced — requires external logic to track last position)

ADC Count Ranges

Common PLC analog input card resolutions and their count ranges:

| Count Range | Bits (effective) | Example Cards | |---|---|---| | 0 – 4000 | ~12-bit | Mitsubishi Q64AD, FX3U-4AD, many Siemens S7-300 SM331 | | 0 – 8000 | ~13-bit | Mitsubishi iQ-R R60AD, some OMRON CJ1W | | 0 – 16000 | ~14-bit | Mitsubishi Q68AD, R60ADI, Yokogawa high-res cards | | 0 – 27648 | 15-bit | Siemens S7-1200 / S7-1500 standard AI modules | | 0 – 32767 | 15-bit | Generic 15-bit ADC, some Beckhoff EL3xxx | | 0 – 65535 | 16-bit | High-resolution process cards, Beckhoff EL3174 |

Always verify the count range from your card's datasheet. The same card family can have different ranges depending on firmware version or configuration mode.

4-20mA Live-Zero Offset

For 4-20mA signals, some cards map:

  • 4mA → count_min (e.g. 0 counts — the offset is in the physical signal, not the count)
  • 20mA → count_max (e.g. 4000 counts)

Other cards use an internal offset:

  • 0mA → 0 counts
  • 4mA → 800 counts (on a 0–4000 card)
  • 20mA → 4000 counts

Set ai_count_min accordingly. Check your card datasheet. Mitsubishi Q64AD uses the first convention (0 counts = 4mA). Some generic ADC boards use the second.


Derivative Filter

ADC output is an integer staircase. Even with a perfectly stable physical process, consecutive readings may be 2981, 2982, 2981, 2983. The derivative term −d(PV)/dt amplifies every step into a spike.

The Derivative Filter α parameter applies a first-order low-pass (exponential moving average) filter:

D_filtered(t) = α × D_raw(t) + (1 − α) × D_filtered(t−1)

| α value | Effect | Use when | |---|---|---| | 1.0 | No filter (raw derivative) | PV is already smooth (e.g. filtered at sensor level) | | 0.5 | Light filtering | Low noise, 12-bit ADC, moderate process dynamics | | 0.3 | Moderate filtering | Typical 12-bit ADC with moderate noise | | 0.1 | Heavy filtering | Noisy signal, high-resolution ADC, slow process |

Start with 0.3 for most industrial ADC inputs and adjust based on observed D term behaviour in Grafana/dashboard.


Wiring to Hardware

Typical Node-RED flow for a soft-PLC temperature control loop

[SLMP Read D100]  → [set topic=PV]  → [hardloop-pid]  → [extract AO_count] → [SLMP Write D200]
[Inject SV=75]    →────────────────↗                  → [InfluxDB out]
[Inject auto=true]→────────────────↗                  → [Dashboard gauge]

Extracting AO_count for register write

// Function node between hardloop-pid output and Modbus/SLMP write node
msg.payload = msg.payload.AO_count;  // integer count only
return msg;

Fault handling

// Function node to route fault state to alarm
if (msg.payload.fault === true) {
    node.warn("PID fault: " + msg.payload.fault_reason);
    // Optionally route to email/SMS/alarm node
}
return msg;

Monitoring scan jitter in InfluxDB

// Send scan_ms to InfluxDB for jitter monitoring
msg.payload = { scan_ms: msg.payload.scan_ms };
return msg;

Limitations

  • Not suitable for fast loops (<100ms) — Node-RED V8 event loop jitter of 10–50ms makes it unsafe for position, speed, or torque control at these speeds.
  • Not a hard real-time system — Node-RED is a general-purpose runtime. Garbage collection pauses and event queue congestion can cause occasional missed cycles. Design your process to tolerate a missed scan without unsafe conditions.
  • Watchdog is software only — The watchdog runs inside Node-RED. If Node-RED itself crashes, the watchdog cannot fire. Rely on the platform's hardware watchdog (most soft-PLC platforms have one) as the final safety layer.
  • Single-loop only — Each node instance controls one process variable. Use multiple instances for multi-loop control.
  • No feedforward — This is a pure feedback PID. Feedforward terms must be implemented externally in function nodes if needed.

Changelog

v1.0.0 — 2026-03-14

  • Initial release
  • Raw AI count input with configurable count range (0–4000 / 0–8000 / 0–16000 and any custom range)
  • Count → EU conversion (AI side) built into the node
  • PID computation via simple-pid-controller v2.0.0 in EU domain
  • EU → AO count conversion (AO side) with integer rounding and hard clamping
  • Separate AI and AO count range configuration for mixed-resolution card installations
  • Built-in hardware watchdog with configurable timeout and safe AO count
  • Safe state output on watchdog trip, fault, node close/redeploy
  • Derivative low-pass filter (configurable α) for ADC quantisation noise mitigation
  • scan_ms field for loop jitter monitoring
  • fault, watchdog_ok, mode fields in every output message
  • All simple-pid-controller v2.0.0 features: anti-windup, output clamping, deadband, bumpless transfer, runtime gain tuning, reset, settled tolerance
  • Node-RED editor with full configuration form and inline help text
  • Comprehensive help panel with input/output documentation and platform notes

License

Copyright 2026, Harshad Joshi and Bufferstack.IO Analytics Technology LLP, Pune.

Licensed under the Apache-2.0 License. See LICENSE for full terms.