Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Komfovent #95722

Merged
merged 38 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e09f7ed
komfovent integration V1
ProstoSanja Jul 1, 2023
3127804
add dependency
ProstoSanja Jul 2, 2023
3e397ba
integrate komfovent api
ProstoSanja Jul 2, 2023
4d15867
fix errors found in testing
ProstoSanja Jul 2, 2023
1acebab
tests for form handling
ProstoSanja Jul 2, 2023
d5fc378
update deps
ProstoSanja Jul 2, 2023
357108a
update coverage rc
ProstoSanja Jul 2, 2023
7935703
Merge branch 'dev' into komfovent
ProstoSanja Jul 2, 2023
ff6b9a0
add correct naming
ProstoSanja Jul 2, 2023
3583155
Merge branch 'dev' into komfovent
ProstoSanja Jul 3, 2023
0a417f0
Merge branch 'dev' into komfovent
ProstoSanja Jul 4, 2023
60c8d2d
Merge branch 'dev' into komfovent
ProstoSanja Jul 5, 2023
1622573
Merge branch 'dev' into komfovent
ProstoSanja Jul 6, 2023
ecb3b2e
minor feedback
ProstoSanja Jul 6, 2023
bddfcc7
Merge branch 'dev' into komfovent
ProstoSanja Jul 10, 2023
ac50f2e
Merge branch 'dev' into komfovent
ProstoSanja Jul 13, 2023
4161c5a
Merge branch 'dev' into komfovent
ProstoSanja Jul 18, 2023
bef4d53
pre-commit fixes
ProstoSanja Jul 24, 2023
6f2ea82
feedback fixes part 1 of 2
ProstoSanja Jul 25, 2023
1865e6b
feedback fixes part 2 of 2
ProstoSanja Jul 25, 2023
c0d1a6d
Merge branch 'dev' into komfovent
ProstoSanja Jul 25, 2023
1d8e5c8
Merge branch 'dev' into komfovent
ProstoSanja Sep 9, 2023
5705260
add hvac mode support
ProstoSanja Sep 9, 2023
ad8b412
fix tests
ProstoSanja Sep 9, 2023
19105ab
Merge branch 'dev' into komfovent
ProstoSanja Sep 9, 2023
c95ff33
address feedback
ProstoSanja Sep 9, 2023
dafd55e
Merge branch 'dev' into komfovent
ProstoSanja Oct 1, 2023
b832a02
fix code coverage + PR feedback
ProstoSanja Oct 7, 2023
2e639cd
Merge branch 'dev' into komfovent
ProstoSanja Oct 7, 2023
4cce280
Merge branch 'dev' into komfovent
ProstoSanja Oct 8, 2023
e3af18f
Merge branch 'dev' into komfovent
ProstoSanja Oct 16, 2023
bb25ea9
Merge branch 'dev' into komfovent
ProstoSanja Oct 19, 2023
cdd41f3
Merge branch 'dev' into komfovent
ProstoSanja Oct 23, 2023
808794a
Merge branch 'dev' into komfovent
ProstoSanja Oct 28, 2023
33ea3a1
PR feedback
ProstoSanja Oct 31, 2023
0325bc1
use device name
ProstoSanja Nov 2, 2023
58f44c2
Merge branch 'dev' into komfovent
ProstoSanja Nov 2, 2023
2ea95d2
Merge branch 'dev' into komfovent
joostlek Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@ omit =
homeassistant/components/kodi/browse_media.py
homeassistant/components/kodi/media_player.py
homeassistant/components/kodi/notify.py
homeassistant/components/komfovent/__init__.py
homeassistant/components/komfovent/climate.py
homeassistant/components/konnected/__init__.py
homeassistant/components/konnected/panel.py
homeassistant/components/konnected/switch.py
Expand Down
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,8 @@ build.json @home-assistant/supervisor
/tests/components/knx/ @Julius2342 @farmio @marvin-w
/homeassistant/components/kodi/ @OnFreund
/tests/components/kodi/ @OnFreund
/homeassistant/components/komfovent/ @ProstoSanja
/tests/components/komfovent/ @ProstoSanja
/homeassistant/components/konnected/ @heythisisnate
/tests/components/konnected/ @heythisisnate
/homeassistant/components/kostal_plenticore/ @stegm
Expand Down
36 changes: 36 additions & 0 deletions homeassistant/components/komfovent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""The Komfovent integration."""
from __future__ import annotations

from typing import cast

import komfovent_api

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import DOMAIN

PLATFORMS: list[Platform] = [Platform.CLIMATE]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Komfovent from a config entry."""
host = cast(str, entry.data[CONF_HOST])
username = cast(str, entry.data[CONF_USERNAME])
password = cast(str, entry.data[CONF_PASSWORD])
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
_, credentials = komfovent_api.get_credentials(host, username, password)
result, settings = await komfovent_api.get_settings(credentials)
if result != komfovent_api.KomfoventConnectionResult.SUCCESS:
raise ConfigEntryNotReady(f"Unable to connect to {host}: {result}")

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = (credentials, settings)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
90 changes: 90 additions & 0 deletions homeassistant/components/komfovent/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Ventilation Units from Komfovent integration."""
from __future__ import annotations

import komfovent_api

from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN

HASS_TO_KOMFOVENT_MODES = {
HVACMode.COOL: komfovent_api.KomfoventModes.COOL,
HVACMode.HEAT_COOL: komfovent_api.KomfoventModes.HEAT_COOL,
HVACMode.OFF: komfovent_api.KomfoventModes.OFF,
HVACMode.AUTO: komfovent_api.KomfoventModes.AUTO,
}
KOMFOVENT_TO_HASS_MODES = {v: k for k, v in HASS_TO_KOMFOVENT_MODES.items()}


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Komfovent unit control."""
credentials, settings = hass.data[DOMAIN][entry.entry_id]
async_add_entities([KomfoventDevice(credentials, settings)], True)


class KomfoventDevice(ClimateEntity):
"""Representation of a ventilation unit."""

_attr_hvac_modes = list(HASS_TO_KOMFOVENT_MODES.keys())
_attr_preset_modes = [mode.name for mode in komfovent_api.KomfoventPresets]
_attr_supported_features = ClimateEntityFeature.PRESET_MODE
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = "ventilation_unit"
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
_attr_has_entity_name = True
_attr_name = None
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved

ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
def __init__(
self,
credentials: komfovent_api.KomfoventCredentials,
settings: komfovent_api.KomfoventSettings,
) -> None:
"""Initialize the ventilation unit."""
self._komfovent_credentials = credentials
self._komfovent_settings = settings

self._attr_unique_id = settings.serial_number
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, settings.serial_number)},
model=settings.model,
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
sw_version=settings.version,
manufacturer="Komfovent",
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
)

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
await komfovent_api.set_preset(
self._komfovent_credentials,
komfovent_api.KomfoventPresets[preset_mode],
)

async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
await komfovent_api.set_mode(
self._komfovent_credentials, HASS_TO_KOMFOVENT_MODES[hvac_mode]
)

async def async_update(self) -> None:
"""Get the latest data."""
result, status = await komfovent_api.get_unit_status(
self._komfovent_credentials
)
if result != komfovent_api.KomfoventConnectionResult.SUCCESS or not status:
self._attr_available = False
return
self._attr_available = True
self._attr_preset_mode = status.preset
self._attr_current_temperature = status.temp_extract
self._attr_hvac_mode = KOMFOVENT_TO_HASS_MODES[status.mode]
74 changes: 74 additions & 0 deletions homeassistant/components/komfovent/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Config flow for Komfovent integration."""
from __future__ import annotations

import logging
from typing import Any

import komfovent_api
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

STEP_USER = "user"
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_USERNAME, default="user"): str,
vol.Required(CONF_PASSWORD): str,
}
)

ERRORS_MAP = {
komfovent_api.KomfoventConnectionResult.NOT_FOUND: "cannot_connect",
komfovent_api.KomfoventConnectionResult.UNAUTHORISED: "invalid_auth",
komfovent_api.KomfoventConnectionResult.INVALID_INPUT: "invalid_input",
}


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Komfovent."""

VERSION = 1

def __return_error(
self, result: komfovent_api.KomfoventConnectionResult
) -> FlowResult:
return self.async_show_form(
step_id=STEP_USER,
data_schema=STEP_USER_DATA_SCHEMA,
errors={"base": ERRORS_MAP.get(result, "unknown")},
)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
joostlek marked this conversation as resolved.
Show resolved Hide resolved
return self.async_show_form(
step_id=STEP_USER, data_schema=STEP_USER_DATA_SCHEMA
)

conf_host = user_input[CONF_HOST]
conf_username = user_input[CONF_USERNAME]
conf_password = user_input[CONF_PASSWORD]

result, credentials = komfovent_api.get_credentials(
conf_host, conf_username, conf_password
)
if result != komfovent_api.KomfoventConnectionResult.SUCCESS:
return self.__return_error(result)

result, settings = await komfovent_api.get_settings(credentials)
if result != komfovent_api.KomfoventConnectionResult.SUCCESS:
return self.__return_error(result)

await self.async_set_unique_id(settings.serial_number)
self._abort_if_unique_id_configured()

return self.async_create_entry(title=settings.name, data=user_input)
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions homeassistant/components/komfovent/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the Komfovent integration."""

DOMAIN = "komfovent"
9 changes: 9 additions & 0 deletions homeassistant/components/komfovent/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domain": "komfovent",
"name": "Komfovent",
"codeowners": ["@ProstoSanja"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/komfovent",
"iot_class": "local_polling",
"requirements": ["komfovent-api==0.0.3"]
}
29 changes: 29 additions & 0 deletions homeassistant/components/komfovent/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_input": "Failed parse provided hostname",
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"entity": {
"climate": {
"ventilation_unit": {
"name": "Ventilation unit"
}
}
ProstoSanja marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
"kmtronic",
"knx",
"kodi",
"komfovent",
"konnected",
"kostal_plenticore",
"kraken",
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -2881,6 +2881,12 @@
"config_flow": true,
"iot_class": "local_push"
},
"komfovent": {
"name": "Komfovent",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_polling"
},
"konnected": {
"name": "Konnected.io",
"integration_type": "hub",
Expand Down
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,9 @@ kiwiki-client==0.1.1
# homeassistant.components.knx
knx-frontend==2023.6.23.191712

# homeassistant.components.komfovent
komfovent-api==0.0.3

# homeassistant.components.konnected
konnected==1.2.0

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,9 @@ kegtron-ble==0.4.0
# homeassistant.components.knx
knx-frontend==2023.6.23.191712

# homeassistant.components.komfovent
komfovent-api==0.0.3

# homeassistant.components.konnected
konnected==1.2.0

Expand Down
1 change: 1 addition & 0 deletions tests/components/komfovent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Komfovent integration."""
14 changes: 14 additions & 0 deletions tests/components/komfovent/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Common fixtures for the Komfovent tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch

import pytest


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.komfovent.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry