A local evohome ↔ MQTT integration service
evoGateway provides a local, fully independent interface between Honeywell’s evohome RF network and any MQTT-based home automation system.
It receives and decodes RF traffic from evohome devices using the excellent ramses_rf library, publishes structured MQTT topics, and also accepts MQTT commands which are transmitted back onto the RF network as if they originated from an evohome Honeywell HGI80.
The gateway operates without relying on Honeywell’s cloud services, offering a robust and responsive integration path for platforms such as openHAB, Home Assistant, and any system capable of consuming or producing MQTT messages.
All RF communication, schema management, and MQTT publishing is performed locally, providing a reliable and transparent interface to the evohome system.
There has been extensive refactoring and reorganisation of evoGateway since the previous version 3.x. Users upgrading from earlier releases should be aware of several important changes:
The codebase is now organised into distinct functional components, moving away from the single large script file. This should make it easier to adapt to any future changes in the underlying ramses_rf library:
.
├── config/
│ ├── evogateway.cfg
│ ├── ramses_rf_schema.json
│
├── evogateway/
│ ├── app.py
│ ├── config.py
│ ├── logger.py
│ ├── router.py
│ ├── services.py
│ └── utils.py
│
├── logs/
│ ├── events.log
│ └── packet.log
│
├── README.md
└── evogateway.py
- The
ramses_rf_schema.jsonfile is now the master source for zones/devices. devices.jsonandzones.jsonhave been removed. Device names, zone names, and relationships are now maintained entirely in the schema file.
-
A more consistent and hierarchical MQTT layout is now used by default.
-
The previous flat/legacy layout is still available by enabling:
MQTT_LEGACY_TOPIC_STRUCTURE = True -
Device and zone categorisation defaults have changed slightly, especially around controllers, HGI devices, and DHW classification.
- Several older parameters have been renamed or removed.
- A complete sample configuration file is provided (
config/evogateway.cfg.sample).
- The old single-file architecture has been replaced with a module-based structure.
- Logging, MQTT handling, routing, serial handling, and formatting logic now live in dedicated modules.
While core functionality should hopefully remain consistent with previous versions, some behaviour will almost certainly differ slightly as compared to previous versions.
- Python 3.12.3
- Python dependencies can be installed via:
pip install -r requirements.txtIncludes:
ramses_rfpaho-mqttcolorama
NOTE The hardware can be purchased in component form from ebay/Ali Express etc or, fully assembled with a proper PCB, from ebay (search for nanoCUL FTDI 868MHz).
If assembling yourself, you will need:
-
1 x Arduino nano (clone should be fine), preferably with FTDI usb chipset, though the cheaper CH341 chipset also worked. The only issue I had with the CH341 was that the USB port was not always cleanly released when the python script exited. My FTDI based build is much more reliable in this respect.
-
1 x CC1101 radio, 868MHz, e.g. something like this. However, note that there has been some report of issues with the radio crystal's accuracy on some of these boards (discussion on evofw3 board).
-
A breadboard or 8 x Dupont fly leads. If using fly leads you need to ensure that sure that you have the correct male/female combination for your arduino and CC1101 card.
Wiring pin connections will depend on the specific CC1101 board. In my case, I used the following:
WIRE COLOUR CC1101 PIN NANO PIN
Red Vcc 3.3V pin
Black GND GND
Orange MOSI 15
Yellow SCLK 17
Blue MISO 16
Dark Red GDO2 32
Grey GDO0 1
White CSN 14
ANT Antenna coil
Although arduinos with the FTDI FT232L chipset are recommended, clone nanos with the CH341 usb chip have also worked reasonably. In my case the radio board is connected directly to the nano using just male/female dupont wires, with the male side soldered directly onto the radio board. Note that actual pin connection points should always follow the requirements of evofw3 (e.g. see the atm328_pins.h file), otherwise corresponding changes may need to be made in in this file before compiling and flashing the firmware.
evoGateway requires the evofw3 arduino firmware by @ghoti57, to decode/encode the radio signals and make the message packets available to/from the ramses_rf framework.
evoGateway uses two distinct configuration sources, each serving a different purpose:
-
The evoGateway configuration file (
evogateway.cfg)- Contains all runtime settings for the gateway itself (serial port, MQTT behaviour, logging, file paths, etc.).
-
The Ramses RF schema file (
ramses_rf_schema.json)- Stores the discovered evohome network structure (devices, zones, controller relationships, metadata, etc.).
- Automatically generated by evoGateway on first run, and can be modified by hand as required.
Both files live under the config/ directory.
(For those upgrading from an earlier version, the previous devices.json and zones.json files have been retired; the schema file is now the single authoritative source.)
This file defines the operational parameters for evoGateway. A sample configuration with all available parameters is provided in:
config/evogateway_sample.cfg
Users should copy this to evogateway.cfg and adjust as needed. The key parameters to include are the serial port settings and the mqtt broker details. The rest are optional and can be omitted.
Controls communication with the evofw3 Arduino interface:
COM_PORT(mandatory)COM_BAUD
The COM_PORTparameter can take any format that ramses_rf supports, such as:
- /dev/ttyUSB0
- /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
- mqtt://username:password@172.16.1.200:1883
Broker credentials, topic definitions, and publishing behaviour:
- MQTT server, username, password
- Topic roots and subtopics
- JSON-only vs. JSON+key/value publishing
- Zone grouping or device-flat layout
- Legacy topic compatibility mode
The ramses schema file is the authoritative record of the evohome system as 'discovered' by the ramses_rf framework by listening in on the RF network.
It contains structured data on:
- All devices observed on the network
- Device types and capabilities
- Zones and zone data
- Controller identity
- Temperature ranges, supported features, flow configuration
- A
known_listof devices that are permitted to operate via the ramses framework
-
On first run (or if there is no valid schema file, or if the
ENABLE_DISCOVERYconfig parameter is explicitly set toTrue), evoGateway begins in both discovery mode + eavesdrop mode, to build the schema incrementally from RF traffic. -
During discovery, the evolving schema is published regularly to MQTT.
-
Once sufficient information is collected (this may may take some time to build up, especially if there are any ufh controllers or DHW senders on the network), the schema can be saved either manually via an MQTT command or automatically at system shutdown. The default save location is
config/ramses_rf_schema.json. -
On subsequent runs, evoGateway loads this file if available, avoiding repeated discovery (which can be very resource intensive).
-
Note that if evoGateway is started with a valid schema file, it will NOT normally re-save the current schema at system shutdown, in order to avoid overwriting any custom modifications in the startup schema. This behaviour can be changed by setting the configuration parameter
ALWAYS_SAVE_SCHEMA_ON_SHUTDOWNtoTrue.
Two administrative commands (sent via the MQTT command topic) control schema publishing and persistence:
| Command | Description |
|---|---|
POST_SCHEMA |
Publishes the current schema over MQTT to the _gateway_config subtree. |
SAVE_SCHEMA |
Publishes the schema and writes it back to the JSON file on disk. |
Although it is not normally necessary to modify this file manually, initially it may be easier to add device aliases directly in this file, and/or amend the known_list, as these do not seem to always come through from the automatic discovery.
The schema file can subsequently be regenerated from scratch at any time by either:
- Deleting the current schema and then starting evoGateway
- Setting the
ENABLE_DISCOVERYtoTruein theRamses_rfsection of the evoGateway config file.
Once installed and configured, evoGateway:
- Loads the existing
ramses_rfschema - Opens the serial connection to the hardware
- Starts listening to all evohome RF traffic
- Publishes decoded messages to MQTT
- Subscribes to a command topic and transmits valid commands onto the RF network
- Maintains logs of all events and packets
- When operating in eavesdropping or discovery mode, the current schema is saved on clean shutdown.
- The schema can also be manually posted to MQTT or saved to file at any time via system MQTT commands.
evoGateway's MQTT topic structure has been slightly changed from earlier versions, to provide a slightly cleaner and more consistent hierarchy. The key changes are:
- Devices that previously appeared under
"_zone_independent"now appear under a dedicatedsystem/topic. - All zones are now grouped under a single
zones/root topic. - Hot water (DHW) is treated as just another zone under
zones. - Device categories such as controllers, HGI, relays, UFH, OTB etc remain available and behave largely as before, but are now placed cleanly within the new hierarchy.
Users upgrading from older evoGateway installations who prefer the original topic layout for continuity can try the configuration setting MQTT_LEGACY_TOPIC_STRUCTURE = True. In this mode:
- The old
"_zone_independent"root is restored. - Zones appear directly under the root topic. No structural
zones/grouping is applied. - Controllers and HGIs are not separated into their own buckets.
evoGateway listens for commands on a designated MQTT topic and sends them onto the RF network (via the ramses library) as if they originated from a Honeywell HGI80.
The default command topic is evohome/evogateway/system/_command but this can be changed as required in the config file:
[MQTT]
MQTT_CMD_TOPIC = evohome/evogateway/system/_commandCommands are sent as JSON objects to the command topic.
There are two supported styles:
- High-level “command” calls (preferred) – map directly onto
ramses_rfcommand constructors. - Low-level “code” packets – send raw RAMSES frames (for advanced use only).
Both use a single JSON document, for example:
{"command": "set_zone_setpoint", "zone_idx": "01", "setpoint": 21.0}or
{"code": "2309", "verb": "W", "payload": "00072100"}The preferred approach is to use the high-level command interface, which wraps the ramses_rf Command constructors. These can be found in the ramses_rf repo.
{
"command": "<method_name>",
"<keyword_1>": "<value>",
"<keyword_2>": "<value>",
...
}-
commandName of theramses_rfcommand method (e.g.set_zone_setpoint,get_system_time,set_dhw_mode, etc.) -
Additional keys Passed through as keyword arguments to that method (e.g.
zone_idx,setpoint,until,ctl_id).
If ctl_id (controller ID) is omitted, evoGateway will attempt to automatically insert the ID of the controller discovered from the Ramses schema.
a) Get the system time
{"command": "get_system_time"}b) Set a zone setpoint
Set Zone 01 to 21.0 °C indefinitely:
{"command": "set_zone_setpoint", "zone_idx": "01", "setpoint": 21.0}c) Set DHW mode until a given time
{
"command": "set_dhw_mode",
"active": true,
"until": "2025-05-31T17:40:00"
}Date/time values should be in ISO-8601 format (YYYY-MM-DDTHH:MM:SS).
Note: The available command names and their parameters are defined by ramses_rf within its Command class. The gateway does not add or remove arguments; it simply passes them through as given.
As of November 2025, the following constructor methods appear to be available, along with their respective list of keyword arguments (note that not all the arguments are mandatory):
Click to expand list of commands
Method |Verb| Code | Arguments
------------------------| ---| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
put_weather_temp | I | 0002 | dev_id, temperature
get_zone_name | RQ | 0004 | ctl_id, zone_idx
set_zone_name | W | 0004 | ctl_id, zone_idx, name
get_schedule_version | RQ | 0006 | ctl_id
get_relay_demand | RQ | 0008 | dev_id, zone_idx|None
get_zone_config | RQ | 000A | ctl_id, zone_idx
set_zone_config | W | 000A | ctl_id, zone_idx, min_temp = 5, max_temp = 35, local_override = False, openwindow_function = False, multiroom_mode = False
get_system_language | RQ | 0100 | ctl_id
get_schedule_fragment | RQ | 0404 | ctl_id, zone_idx, frag_number, total_frags|None
set_schedule_fragment | W | 0404 | ctl_id, zone_idx, frag_num, frag_cnt, fragment
get_system_log_entry | RQ | 0418 | ctl_id, log_idx
get_mix_valve_params | RQ | 1030 | ctl_id, zone_idx
set_mix_valve_params | W | 1030 | ctl_id, zone_idx, max_flow_setpoint, min_flow_setpoint, valve_run_time, pump_run_time
get_dhw_params | RQ | 10A0 | ctl_id
set_dhw_params | W | 10A0 | ctl_id, setpoint|None, overrun|None, differential|None
get_tpi_params | RQ | 1100 | dev_id, domain_id|None
set_tpi_params | W | 1100 | ctl_id, domain_id|None, cycle_rate = 3, min_on_time = 5, min_off_time = 5, proportional_band_width|None
get_dhw_temp | RQ | 1260 | ctl_id
put_dhw_temp | I | 1260 | dev_id, temperature|None
put_outdoor_temp | I | 1290 | dev_id, temperature|None
put_co2_level | I | 1298 | dev_id, co2_level|None
put_indoor_humidity | I | 12A0 | dev_id, indoor_humidity|None
get_zone_window_state | RQ | 12B0 | ctl_id, zone_idx
get_dhw_mode | RQ | 1F41 | ctl_id
set_dhw_mode | W | 1F41 | ctl_id, mode|None, active|None, until|None, duration|None
put_bind | I/W| 1FC9 | verb: VerbT, src_id, codes: Code|Iterable[Code]|None, dst_id|None
set_fan_mode | I | 22F1 | fan_id, fan_mode|None, seqn|None, src_id|None, idx = "00"
set_bypass_position | I/W| 22F7 | fan_id, bypass_position|None, src_id|None, **kwargs (bypass_mode: 'auto'|'on'|'off' optional)
get_zone_setpoint | W | 2309 | ctl_id, zone_idx
set_zone_setpoint | W | 2309 | ctl_id, zone_idx, setpoint
get_zone_mode | RQ | 2349 | ctl_id, zone_idx
set_zone_mode | W | 2349 | ctl_id, zone_idx, mode|None, setpoint|None, until|None, duration|None
set_fan_param | W | 2411 | fan_id, param_id, value|int|float|bool, src_id|None
get_fan_param | RQ | 2411 | fan_id, param_id, src_id
get_system_mode | RQ | 2E04 | ctl_id
set_system_mode | W | 2E04 | ctl_id, system_mode|None, until|None
put_presence_detected | I | 2E10 | dev_id, presence_detected|None
get_zone_temp | RQ | 30C9 | ctl_id, zone_idx
put_sensor_temp | I | 30C9 | dev_id, temperature|None
get_system_time | RQ | 313F | ctl_id
set_system_time | W | 313F | ctl_id, datetime, is_dst = False
get_hvac_fan_31da | I | 31DA | dev_id, hvac_id, bypass_position|None, air_quality|None, co2_level|None, indoor_humidity|None, outdoor_humidity|None, exhaust_temp|None, supply_temp|None, indoor_temp|None, outdoor_temp|None, speed_capabilities: list[str], fan_info, _unknown_fan_info_flags: list[int], exhaust_fan_speed|None, supply_fan_speed|None, remaining_mins|None, post_heat|None, pre_heat|None, supply_flow|None, exhaust_flow|None, **kwargs (air_quality_basis, _extra)
get_opentherm_data | RQ | 3220 | otb_id, msg_id
put_actuator_state | I | 3EF0 | dev_id, modulation_level|None
put_actuator_cycle | RP | 3EF1 | src_id, dst_id, modulation_level, actuator_countdown, cycle_countdown|None
For advanced scenarios or when experimenting with undocumented features, you can send raw RAMSES frames.
{
"code": "XXXX",
"verb": "RQ|RP|W|I",
"payload": "AABBCC...",
"dest_id": "01:234567" // optional
}code– 4-character RAMSES message code (e.g.0418,2309).verb– RAMSES verb (RQ,RP,W,I, etc.).payload– hex string payload (exactly as required by that code).dest_id– optional destination device ID. If omitted, the controller is usually assumed.
Example – get first system log entry using raw code:
{"code": "0418", "verb": "RQ", "payload": "000000"}Use this mode only if you are familiar with the RAMSES protocol; the gateway performs minimal validation.
evoGateway exposes a simple schedule interface over the same command topic for complete local control of the evohome schedule, via the following commands:
get_scheduleset_schedule
{
"command": "get_schedule",
"zone_idx": "01",
"force_refresh": true
}zone_idx– required; the zone index as a hexadecimal string (e.g."01","0A","HW").force_refresh– optional; iftrue, retrieves a fresh schedule from the controller.
Schedules are returned via normal RF messages and published to MQTT as zone_schedule payloads under the relevant zone/device topics.
{
"command": "set_schedule",
"zone_idx": "01",
"schedule": { ... }
}schedulemust match the structure expected byramses_rffor zone schedules. Initially downloading a schedule via theget_schedulecommand provides an easy template to work from.- Schedules should be sent as complete schedules for the whole work. Uploading a schedule will overwrite the whole of the existing schedule stored in the evohome controller
If required parameters (e.g. zone_idx or schedule) are missing, the gateway will log an error and ignore the command.
Each command sent via MQTT is tracked and a status is published to the _last_command subtopic beneath the command root.
By default:
-
Commands are sent to:
<MQTT_CMD_TOPIC>/_commands -
Status is published under:
<MQTT_CMD_TOPIC>/_last_command/status
The status payload typically includes:
- Whether the command was transmitted onto the RF network.
- Whether an expected acknowledgement / response was successfully received.
- Any relevant error or timeout information.
This allows for automations to:
- Wait for confirmation that a command was accepted.
- Detect failed or timed-out operations.
- etc.
In addition to “normal” ramses_rf commands, evoGateway also accepts a few gateway maintenance commands via the same MQTT command topic.
These use a sys_config key instead of command:
{"sys_config": "POST_SCHEMA"}Currently supported (subject to change as the gateway evolves):
-
POST_SCHEMAPublishes the current schema, parameters, and status fromramses_rfto the_gateway_configsubtree under the system topic. -
SAVE_SCHEMAAs above, and additionally saves the current schema back to theramses_rf_schema.jsonfile.
These operations are of course local to evoGateway and are not forwarded on the RF network.
evoGateway builds upon two major open-source contributions:
-
ramses_rfby David Bonnes (@zxdavb) https://github.com/zxdavb/ramses_rf -
evofw3by Peter Price (@ghoti57) https://github.com/ghoti57/evofw3
Their work provides the foundation for all decoding, encoding, and RF communication.

