Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions homeassistant/components/opentherm_gw/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {
"error": {
"already_configured": "Gateway already configured",
"id_exists": "Gateway id already exists",
"serial_error": "Error connecting to device",
"timeout": "Connection attempt timed out"
},
"step": {
"init": {
"data": {
"device": "Path or URL",
"floor_temperature": "Floor climate temperature",
"id": "ID",
"name": "Name",
"precision": "Climate temperature precision"
},
"title": "OpenTherm Gateway"
}
},
"title": "OpenTherm Gateway"
}
}
23 changes: 23 additions & 0 deletions homeassistant/components/opentherm_gw/.translations/nl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't add other translations in the config flow PR. Just the default English translation.

Translations are handled separately via Lokalise and synced regularly to github.

https://developers.home-assistant.io/docs/en/internationalization_backend_localization.html#translation-strings

"error": {
"already_configured": "Gateway is reeds geconfigureerd",
"id_exists": "Gateway id bestaat reeds",
"serial_error": "Kan niet verbinden met de Gateway",
"timeout": "Time-out van de verbinding"
},
"step": {
"init": {
"data": {
"device": "Pad of URL",
"floor_temperature": "Thermostaat temperaturen naar beneden afronden",
"id": "ID",
"name": "Naam",
"precision": "Thermostaat temperatuur precisie"
},
"title": "OpenTherm Gateway"
}
},
"title": "OpenTherm Gateway"
}
}
43 changes: 29 additions & 14 deletions homeassistant/components/opentherm_gw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pyotgw.vars as gw_vars
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.components.binary_sensor import DOMAIN as COMP_BINARY_SENSOR
from homeassistant.components.climate import DOMAIN as COMP_CLIMATE
from homeassistant.components.sensor import DOMAIN as COMP_SENSOR
Expand All @@ -16,13 +17,13 @@
ATTR_TEMPERATURE,
ATTR_TIME,
CONF_DEVICE,
CONF_ID,
CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
PRECISION_HALVES,
PRECISION_TENTHS,
PRECISION_WHOLE,
)
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send

import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -75,28 +76,42 @@
)


async def async_setup(hass, config):
async def async_setup_entry(hass, config_entry):
"""Set up the OpenTherm Gateway component."""
conf = config[DOMAIN]
hass.data[DATA_OPENTHERM_GW] = {DATA_GATEWAYS: {}}
for gw_id, cfg in conf.items():
if DATA_OPENTHERM_GW not in hass.data:
hass.data[DATA_OPENTHERM_GW] = {DATA_GATEWAYS: {}}

for gw_id, cfg in config_entry.data.items():
gateway = OpenThermGatewayDevice(hass, gw_id, cfg)
hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id] = gateway
hass.async_create_task(
async_load_platform(hass, COMP_CLIMATE, DOMAIN, gw_id, config)
)
hass.async_create_task(
async_load_platform(hass, COMP_BINARY_SENSOR, DOMAIN, gw_id, config)
)
hass.async_create_task(
async_load_platform(hass, COMP_SENSOR, DOMAIN, gw_id, config)
)

# Schedule directly on the loop to avoid blocking HA startup.
hass.loop.create_task(gateway.connect_and_subscribe(cfg[CONF_DEVICE]))

for comp in [COMP_BINARY_SENSOR, COMP_CLIMATE, COMP_SENSOR]:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, comp)
)

register_services(hass)
return True


async def async_setup(hass, config):
"""Set up the OpenTherm Gateway component."""
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
conf = config[DOMAIN]
for device_id, device_config in conf.items():
device_config[CONF_ID] = device_id

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=device_config
)
)
return True


def register_services(hass):
"""Register services for the component."""
service_reset_schema = vol.Schema(
Expand Down
19 changes: 9 additions & 10 deletions homeassistant/components/opentherm_gw/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the OpenTherm Gateway binary sensors."""
if discovery_info is None:
return
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
sensors = []
for var, info in BINARY_SENSOR_INFO.items():
device_class = info[0]
friendly_name_format = info[1]
sensors.append(
OpenThermBinarySensor(gw_dev, var, device_class, friendly_name_format)
)
for gw_id in config_entry.data:
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id]
for var, info in BINARY_SENSOR_INFO.items():
device_class = info[0]
friendly_name_format = info[1]
sensors.append(
OpenThermBinarySensor(gw_dev, var, device_class, friendly_name_format)
)
async_add_entities(sensors)


Expand Down
14 changes: 8 additions & 6 deletions homeassistant/components/opentherm_gw/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the opentherm_gw device."""
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up an OpenTherm Gateway climate entity."""
ents = []
for gw_id in config_entry.data:
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id]

gateway = OpenThermClimate(gw_dev)
async_add_entities([gateway])
ents.append(OpenThermClimate(gw_dev))
async_add_entities(ents)


class OpenThermClimate(ClimateDevice):
Expand All @@ -62,7 +64,7 @@ def __init__(self, gw_dev):

async def async_added_to_hass(self):
"""Connect to the OpenTherm Gateway device."""
_LOGGER.debug("Added device %s", self.friendly_name)
_LOGGER.debug("Added OpenTherm Gateway climate device %s", self.friendly_name)
async_dispatcher_connect(
self.hass, self._gateway.update_signal, self.receive_report
)
Expand Down
124 changes: 124 additions & 0 deletions homeassistant/components/opentherm_gw/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""OpenTherm Gateway config flow."""
import asyncio
from serial import SerialException

import pyotgw
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.const import (
CONF_DEVICE,
CONF_ID,
CONF_NAME,
PRECISION_HALVES,
PRECISION_TENTHS,
PRECISION_WHOLE,
)

import homeassistant.helpers.config_validation as cv

from . import DOMAIN
from .const import CONF_FLOOR_TEMP, CONF_PRECISION


class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""OpenTherm Gateway Config Flow."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH

async def async_step_init(self, info=None):
"""Handle config flow initiation."""
if info:
name = info[CONF_NAME]
device = info[CONF_DEVICE]
gw_id = info.get(CONF_ID, cv.slugify(name))
precision = info.get(CONF_PRECISION)
floor_temp = info[CONF_FLOOR_TEMP]

entries = {
k: v
for e in self.hass.config_entries.async_entries(DOMAIN)
for k, v in e.data.items()
}

if gw_id in entries:
return self._show_form({"base": "id_exists"})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use the CONF_ID key instead of "base" the form will show the error message next to the corresponding form item.


if device in [e[CONF_DEVICE] for e in entries.values()]:
return self._show_form({"base": "already_configured"})

async def test_connection():
"""Try to connect to the OpenTherm Gateway."""
otgw = pyotgw.pyotgw()
status = await otgw.connect(self.hass.loop, device)
await otgw.disconnect()
return status.get(pyotgw.OTGW_ABOUT)

try:
res = await asyncio.wait_for(test_connection(), timeout=10)
except asyncio.TimeoutError:
return self._show_form({"base": "timeout"})
except SerialException:
return self._show_form({"base": "serial_error"})

if res:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this not true?

return self._create_entry(gw_id, name, device, precision, floor_temp)

return self._show_form()

async def async_step_user(self, info=None):
"""Handle manual initiation of the config flow."""
return await self.async_step_init(info)

async def async_step_import(self, import_config):
"""
Import an OpenTherm Gateway device as a config entry.

This flow is triggered by `async_setup` for configured devices.
"""
climate_config = import_config.get(CLIMATE_DOMAIN, {})
formatted_config = {
CONF_NAME: import_config.get(CONF_NAME, import_config[CONF_ID]),
CONF_DEVICE: import_config[CONF_DEVICE],
CONF_ID: import_config[CONF_ID],
CONF_PRECISION: climate_config.get(CONF_PRECISION),
CONF_FLOOR_TEMP: climate_config.get(CONF_FLOOR_TEMP, False),
}
return await self.async_step_init(info=formatted_config)

def _show_form(self, errors=None):
"""Show the config flow form with possible errors."""
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(CONF_NAME): str,
vol.Required(CONF_DEVICE): str,
vol.Optional(CONF_ID): str,
vol.Optional(CONF_PRECISION): vol.All(
Comment thread
mvn23 marked this conversation as resolved.
Outdated
vol.Coerce(float),
vol.In([PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
),
vol.Optional(CONF_FLOOR_TEMP, default=False): bool,
Comment thread
mvn23 marked this conversation as resolved.
Outdated
}
),
errors=errors,
)

def _create_entry(self, gw_id, name, device, precision, floor_temp):
"""Create entry for the OpenTherm Gateway device."""
return self.async_create_entry(
title="OpenTherm Gateway",
data={
gw_id: {
CONF_DEVICE: device,
CONF_NAME: name,
CLIMATE_DOMAIN: {
CONF_PRECISION: precision,
CONF_FLOOR_TEMP: floor_temp,
},
}
},
)
5 changes: 3 additions & 2 deletions homeassistant/components/opentherm_gw/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"name": "Opentherm Gateway",
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
"requirements": [
"pyotgw==0.4b4"
"pyotgw==0.5b0"
],
"dependencies": [],
"codeowners": [
"@mvn23"
]
],
"config_flow": true
}
21 changes: 10 additions & 11 deletions homeassistant/components/opentherm_gw/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the OpenTherm Gateway sensors."""
if discovery_info is None:
return
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][discovery_info]
sensors = []
for var, info in SENSOR_INFO.items():
device_class = info[0]
unit = info[1]
friendly_name_format = info[2]
sensors.append(
OpenThermSensor(gw_dev, var, device_class, unit, friendly_name_format)
)
for gw_id in config_entry.data:
gw_dev = hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][gw_id]
for var, info in SENSOR_INFO.items():
device_class = info[0]
unit = info[1]
friendly_name_format = info[2]
sensors.append(
OpenThermSensor(gw_dev, var, device_class, unit, friendly_name_format)
)
async_add_entities(sensors)


Expand Down
23 changes: 23 additions & 0 deletions homeassistant/components/opentherm_gw/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {
"title": "OpenTherm Gateway",
"step": {
"init": {
"title": "OpenTherm Gateway",
"data": {
"name": "Name",
"device": "Path or URL",
"id": "ID",
"precision": "Climate temperature precision",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key doesn't seem used.

"floor_temperature": "Floor climate temperature"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

}
}
},
"error": {
"already_configured": "Gateway already configured",
"id_exists": "Gateway id already exists",
"serial_error": "Error connecting to device",
"timeout": "Connection attempt timed out"
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"mqtt",
"nest",
"notion",
"opentherm_gw",
"openuv",
"owntracks",
"plaato",
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,7 @@ pyoppleio==1.0.5
pyota==2.0.5

# homeassistant.components.opentherm_gw
pyotgw==0.4b4
pyotgw==0.5b0

# homeassistant.auth.mfa_modules.notify
# homeassistant.auth.mfa_modules.totp
Expand Down