@wrepinski/node-red-smart-thermostat 2.0.7
Smart thermostat node with adaptive PID control, weekly schedules, boost mode, and Home Assistant MQTT integration. Supports Zigbee radiator valves and AC units.
@wrepinski/node-red-smart-thermostat
A Node-RED node for intelligent temperature control of Zigbee 3.0 radiator valve thermostats and AC units. Uses an adaptive PID algorithm to provide smooth, battery-friendly temperature regulation instead of simple ON/OFF control.
Dokumentacja po polsku / Polish documentation
Features
- Home Assistant HVAC Compatible - Uses
heat,cool,heat_coolmodes - Weekly Schedule - Flexible time slots for each day of the week
- Boost Mode - Temporary temperature override for N minutes
- Away Mode - Temperature limiting when not at home
- MQTT Discovery - Automatic climate entity creation in Home Assistant
- Thermostat Precision - Configurable step size (1°C, 0.5°C, 0.2°C, 0.1°C)
- Adaptive PID Control - Automatically learns optimal control parameters
- Battery-Friendly - Smooth output changes minimize valve motor activations
- Rate Limiting - Prevents rapid temperature setpoint changes that drain batteries
- Hysteresis Control - Dead-band prevents oscillation near the target temperature
- Active Regulation Output - Third output indicates when actively regulating
- Persistent State - Learned parameters and schedule saved to file
Installation
Via Node-RED Palette Manager
- Open Node-RED
- Go to Menu -> Manage palette -> Install
- Search for
@wrepinski/node-red-smart-thermostat - Click Install
Via npm
cd ~/.node-red
npm install @wrepinski/node-red-smart-thermostat
Manual/Offline Installation
cd ~/.node-red
npm install /path/to/wrepinski-node-red-smart-thermostat-2.0.6.tgz
Usage
Basic Setup
- Drag the smart thermostat node from the palette to your flow
- Select the operating mode (heat/cool/heat_cool)
- Set the thermostat precision (matches your device's step size)
- Connect your temperature sensor output to the node input
- Connect output 1 to your Zigbee thermostat's set-temperature input
- Configure the node with your desired min/max/target temperatures
Example Flow
┌─→ [Zigbee Thermostat]
[Temperature Sensor] → [Smart Thermostat] ─→ [Debug Node]
└─→ [Active Indicator]
Input Messages
| Property | Type | Description |
|---|---|---|
payload |
number | Current temperature reading (required) |
setpoint |
number | Override target temperature (optional) |
mode |
string | HVAC mode: heat, cool, or heat_cool (optional) |
operatingMode |
string | Operating mode: manual, schedule, or off (optional) |
schedule |
object | Weekly schedule (see Schedule section) |
boost |
object/boolean | Boost mode: {temp: 24, duration: 60} or false |
away |
boolean/number | Away mode: true, false, or specific temperature |
Output Messages
Output 1 - Temperature Setpoint:
{
payload: 21.5, // Setpoint to send to thermostat (rounded to precision)
topic: "thermostat/setpoint"
}
Output 2 - Debug/Status:
{
payload: {
currentTemp: 20.8,
targetTemp: 21.0,
setpoint: 21.5, // calculated setpoint
error: 0.2,
trend: "heating", // "heating", "cooling", "stable", or "idle"
mode: "heat_cool", // configured mode
activeMode: "heat", // current active mode (for heat_cool)
precision: 0.5, // thermostat step size
state: "running", // "learning" or "running"
learningComplete: true,
pid: {
Kp: 1.2,
Ki: 0.015,
Kd: 0.8
},
pidTerms: {
P: 0.24,
I: 0.08,
D: -0.02
}
}
}
Output 3 - Active Regulation:
{
payload: true, // or false, or 1/0 depending on config
topic: "thermostat/active"
}
Configuration
| Parameter | Default | Description |
|---|---|---|
| Mode | heat | Operating mode: heat, cool, or heat_cool (HA HVAC compatible) |
| Precision | 0.5°C | Thermostat step size: 1, 0.5, 0.2, or 0.1°C |
| Target Temp | 21°C | Default target temperature |
| Min Temp | 15°C | Minimum allowed setpoint |
| Max Temp | 25°C | Maximum allowed setpoint |
| Hysteresis | 0.2°C | Dead-band to prevent oscillation |
| Max Change | 0.5°C/cycle | Maximum temperature change per update |
| Sample Interval | 60s | Expected time between temperature readings |
| Auto-tuning | Enabled | Enable adaptive learning of PID parameters |
| Active Output | Boolean | Format for output 3: Boolean (true/false) or Number (1/0) |
Operating Modes (Home Assistant HVAC Compatible)
heat
For radiator valves and heating systems. When the room is cold (below target), the setpoint is set above target by at least one precision step to trigger heating.
cool
For AC units and cooling systems. When the room is hot (above target), the setpoint is set below target by at least one precision step to trigger cooling.
heat_cool
Automatically switches between heat and cool based on the current temperature error. Useful for heat pump systems or buildings with both heating and cooling.
How It Works
Setpoint Calculation with Precision
The controller respects your thermostat's precision setting:
- When actively heating: setpoint = target + (at least one precision step)
- When actively cooling: setpoint = target - (at least one precision step)
- When stable: setpoint = target (rounded to precision)
Example with precision = 0.5°C:
Target: 21.0°C
Room temp: 20.5°C (need heating)
Setpoint: 21.5°C (target + 0.5°C minimum)
Room temp: 20.9°C (within hysteresis)
Setpoint: 21.0°C (stable, matches target)
Adaptive Algorithm
The controller uses an adaptive PID (Proportional-Integral-Derivative) algorithm that:
Learning Phase (first ~1 hour):
- Observes how the room temperature responds to setpoint changes
- Estimates the thermal time constant of the room
- Calculates optimal PID parameters using Cohen-Coon tuning
Running Phase:
- Applies PID control with learned parameters
- Continuously fine-tunes parameters based on performance
- Detects and responds to temperature trends
Battery Saving
Traditional ON/OFF thermostats cause frequent valve motor activations, which drains batteries quickly. This node:
- Limits rate of change - Maximum 0.5°C change per cycle
- Uses hysteresis - No adjustment when within dead-band
- Smooth transitions - Gradual setpoint changes instead of step changes
Home Assistant Integration
MQTT Climate Discovery (Recommended)
Enable automatic creation of a climate entity in Home Assistant:
- Enable MQTT Discovery in node configuration
- Select your MQTT broker (must be the same broker used by Home Assistant)
- Set device name (will appear in Home Assistant)
- Deploy - the climate entity will appear automatically in HA
What you get in Home Assistant:
- Native thermostat card support
- Temperature control slider
- Preset modes:
away,boost - Mode switching:
heat,cool,off - Current temperature display
- Action indicator (heating/cooling/idle)
Requirements:
- MQTT broker configured in Node-RED
- MQTT integration in Home Assistant (with discovery enabled)
- Same broker for both Node-RED and HA
Example Lovelace card:
type: thermostat
entity: climate.smart_thermostat_xxxxx
Using node-red-contrib-home-assistant-websocket
To send the calculated setpoint to a Home Assistant climate entity, use the Call Service (action) node from node-red-contrib-home-assistant-websocket.
Configuration:
- Connect the first output of Smart Thermostat to the HA action node
- Configure the action node:
- Action:
climate.set_temperature - Target: Select your climate device (e.g.,
climate.living_room_thermostat) - Data (set type to
J:JSONata):{"temperature": $.payload}
- Action:
Example flow:
[Temperature Sensor] → [Smart Thermostat] → [HA: climate.set_temperature]
JSONata expressions for different scenarios:
Basic temperature setting:
{"temperature": $.payload}
With explicit HVAC mode:
{"temperature": $.payload, "hvac_mode": "heat"}
For heat_cool mode with high/low targets (using second output debug data):
{
"target_temp_high": $.payload.targetTemp + 1,
"target_temp_low": $.payload.targetTemp - 1,
"hvac_mode": "heat_cool"
}
Available climate.set_temperature parameters:
| Parameter | Type | Description |
|---|---|---|
temperature |
number | Target temperature (for heat or cool mode) |
target_temp_high |
number | Upper target (for heat_cool mode) |
target_temp_low |
number | Lower target (for heat_cool mode) |
hvac_mode |
string | heat, cool, heat_cool, off, auto |
Direct MQTT Publishing (Zigbee2MQTT)
For direct control via MQTT (e.g., Zigbee2MQTT), use an mqtt out node.
Configuration:
Connect the first output of Smart Thermostat to an mqtt out node
Configure the mqtt out node:
- Topic:
zigbee2mqtt/YOUR_DEVICE_NAME/set - QoS: 1
- Topic:
Add a change node between them to format the payload:
- Set
msg.payloadto JSONata expression:{"current_heating_setpoint": $.payload}
- Set
Example flow:
[Temperature Sensor] → [Smart Thermostat] → [Change Node] → [MQTT Out]
Alternative: Using function node:
msg.payload = {
current_heating_setpoint: msg.payload
};
return msg;
Common Zigbee2MQTT thermostat properties:
| Property | Description |
|---|---|
current_heating_setpoint |
Target temperature for heating |
occupied_heating_setpoint |
Setpoint when occupied |
system_mode |
off, heat, cool, auto |
running_state |
Current running state |
Note: Property names vary by device. Check your device's Zigbee2MQTT exposes page for exact property names.
Creating Climate Entity via WebSocket (ha-entity)
If you prefer WebSocket integration over MQTT, you can create a virtual climate entity in Home Assistant using the ha-entity node from node-red-contrib-home-assistant-websocket. This method doesn't require MQTT broker configuration.
Requirements:
node-red-contrib-home-assistant-websocketinstalled in Node-RED- Home Assistant with Node-RED integration configured
Setup:
- Connect the second output (debug) of Smart Thermostat to a function node
- Connect the function node to an ha-entity node configured as climate
Function node code:
// Map Smart Thermostat debug output to HA climate entity
const debug = msg.payload;
// Determine HVAC action
let action = 'idle';
if (debug.operatingMode === 'off') {
action = 'off';
} else if (debug.trend === 'heating' || (debug.activeMode === 'heat' && Math.abs(debug.error) >= 0.2)) {
action = 'heating';
} else if (debug.trend === 'cooling' || (debug.activeMode === 'cool' && Math.abs(debug.error) >= 0.2)) {
action = 'cooling';
}
// Determine preset
let preset = 'none';
if (debug.boostActive) {
preset = 'boost';
} else if (debug.awayMode) {
preset = 'away';
}
msg.payload = {
state: debug.operatingMode === 'off' ? 'off' : debug.mode,
attributes: {
current_temperature: debug.currentTemp,
temperature: debug.targetTemp,
hvac_action: action,
hvac_modes: ['off', 'heat', 'cool', 'heat_cool'],
min_temp: 15,
max_temp: 25,
target_temp_step: debug.precision || 0.5,
preset_mode: preset,
preset_modes: ['none', 'away', 'boost'],
friendly_name: 'Smart Thermostat'
}
};
return msg;
ha-entity node configuration:
| Setting | Value |
|---|---|
| Type | climate |
| State | msg.payload.state |
| State Type | msg |
| Attributes | msg.payload.attributes |
| Resend state | Enable |
| Override payload | Enable |
Handling commands from Home Assistant:
To receive commands from Home Assistant (temperature changes, mode switches), add an events: state node that listens to your climate entity:
// In a function node after events:state node
const newState = msg.payload;
const data = msg.data;
// Check what changed
if (data.new_state.attributes.temperature !== data.old_state.attributes.temperature) {
return { setpoint: data.new_state.attributes.temperature };
}
if (data.new_state.state !== data.old_state.state) {
if (data.new_state.state === 'off') {
return { operatingMode: 'off' };
} else {
return {
operatingMode: 'manual',
mode: data.new_state.state
};
}
}
if (data.new_state.attributes.preset_mode !== data.old_state.attributes.preset_mode) {
const preset = data.new_state.attributes.preset_mode;
if (preset === 'boost') {
return { boost: { temp: 24, duration: 60 } };
} else if (preset === 'away') {
return { away: true };
} else {
return { boost: false, away: false };
}
}
return null;
Example flow diagram:
┌──→ [function] → [ha-entity: climate]
[Temperature Sensor] → [Smart Thermostat] ──────────┤
↑ └──→ [Zigbee Thermostat]
│
[events:state] → [function: parse HA commands] ─────┘
Comparison: MQTT Discovery vs ha-entity
| Feature | MQTT Discovery | ha-entity (WebSocket) |
|---|---|---|
| Setup complexity | Easier | More configuration |
| MQTT broker required | Yes | No |
| Real-time updates | Yes | Yes |
| Bidirectional control | Yes | Yes |
| Works offline | Partially | No |
| Entity persistence | Automatic | Requires HA restart |
Schedule, Boost & Away Modes
Weekly Schedule
Set a weekly schedule with flexible time slots per day:
msg.schedule = {
"monday": [
{"time": "06:00", "temp": 21},
{"time": "08:30", "temp": 19},
{"time": "17:00", "temp": 21},
{"time": "22:00", "temp": 18}
],
"tuesday": [
{"time": "06:00", "temp": 21},
{"time": "22:00", "temp": 18}
],
"wednesday": [
{"time": "06:00", "temp": 21},
{"time": "22:00", "temp": 18}
],
// ... other days (sunday, monday, tuesday, wednesday, thursday, friday, saturday)
"default": 18 // Fallback temperature when no slot matches
};
msg.operatingMode = "schedule"; // Activate schedule mode
Each day can have any number of time slots. The controller uses the most recent slot before the current time.
Boost Mode
Temporarily override the temperature for a specified duration:
// Activate boost: 24°C for 60 minutes
msg.boost = { temp: 24, duration: 60 };
// Deactivate boost
msg.boost = false;
Boost has the highest priority and overrides both schedule and away modes.
Away Mode
Limit the maximum temperature when not at home:
// Enable away mode (uses configured away temperature)
msg.away = true;
// Enable away with specific temperature
msg.away = 16;
// Disable away mode
msg.away = false;
Temperature Priority
- Boost - Highest priority, overrides everything
- Away - Limits maximum temperature
- Schedule - Uses scheduled temperature for current time
- Manual - Uses configured target temperature
Dynamic Control
You can change settings dynamically by sending messages:
// Change target temperature
msg.setpoint = 22.5;
msg.payload = 20.1; // Current temperature reading
return msg;
// Change HVAC mode
msg.mode = "cool";
msg.payload = 24.5;
return msg;
// Change operating mode
msg.operatingMode = "schedule";
return msg;
This is useful for:
- Time-based schedules
- Presence detection
- Season-based mode switching
- Energy saving modes
Active Regulation Output
The third output indicates whether the thermostat is actively working to reach the target temperature:
- true/1 - Actively heating or cooling toward target
- false/0 - Idle (target reached, within hysteresis, or wrong mode for current conditions)
Use cases:
- Control circulation pumps
- Trigger notifications
- Energy monitoring
- Display active status on dashboards
Persistent Storage
Learned PID parameters are automatically saved to files and restored after Node-RED restart.
Storage location: ~/.node-red/.smart-thermostat/state-<node-id>.json
What is saved:
- PID parameters (Kp, Ki, Kd)
- Learning state and progress
- Temperature history for adaptation
- Current operating mode
When is state saved:
- When PID parameters change (after learning completes)
- When continuous adaptation adjusts parameters
- When node is closed/restarted
Resetting Learned Parameters
If the controller behaves unexpectedly or if your heating/cooling system changes:
- Open the node configuration in Node-RED editor
- Click the Reset Learned Parameters button
- Deploy the flow
- The controller will restart the learning phase
This deletes the state file and resets all learned parameters.
Troubleshooting
Output oscillates rapidly
- Increase the Hysteresis value (try 0.3-0.5°C)
- Decrease Max Change to limit rate of change
Response is too slow
- Disable Auto-tuning and manually set PID parameters
- Decrease Hysteresis value
Temperature overshoots target
- Wait for learning phase to complete (at least 1 hour)
- If persists after learning, reset and try again with more stable input data
Mode not changing
- Ensure you're sending
msg.modewith the temperature reading - Valid values:
heat,cool,heat_cool(case insensitive) - Legacy values also accepted:
heating,cooling,auto
Changelog
v2.0.6
- Repository Migration - Moved to new repository: node-red-smart-thermostat
- Updated all repository URLs and references
v2.0.0
- Weekly Schedule - Flexible time slots for each day of the week
- Boost Mode - Temporary temperature override with countdown timer
- Away Mode - Temperature limiting when not at home
- MQTT Discovery - Automatic climate entity creation in Home Assistant
- Operating Modes - Switch between manual, schedule, and off
- Enhanced Status - Node status shows boost timer, away mode, schedule info
- Preset Modes - Home Assistant presets: away, boost
- New input properties:
msg.schedule,msg.boost,msg.away,msg.operatingMode - Extended debug output with schedule/boost/away information
v1.4.1
- Added Home Assistant integration documentation (JSONata examples)
- Added Zigbee2MQTT / MQTT publishing examples
- Documentation improvements
v1.4.0
- Breaking: Changed mode names to Home Assistant HVAC format (
heat,cool,heat_cool) - Added thermostat precision setting (1°C, 0.5°C, 0.2°C, 0.1°C)
- Renamed
outputtosetpointin debug output for clarity - Setpoint now guarantees minimum step above/below target when actively regulating
- Setpoint equals target when stable (within hysteresis)
- Legacy mode names (
heating,cooling,auto) still accepted for backward compatibility
v1.3.0
- Added file-based persistent storage for learned parameters
- State survives Node-RED restarts
- Automatic migration from context storage
- Smart save: only when PID parameters actually change
v1.2.0
- Added cooling mode support
- Added auto mode (automatic heating/cooling switching)
- Added third output for active regulation status
- Added output format selection (boolean/number)
- Updated documentation
v1.1.0
- Added cooling mode
- Added mode selection in UI
v1.0.0
- Initial release with heating support
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request on GitHub.