A REST API for managing multi-zone heating systems with Home Assistant integration. Control your Zigbee thermostats, define heating schedules, and manage different heating modes—all through a simple API or Home Assistant automations.
- 🏠 Multi-zone control - Manage heating independently for each room/area
- 📅 Smart scheduling - Define custom schedules with workday/weekend/holiday variations
- 🎛️ Mode switching - Quick preset modes: default, stay_home, eco, timer, manual, off, ventilation
- 🏡 Home Assistant integration - Automatic area discovery, real-time synchronization, MQTT-based control
- 🌡️ TRV support - Direct control of Zigbee2MQTT radiator thermostats
- 📊 REST API - Full OpenAPI documentation at
/docs
- Docker and Docker Compose
- Home Assistant instance with WebSocket access
- Zigbee2MQTT (to control TRV devices)
- Climate entities configured in Home Assistant
git clone https://github.com/kaninfod/heating_controller.git
cd heating_controller
# Create configuration
cp .env.example .env
# Edit .env with your values
nano .env
# Start
docker-compose up -dThe API will be available at http://localhost:8321.
Add a REST command to call the controller API. In your configuration.yaml:
rest_command:
heating_set_mode:
url: "http://192.168.68.102:8321/api/modes/set"
method: post
content_type: "application/json"
headers: {"accept": "application/json"}
payload: '{"mode": "{{ mode }}", "active_areas": ["{{ rooms }}"], "ventilation_time": {{ ventilation_time }}}'
input_select:
heating_mode:
name: Heating Mode
options:
- default
- stay_home
- eco
- timer
- manual
- off
- ventilation
initial: defaultThis allows you to call the heating controller with fine-grained parameters like specific areas and ventilation duration.
Edit config/thermostat_mapping.json to match your setup:
{
"climate.bedroom_thermostat": "bedroom thermostat",
"climate.kitchen_thermostat": "kitchen thermostat",
"climate.living_room_thermostat": "living room thermostat"
}The controller automatically discovers areas from Home Assistant.
This is the primary way to control heating from Home Assistant:
curl -X POST http://localhost:8321/api/modes/set \
-H "Content-Type: application/json" \
-d '{
"mode": "stay_home",
"active_areas": ["bedroom", "living_room"],
"ventilation_time": 10
}'Parameters:
mode(required): One ofdefault,stay_home,eco,timer,manual,off,ventilationactive_areas(optional): List of specific areas to heat (empty list = all areas)ventilation_time(optional): Minutes to run ventilation mode (only forventilationmode)
Examples:
Normal heating (all areas):
curl -X POST http://localhost:8321/api/modes/set \
-H "Content-Type: application/json" \
-d '{"mode": "default", "active_areas": [], "ventilation_time": 0}'Stay home in bedroom only:
curl -X POST http://localhost:8321/api/modes/set \
-H "Content-Type: application/json" \
-d '{"mode": "stay_home", "active_areas": ["bedroom"], "ventilation_time": 0}'Ventilate for 15 minutes:
curl -X POST http://localhost:8321/api/modes/set \
-H "Content-Type: application/json" \
-d '{"mode": "ventilation", "active_areas": [], "ventilation_time": 15}'Eco mode (away):
curl -X POST http://localhost:8321/api/modes/set \
-H "Content-Type: application/json" \
-d '{"mode": "eco", "active_areas": [], "ventilation_time": 0}'curl http://localhost:8321/api/statusFor simple mode changes without parameters:
curl -X POST http://localhost:8321/api/modes/defaultAvailable modes:
default- Normal schedulestay_home- Generate optimized schedule for staying homeeco- Energy-saving modetimer- Temporary timer modemanual- Manual control without scheduleoff- Turn off heatingventilation- Ventilation only
curl http://localhost:8321/api/areascurl http://localhost:8321/api/schedulescurl http://localhost:8321/api/schedules/defaultcurl -X POST http://localhost:8321/api/areas/bedroom/temperature \
-H "Content-Type: application/json" \
-d '{"temperature": 22}'Open http://localhost:8321/docs in your browser to explore all endpoints with a UI.
# Home Assistant WebSocket connection
HA_WEBSOCKET_URL=wss://your-ha-instance/api/websocket
HA_ACCESS_TOKEN=eyJ0eXA... # Long-lived access token from HA profile
# Heating mode input_select entity
MODE_ENTITY=input_select.heating_mode
# Areas to exclude from control (comma-separated)
# Leave empty to control all discovered areas
BLACKLISTED_AREAS=
# Logging
LOG_LEVEL=DEBUG
SYSLOG_HOST=192.168.1.100
SYSLOG_PORT=514File: config/thermostat_mapping.json
Maps Home Assistant climate entities to Zigbee2MQTT device names:
{
"climate.bedroom_thermostat": "bedroom thermostat",
"climate.kitchen_thermostat": "kitchen thermostat"
}File: config/day_types.json
Defines temperature curves for different day types:
{
"workday": "09:00/21 12:00/18 17:00/21 22:00/17 23:00/16",
"weekend": "09:00/20 12:00/18 17:00/20 22:00/17 23:00/16"
}Format: HH:MM/TEMPERATURE separated by spaces
Directory: config/schedules/
Contains JSON files defining weekly schedules:
{
"monday": "workday",
"tuesday": "workday",
"wednesday": "workday",
"thursday": "workday",
"friday": "workday",
"saturday": "weekend",
"sunday": "weekend"
}The recommended way to control the heating from Home Assistant is via the REST command defined in configuration.yaml:
rest_command:
heating_set_mode:
url: "http://192.168.68.102:8321/api/modes/set"
method: post
content_type: "application/json"
headers: {"accept": "application/json"}
payload: '{"mode": "{{ mode }}", "active_areas": ["{{ rooms }}"], "ventilation_time": {{ ventilation_time }}}'This allows automations to call the heating controller with specific parameters.
The controller automatically discovers areas from Home Assistant. Each area with climate entities is available for control via the active_areas parameter.
Switch to eco mode when everyone leaves:
automation:
- alias: "Heating: Away Mode"
trigger:
platform: state
entity_id: group.all_people
to: "not_home"
action:
- service: rest_command.heating_set_mode
data:
mode: eco
rooms: ""
ventilation_time: 0Stay home mode with specific bedroom:
automation:
- alias: "Heating: Stay Home - Bedroom"
trigger:
platform: state
entity_id: input_select.heating_mode_detail
to: "Stay home (Bedroom)"
action:
- service: rest_command.heating_set_mode
data:
mode: stay_home
rooms: "bedroom"
ventilation_time: 0Ventilate for 10 minutes:
automation:
- alias: "Heating: Ventilate"
trigger:
platform: state
entity_id: input_select.heating_mode_detail
to: "Ventilate"
action:
- variables:
vent_time: 10
- service: rest_command.heating_set_mode
data:
mode: ventilation
rooms: ""
ventilation_time: "{{ vent_time }}"
- if:
- condition: template
value_template: "{{ response['status'] == 200 }}"
then:
- service: timer.start
target:
entity_id: timer.ventilation_timer
data:
duration: "00:{{ '{:02d}'.format(vent_time | int) }}:00"When you change the heating mode via the API, it updates the input_select.heating_mode entity in Home Assistant for synchronization with other automations or dashboards.
Schedules are published to Zigbee2MQTT via MQTT topics:
zigbee2mqtt/{device_name}/set
The controller handles publishing schedule updates automatically when switching modes.
-
Verify Home Assistant connection:
- Check
.envhas correctHA_WEBSOCKET_URLandHA_ACCESS_TOKEN - Ensure access token is a long-lived token from HA profile
- Verify network connectivity to Home Assistant
- Check
-
Check area configuration:
- Navigate to Settings → Areas in Home Assistant
- Create areas and assign devices to them
- At least one area must have a climate entity
-
View logs:
docker-compose logs app | grep -i area
-
Verify thermostat mapping:
cat config/thermostat_mapping.json
- Climate entity IDs must match Home Assistant exactly
- Z2M device names must match Zigbee2MQTT device names
-
Check MQTT connectivity:
docker-compose logs app | grep -i mqtt- Zigbee2MQTT must be running and accessible
-
Verify Zigbee pairing:
- Device must be paired in Zigbee2MQTT
- Device name must be exactly as specified in mapping
The controller caches schedules in memory. This is normal. To reset:
docker-compose restart app-
Check logs:
docker-compose logs app
-
Verify Home Assistant is reachable:
curl $HA_WEBSOCKET_URL -
Restart the service:
docker-compose restart app
To prevent specific areas from being controlled:
BLACKLISTED_AREAS=garage,storage,officeThe controller will skip these areas during discovery.
Set log level in .env:
LOG_LEVEL=DEBUG # Verbose logging
LOG_LEVEL=INFO # Standard logging
LOG_LEVEL=WARNING # Errors onlySend logs to syslog server:
SYSLOG_HOST=192.168.1.100
SYSLOG_PORT=514View logs locally:
docker-compose logs -f app[Add license information]
Contributions are welcome. Please open an issue or pull request on GitHub.
For issues or questions, please open an issue on the GitHub repository.