Skip to content

Commit

Permalink
Add config flow to generic hygrostat (home-assistant#119017)
Browse files Browse the repository at this point in the history
* Add config flow to hygrostat

Co-authored-by: Franck Nijhof <[email protected]>
  • Loading branch information
elupus and frenck authored Jun 23, 2024
1 parent 4474e8c commit 84d1d11
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 13 deletions.
20 changes: 20 additions & 0 deletions homeassistant/components/generic_hygrostat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import voluptuous as vol

from homeassistant.components.humidifier import HumidifierDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery
Expand Down Expand Up @@ -73,3 +74,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up from a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, (Platform.HUMIDIFIER,))
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
return True


async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener, called when the config entry options are changed."""
await hass.config_entries.async_reload(entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(
entry, (Platform.HUMIDIFIER,)
)
100 changes: 100 additions & 0 deletions homeassistant/components/generic_hygrostat/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Config flow for Generic hygrostat."""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any, cast

import voluptuous as vol

from homeassistant.components.humidifier import HumidifierDeviceClass
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import CONF_NAME, PERCENTAGE
from homeassistant.helpers import selector
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
)

from . import (
CONF_DEVICE_CLASS,
CONF_DRY_TOLERANCE,
CONF_HUMIDIFIER,
CONF_MIN_DUR,
CONF_SENSOR,
CONF_WET_TOLERANCE,
DEFAULT_TOLERANCE,
DOMAIN,
)

OPTIONS_SCHEMA = {
vol.Required(CONF_DEVICE_CLASS): selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
HumidifierDeviceClass.HUMIDIFIER,
HumidifierDeviceClass.DEHUMIDIFIER,
],
translation_key=CONF_DEVICE_CLASS,
mode=selector.SelectSelectorMode.DROPDOWN,
),
),
vol.Required(CONF_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=SENSOR_DOMAIN, device_class=SensorDeviceClass.HUMIDITY
)
),
vol.Required(CONF_HUMIDIFIER): selector.EntitySelector(
selector.EntitySelectorConfig(domain=SWITCH_DOMAIN)
),
vol.Required(
CONF_DRY_TOLERANCE, default=DEFAULT_TOLERANCE
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0,
max=100,
step=0.5,
unit_of_measurement=PERCENTAGE,
mode=selector.NumberSelectorMode.BOX,
)
),
vol.Required(
CONF_WET_TOLERANCE, default=DEFAULT_TOLERANCE
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0,
max=100,
step=0.5,
unit_of_measurement=PERCENTAGE,
mode=selector.NumberSelectorMode.BOX,
)
),
vol.Optional(CONF_MIN_DUR): selector.DurationSelector(
selector.DurationSelectorConfig(allow_negative=False)
),
}

CONFIG_SCHEMA = {
vol.Required(CONF_NAME): selector.TextSelector(),
**OPTIONS_SCHEMA,
}


CONFIG_FLOW = {
"user": SchemaFlowFormStep(vol.Schema(CONFIG_SCHEMA)),
}

OPTIONS_FLOW = {
"init": SchemaFlowFormStep(vol.Schema(OPTIONS_SCHEMA)),
}


class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow."""

config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW

def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title."""
return cast(str, options["name"])
50 changes: 43 additions & 7 deletions homeassistant/components/generic_hygrostat/humidifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from __future__ import annotations

import asyncio
from collections.abc import Callable
from collections.abc import Callable, Mapping
from datetime import datetime, timedelta
import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast

from homeassistant.components.humidifier import (
ATTR_HUMIDITY,
Expand All @@ -18,6 +18,7 @@
HumidifierEntity,
HumidifierEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
Expand All @@ -39,7 +40,7 @@
State,
callback,
)
from homeassistant.helpers import condition
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import (
async_track_state_change_event,
Expand Down Expand Up @@ -83,22 +84,57 @@ async def async_setup_platform(
"""Set up the generic hygrostat platform."""
if discovery_info:
config = discovery_info
await _async_setup_config(
hass, config, config.get(CONF_UNIQUE_ID), async_add_entities
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize config entry."""

await _async_setup_config(
hass,
config_entry.options,
config_entry.entry_id,
async_add_entities,
)


def _time_period_or_none(value: Any) -> timedelta | None:
if value is None:
return None
return cast(timedelta, cv.time_period(value))


async def _async_setup_config(
hass: HomeAssistant,
config: Mapping[str, Any],
unique_id: str | None,
async_add_entities: AddEntitiesCallback,
) -> None:
name: str = config[CONF_NAME]
switch_entity_id: str = config[CONF_HUMIDIFIER]
sensor_entity_id: str = config[CONF_SENSOR]
min_humidity: float | None = config.get(CONF_MIN_HUMIDITY)
max_humidity: float | None = config.get(CONF_MAX_HUMIDITY)
target_humidity: float | None = config.get(CONF_TARGET_HUMIDITY)
device_class: HumidifierDeviceClass | None = config.get(CONF_DEVICE_CLASS)
min_cycle_duration: timedelta | None = config.get(CONF_MIN_DUR)
sensor_stale_duration: timedelta | None = config.get(CONF_STALE_DURATION)
min_cycle_duration: timedelta | None = _time_period_or_none(
config.get(CONF_MIN_DUR)
)
sensor_stale_duration: timedelta | None = _time_period_or_none(
config.get(CONF_STALE_DURATION)
)
dry_tolerance: float = config[CONF_DRY_TOLERANCE]
wet_tolerance: float = config[CONF_WET_TOLERANCE]
keep_alive: timedelta | None = config.get(CONF_KEEP_ALIVE)
keep_alive: timedelta | None = _time_period_or_none(config.get(CONF_KEEP_ALIVE))
initial_state: bool | None = config.get(CONF_INITIAL_STATE)
away_humidity: int | None = config.get(CONF_AWAY_HUMIDITY)
away_fixed: bool | None = config.get(CONF_AWAY_FIXED)
unique_id: str | None = config.get(CONF_UNIQUE_ID)

async_add_entities(
[
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/generic_hygrostat/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"domain": "generic_hygrostat",
"name": "Generic hygrostat",
"codeowners": ["@Shulyaka"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/generic_hygrostat",
"integration_type": "helper",
"iot_class": "local_polling",
"quality_scale": "internal"
}
56 changes: 56 additions & 0 deletions homeassistant/components/generic_hygrostat/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"title": "Generic hygrostat",
"config": {
"step": {
"user": {
"title": "Add generic hygrostat",
"description": "Create a entity that control the humidity via a switch and sensor.",
"data": {
"device_class": "Device class",
"dry_tolerance": "Dry tolerance",
"humidifier": "Switch",
"min_cycle_duration": "Minimum cycle duration",
"name": "[%key:common::config_flow::data::name%]",
"target_sensor": "Humidity sensor",
"wet_tolerance": "Wet tolerance"
},
"data_description": {
"dry_tolerance": "The minimum amount of difference between the humidity read by the sensor specified in the target sensor option and the target humidity that must change prior to being switched on.",
"humidifier": "Humidifier or dehumidifier switch; must be a toggle device.",
"min_cycle_duration": "Set a minimum amount of time that the switch specified in the humidifier option must be in its current state prior to being switched either off or on.",
"target_sensor": "Sensor with current humidity.",
"wet_tolerance": "The minimum amount of difference between the humidity read by the sensor specified in the target sensor option and the target humidity that must change prior to being switched off."
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"device_class": "[%key:component::generic_hygrostat::config::step::user::data::device_class%]",
"dry_tolerance": "[%key:component::generic_hygrostat::config::step::user::data::dry_tolerance%]",
"humidifier": "[%key:component::generic_hygrostat::config::step::user::data::humidifier%]",
"min_cycle_duration": "[%key:component::generic_hygrostat::config::step::user::data::min_cycle_duration%]",
"target_sensor": "[%key:component::generic_hygrostat::config::step::user::data::target_sensor%]",
"wet_tolerance": "[%key:component::generic_hygrostat::config::step::user::data::wet_tolerance%]"
},
"data_description": {
"dry_tolerance": "[%key:component::generic_hygrostat::config::step::user::data_description::dry_tolerance%]",
"humidifier": "[%key:component::generic_hygrostat::config::step::user::data_description::humidifier%]",
"min_cycle_duration": "[%key:component::generic_hygrostat::config::step::user::data_description::min_cycle_duration%]",
"target_sensor": "[%key:component::generic_hygrostat::config::step::user::data_description::target_sensor%]",
"wet_tolerance": "[%key:component::generic_hygrostat::config::step::user::data_description::wet_tolerance%]"
}
}
}
},
"selector": {
"device_class": {
"options": {
"humidifier": "Humidifier",
"dehumidifier": "Dehumidifier"
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
FLOWS = {
"helper": [
"derivative",
"generic_hygrostat",
"generic_thermostat",
"group",
"integration",
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -2127,12 +2127,6 @@
"config_flow": true,
"iot_class": "local_push"
},
"generic_hygrostat": {
"name": "Generic hygrostat",
"integration_type": "hub",
"config_flow": false,
"iot_class": "local_polling"
},
"geniushub": {
"name": "Genius Hub",
"integration_type": "hub",
Expand Down Expand Up @@ -7160,6 +7154,11 @@
"config_flow": true,
"iot_class": "calculated"
},
"generic_hygrostat": {
"integration_type": "helper",
"config_flow": true,
"iot_class": "local_polling"
},
"generic_thermostat": {
"integration_type": "helper",
"config_flow": true,
Expand Down Expand Up @@ -7265,6 +7264,7 @@
"filesize",
"garages_amsterdam",
"generic",
"generic_hygrostat",
"generic_thermostat",
"google_travel_time",
"group",
Expand Down
66 changes: 66 additions & 0 deletions tests/components/generic_hygrostat/snapshots/test_config_flow.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# serializer version: 1
# name: test_config_flow[create]
FlowResultSnapshot({
'result': ConfigEntrySnapshot({
'title': 'My hygrostat',
}),
'title': 'My hygrostat',
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
})
# ---
# name: test_config_flow[init]
FlowResultSnapshot({
'type': <FlowResultType.FORM: 'form'>,
})
# ---
# name: test_options[create_entry]
FlowResultSnapshot({
'result': True,
'type': <FlowResultType.CREATE_ENTRY: 'create_entry'>,
})
# ---
# name: test_options[dehumidifier]
StateSnapshot({
'attributes': ReadOnlyDict({
'action': <HumidifierAction.OFF: 'off'>,
'current_humidity': 10.0,
'device_class': 'dehumidifier',
'friendly_name': 'My hygrostat',
'humidity': 100,
'max_humidity': 100,
'min_humidity': 0,
'supported_features': <HumidifierEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'humidifier.my_hygrostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_options[humidifier]
StateSnapshot({
'attributes': ReadOnlyDict({
'action': <HumidifierAction.OFF: 'off'>,
'current_humidity': 10.0,
'device_class': 'humidifier',
'friendly_name': 'My hygrostat',
'humidity': 100,
'max_humidity': 100,
'min_humidity': 0,
'supported_features': <HumidifierEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'humidifier.my_hygrostat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_options[init]
FlowResultSnapshot({
'type': <FlowResultType.FORM: 'form'>,
})
# ---
Loading

0 comments on commit 84d1d11

Please sign in to comment.