This is a custom integration for Home Assistant to monitor and control Danfoss ECL310 district heating controllers via Modbus TCP. Using Application 247.1 (V01)
It supports reading temperatures, pump/valve statuses, and operating modes, as well as writing target temperatures and the heat curve back to the controller.
- Home Assistant 2026.9 or newer.
- The built-in Modbus integration (installed automatically as a dependency). It does not need any YAML configuration of its own.
The controller is always addressed on Modbus unit 254, which is fixed by Danfoss for application 247.1 and therefore is not asked for during setup.
1.2.2 corrects two register addresses and adds the remaining application 247.1 values. The full list is in CHANGELOG.md and in the release notes.
Fixed
Limit: Summer Cutoutwas reading11189; the controller reports it on11178. It never answered, which is why the log filled withsummer_cutoutin the "did not answer this cycle" warning.Temp: Flow Target (Calc)was reading11253; the calculated flow target is on11252, which is why it appeared in the same warning.- Three temperatures were decoded with the wrong scale (10-100x too large):
Temp: Flow Target (Calc)showed246.30instead of24.63, andTemp: Outdoor (Acc.)/Temp: Reference Placeshowed1619.0/2463.0instead of16.19/24.63. They are hundredths-of-a-degree registers and now usescale: 0.01.
Added
Temp: Outdoor (Acc.)(11099) andTemp: Reference Place(11500) on the Sensors device.- Pump protection and start temperatures as both a read-only sensor and a writable number:
Pump Frost Protection (
11076), Pump Start Temperature (11077) and Circ Pump Frost Protection (12075). The numbers live on Controls, their read-backs on Diagnostics.
Changed
Temp: Flow Target (Calc),Temp: Outdoor (Acc.)andTemp: Reference Placenow show two decimal places.- The brand images are now the Danfoss logo - the light and dark wordmark plus the Danfoss "D" as the integration icon - replacing the previous custom artwork.
Both corrected sensors keep their entity IDs - only the register behind them changed - so nothing has
to be re-mapped after upgrading. The three temperatures above are hundredths-of-a-degree registers,
so they use scale: 0.01 and are shown with two decimals.
1.2.0 replaces the integration's own Modbus socket with Home Assistant's built-in Modbus integration. That is what makes it work on 2026.9+, lets other Modbus clients share the controller, and removes the pymodbus version juggling of 1.1.x. The full list is in CHANGELOG.md and in the release notes.
โ ๏ธ Home Assistant 2026.9 or newer is required. On an older Home Assistant the integration will not load at all.
What changed since 1.1.9
| Shared connection | The controller is read through the Modbus integration (async_get_unit), so a second Modbus client on the same controller no longer competes for the one available session. |
| Five devices | Entities are grouped onto Controls, Sensors, Pumps, Configuration and Diagnostics (see below) instead of piling up on one page. |
| Batched polling | Neighbouring registers are read as one Modbus block per group; the three intervals (status 30 s, temperature 60 s, settings 600 s) are adjustable at runtime. |
| Identity on the device page | Serial number, firmware and hardware revision now reach the device registry - in 1.1.x the sensors holding them were registered as disabled and never ran. |
| Honest errors | A rejected write raises a visible error instead of failing silently, and a controller that stops answering marks its entities unavailable. |
| Reconfigure | The IP address can be changed from the integration's โฎ menu without deleting and re-adding the entry. |
| Dashboard included | A ready-to-use Lovelace dashboard ships in dashboards/ (see below). |
Upgrading from 1.1.x is safe. Entity IDs and unique IDs are stored the first time an entity is registered, so an existing install keeps exactly the IDs it has - only the device page an entity is filed under changes. Rolling back is equally safe: redownload 1.1.9 in HACS (or put the previous folder back) and restart Home Assistant.
- The entities are now grouped onto several devices โ Controls, Sensors, Pumps, Configuration and Diagnostics, all hanging off the controller device. Check that each one lands where you would expect.
- The controller's device page now shows a serial number, firmware and hardware revision. These never appeared in 1.1.9, because the sensors that held them were registered as disabled and so never ran.
- Temperatures, pump and valve states read the same as before, but they are now explicitly requested from unit 254 rather than relying on a Modbus keyword that recent versions of the underlying library had removed.
- Changing Set: Target Comfort / Set: Target Setback reaches the controller, and a rejected write now raises a visible error instead of failing silently.
- If you run another Modbus integration against the same controller, both should keep working โ they now share a single connection instead of competing for it.
- Reconfigure on the integration's โฎ menu lets you change the controller's IP without deleting and re-adding the entry.
Please report anything that looks wrong on the issue tracker, with debug logging enabled (see Troubleshooting).
- Shared Modbus connection: The integration asks Home Assistant's Modbus integration for a unit on your controller instead of opening its own socket, so it never competes with other Modbus integrations for the device.
- Multi-Device Support: Add multiple ECL310 controllers by IP address; they will appear as separate devices in Home Assistant.
- Setpoint Control: Adjust "Comfort" and "Setback" target temperatures and the heating curve directly from the Lovelace UI. Changes are written back to the controller, and the controller's own response is reported if a write fails.
- Localized: Fully translated into English, German, French, Italian, and Spanish.
- Robust Connection: Dropped links are re-established automatically, and a controller that stops answering marks its entities unavailable instead of silently reporting no value.
- Grouped Entities: Sensors are logically named and grouped (e.g., "Mode: Pump P1", "Temp: Outdoor (S1)") for easy sorting.
The integration reads the controller through the Home Assistant Modbus integration, which owns the TCP connection. Three data coordinators poll the device at different intervals, and each poll batches neighbouring registers into a single Modbus request to keep the traffic on the bus low:
- Status (30s): Pumps, valves, operating modes and the writable setpoints.
- Temperature (60s): Sensor readings and temperature limits.
- Settings (600s): Static configuration and system information.
Every interval is adjustable at runtime through the Interval: number entities.
Note: Setpoints are polled on the fastest interval. When you change a value in Home Assistant it is written to the Modbus register immediately and shown right away; the next poll confirms what the controller accepted.
The integration creates a device for the controller itself and a child device per group, so related entities are not all jumbled onto one page:
| Device | What is on it |
|---|---|
| ECL 310 (the controller) | The identity only โ manufacturer, model, serial number, firmware and hardware revision. No entities of its own. |
| Controls | The writable values: comfort and setback setpoints, heat curve slope, the six curve coordinates, and the pump protection and start temperatures. |
| Sensors | The live readings: temperatures, operating modes, valve travel, the accumulated outdoor and reference temperatures, and the return and summer limits. |
| Pumps | The three circulation pumps and their manual modes. |
| Configuration | The polling interval sliders. |
| Diagnostics | Limits, setpoint read-backs and system information โ hidden from the main view by their entity category. |
The group devices are child devices of the controller, so they nest underneath it rather than appearing as unrelated entries. Disabling the controller disables its children along with it.
Upgrading changes nothing about your entities. Entity IDs and unique IDs are stored the first time an entity is registered, so existing installs keep exactly the IDs they have. On a fresh install the group device's name becomes part of any newly generated entity ID โ for example number.controls_set_target_comfort โ because Home Assistant prefixes the device name for entities that use it.
The grouping itself lives in const.py: device_group_for_sensor() decides between Sensors, Pumps and Diagnostics, NUMBER_ENTITIES go to Controls and INTERVAL_ENTITIES to Configuration.
- Open HACS in Home Assistant.
- Go to "Integrations" > Top right menu > "Custom repositories".
- Enter the URL of this GitHub repository.
- Category: Integration.
- Click Add and then install "Danfoss ECL310".
- Restart Home Assistant.
- Download the
danfoss_ecl310folder from this repository. - Copy the folder into your Home Assistant
config/custom_components/directory. - The path should look like this:
/config/custom_components/danfoss_ecl310/__init__.py. - Restart Home Assistant.
- Go to Settings -> Devices & Services.
- Click Add Integration in the bottom right.
- Search for Danfoss ECL310.
- Enter the IP Address of your controller.
- Enter the Port (Default is
502). - Click Submit.
The connection is verified before the entry is created, so a wrong IP is reported as Failed to connect rather than creating a broken device.
To add a second device, simply repeat these steps with a different IP address. To point an existing entry at a new address, use the Reconfigure option on the integration's menu โ there is no need to delete and re-add it.
A ready-to-use Lovelace dashboard is included, built for the 2026.9 sections view:
- Standard view (
input_boolean.expert_mode = off): the live temperatures as tiles with bar gauges, the setpoint sliders, a storage gauge, the operating states and a 72 h history. - Expert view (
input_boolean.expert_mode = on): everything above plus the heating curve with the current operating point, all curve setpoints, the limits, valves/pumps and the maintenance/diagnostics section.
Standard view
Expert view - the three sections the view switch reveals
The dashboard uses built-in Home Assistant cards only - no card-mod, no extra theme, no custom
CSS - and every value it shows comes from registers this integration already reads. It needs no
template sensors, so a missing helper cannot leave you with empty cards: the single helper it
requires is the view switch. The only HACS requirement is
plotly-graph-card for the heating curve.
Two files are involved:
| File | What it is |
|---|---|
dashboards/ecl310-helpers.yaml |
The one helper the dashboard needs: input_boolean.expert_mode, the switch between the two views. No integration can create an input_boolean, and Lovelace cannot switch a view on something it cannot see. Install it as a package. |
dashboards/ecl310-dashboard.yaml |
The dashboard itself. |
โ Dashboard install guide (3 steps: match the entity ids, install dashboards/ecl310-helpers.yaml as a package, paste dashboards/ecl310-dashboard.yaml into a dashboard).
Want to look first? Open dashboards/preview/index.html in a browser - an offline preview with sample data, a working Standard/Expert switch and scenarios for the conditional warning cards.
This integration is designed to be easily extensible. All register mappings are defined in const.py. You do not need to touch the complex logic code to add a new sensor.
- Open
custom_components/danfoss_ecl310/const.py. - Locate the appropriate list based on how often you want the data to update:
SENSORS_STATUS: Status/Modes (default 30s).SENSORS_TEMPERATURE: Temperatures (default 60s).SENSORS_SETTINGS: Static settings and system info (default 600s).
- Add a new line to the list dictionary.
- Restart Home Assistant.
{
"key": "unique_internal_key", # Must be unique per device (e.g., "return_temp")
"name": "Displayed Name", # e.g., "Temp: Return"
"addr": 12345, # The Modbus Register Address
"type": "input", # "input" (Input Register) or "holding" (Holding Register)
"signed": True, # Optional: decode as a negative-capable int16. Default False.
"scale": 0.01, # Optional: multiplier (e.g. 0.01 turns 2350 into 23.50). Default 1.
"unit": UnitOfTemperature.CELSIUS, # Optional: Unit
"device_class": SensorDeviceClass.TEMPERATURE, # Optional: Device class
"icon": "mdi:thermometer", # Optional: Icon
"entity_category": EntityCategory.DIAGNOSTIC, # Optional: "Diagnostic"/"Config" grouping
"precision": 1, # Optional: suggested number of decimals
"trans_key": "simple_on_off" # Optional: Translation key for state mapping
}
signedmatters. Only registers that can genuinely hold a negative number should set it. Temperatures do; counters, status codes, serial numbers and firmware revisions do not, and marking one of those as signed turns e.g. a serial number of50000into-15536.
If you want to read a temperature from register 11200:
- Go to
SENSORS_TEMPERATUREinconst.py. - Add this line:
{"key": "my_new_temp", "name": "Temp: New", "addr": 11200, "type": "input", "signed": True, "scale": 0.01, "unit": UnitOfTemperature.CELSIUS, "device_class": SensorDeviceClass.TEMPERATURE},- Restart Home Assistant.
If you are reading a status register (0/1), you can use these keys in trans_key to make the UI show text instead of numbers:
operating_mode: 0 = Standby, 1 = Schedule, 2 = Comfort, 3 = Setback, 4 = Frost Protection, 5 = Manualsimple_on_off: 0 = Off, 1 = On (Localized)pump_mode: 0 = Auto, 1 = Off, 2 = Onvalve_manual_mode: 0 = Auto, 1 = Stop, 2 = Closing, 3 = Opening
If values are not appearing or the connection fails, enable debug logging to see exactly what is happening on the Modbus connection.
Add this to your configuration.yaml:
logger:
default: info
logs:
custom_components.danfoss_ecl310: debug
modbus_connection: debug
pymodbus: debugCommon Issues:
- "Failed to connect" during setup: Check that the IP is correct and that port 502 is reachable. Make sure no other Modbus client is holding the controller's single available session.
- Entities unavailable: The controller did not answer a poll โ usually because it is busy or restarting. The integration reconnects by itself; there is no need to reload the integration.
- The controller's entities sit on a different device than before: Since 1.2.0 they are grouped across the controller plus Controls, Sensors, Pumps, Configuration and Diagnostics. Entity IDs do not change, so automations keep working โ only the device page an entity is filed under differs.
- Values still show as "Unknown" after upgrading from 1.1.x: The System: sensors used to be registered as disabled. Entities that already exist keep the state they were created with, so enable them once under Settings โ Devices & Services โ Entities if you want to see them.
- Setting the comfort temperature has no effect at the top of the range: The setpoint range defaults to 10โ90 ยฐC. Verify the permitted range for your application in the controller and adjust
min/maxinconst.pyif it differs.
The integration is currently translated into:
- us English (Default)
- ๐ฉ๐ช German
- ๐ซ๐ท French
- ๐ฎ๐น Italian
- ๐ช๐ธ Spanish
The language is automatically selected based on your Home Assistant user profile settings.
| File | Responsibility |
|---|---|
__init__.py |
Entry setup, the three coordinators, and the controller and group devices. |
device.py |
Everything that knows the controller's registers. No Home Assistant imports, so it is testable against a mock bus. |
const.py |
The declarative register and entity map, including which device each entity belongs on. |
sensor.py / number.py |
The entities. |
config_flow.py |
Setup and reconfigure flows. |
brand/ |
The integration's own icon and logo. Home Assistant reads these directly, so no entry in the brands repository is needed. |
info.md |
The short summary HACS can show instead of this README. |
info.mdversus this README. HACS only rendersinfo.mdwhenrender_readmeisfalseinhacs.json. This repository currently setsrender_readme: true, so HACS shows this README andinfo.mdstays unused โ flip that flag tofalseif you would rather HACS show the shorter summary.


