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 config flow to Smarty #127540

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/smarttub/ @mdz
/tests/components/smarttub/ @mdz
/homeassistant/components/smarty/ @z0mbieprocess
/tests/components/smarty/ @z0mbieprocess
/homeassistant/components/smhi/ @gjohansson-ST
/tests/components/smhi/ @gjohansson-ST
/homeassistant/components/smlight/ @tl-sl
Expand Down
110 changes: 78 additions & 32 deletions homeassistant/components/smarty/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
from pysmarty2 import Smarty
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import discovery
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType

DOMAIN = "smarty"
DATA_SMARTY = "smarty"
SMARTY_NAME = "Smarty"
from .const import DOMAIN, SIGNAL_UPDATE_SMARTY

_LOGGER = logging.getLogger(__name__)

Expand All @@ -26,48 +26,94 @@
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): vol.All(ipaddress.ip_address, cv.string),
vol.Optional(CONF_NAME, default=SMARTY_NAME): cv.string,
vol.Optional(CONF_NAME, default="Smarty"): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)

RPM = "rpm"
SIGNAL_UPDATE_SMARTY = "smarty_update"
PLATFORMS = [Platform.BINARY_SENSOR, Platform.FAN, Platform.SENSOR]

type SmartyConfigEntry = ConfigEntry[Smarty]

def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the smarty environment."""

conf = config[DOMAIN]

host = conf[CONF_HOST]
name = conf[CONF_NAME]

_LOGGER.debug("Name: %s, host: %s", name, host)

smarty = Smarty(host=host)

hass.data[DOMAIN] = {"api": smarty, "name": name}
async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool:
"""Create a smarty system."""
if config := hass_config.get(DOMAIN):
hass.async_create_task(_async_import(hass, config))

Check warning on line 44 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L44

Added line #L44 was not covered by tests
return True

# Initial update
smarty.update()

# Load platforms
discovery.load_platform(hass, Platform.FAN, DOMAIN, {}, config)
discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config)
discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config)
async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
"""Set up the smarty environment."""

def poll_device_update(event_time):
if not hass.config_entries.async_entries(DOMAIN):

Check warning on line 51 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L51

Added line #L51 was not covered by tests
# Start import flow
result = await hass.config_entries.flow.async_init(

Check warning on line 53 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L53

Added line #L53 was not covered by tests
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
if result["type"] == FlowResultType.ABORT:
ir.async_create_issue(

Check warning on line 57 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L56-L57

Added lines #L56 - L57 were not covered by tests
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{result['reason']}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key=f"deprecated_yaml_import_issue_{result['reason']}",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Smarty",
},
)
return

Check warning on line 71 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L71

Added line #L71 was not covered by tests

ir.async_create_issue(

Check warning on line 73 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L73

Added line #L73 was not covered by tests
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Smarty",
},
)


async def async_setup_entry(hass: HomeAssistant, entry: SmartyConfigEntry) -> bool:
"""Set up the Smarty environment from a config entry."""

def _setup_smarty() -> Smarty:
smarty = Smarty(host=entry.data[CONF_HOST])
smarty.update()
return smarty

Check warning on line 95 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L92-L95

Added lines #L92 - L95 were not covered by tests

smarty = await hass.async_add_executor_job(_setup_smarty)

Check warning on line 97 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L97

Added line #L97 was not covered by tests

entry.runtime_data = smarty

Check warning on line 99 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L99

Added line #L99 was not covered by tests

async def poll_device_update(event_time) -> None:

Check warning on line 101 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L101

Added line #L101 was not covered by tests
"""Update Smarty device."""
_LOGGER.debug("Updating Smarty device")
if smarty.update():
if await hass.async_add_executor_job(smarty.update):

Check warning on line 104 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L104

Added line #L104 was not covered by tests
_LOGGER.debug("Update success")
dispatcher_send(hass, SIGNAL_UPDATE_SMARTY)
async_dispatcher_send(hass, SIGNAL_UPDATE_SMARTY)

Check warning on line 106 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L106

Added line #L106 was not covered by tests
else:
_LOGGER.debug("Update failed")

track_time_interval(hass, poll_device_update, timedelta(seconds=30))
async_track_time_interval(hass, poll_device_update, timedelta(seconds=30))

Check warning on line 110 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L110

Added line #L110 was not covered by tests

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

Check warning on line 112 in homeassistant/components/smarty/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/__init__.py#L112

Added line #L112 was not covered by tests

return True


async def async_unload_entry(hass: HomeAssistant, entry: SmartyConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
18 changes: 8 additions & 10 deletions homeassistant/components/smarty/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,25 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from . import DOMAIN, SIGNAL_UPDATE_SMARTY
from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry

Check warning on line 17 in homeassistant/components/smarty/binary_sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/binary_sensor.py#L17

Added line #L17 was not covered by tests

_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(
async def async_setup_entry(

Check warning on line 22 in homeassistant/components/smarty/binary_sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/binary_sensor.py#L22

Added line #L22 was not covered by tests
hass: HomeAssistant,
config: ConfigType,
entry: SmartyConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Smarty Binary Sensor Platform."""
smarty: Smarty = hass.data[DOMAIN]["api"]
name: str = hass.data[DOMAIN]["name"]

smarty = entry.runtime_data

Check warning on line 29 in homeassistant/components/smarty/binary_sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/binary_sensor.py#L29

Added line #L29 was not covered by tests

sensors = [
AlarmSensor(name, smarty),
WarningSensor(name, smarty),
BoostSensor(name, smarty),
AlarmSensor(entry.title, smarty),
WarningSensor(entry.title, smarty),
BoostSensor(entry.title, smarty),
]

async_add_entities(sensors, True)
Expand Down
63 changes: 63 additions & 0 deletions homeassistant/components/smarty/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Config flow for Smarty integration."""

from typing import Any

from pysmarty2 import Smarty
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_NAME

from .const import DOMAIN


class SmartyConfigFlow(ConfigFlow, domain=DOMAIN):
"""Smarty config flow."""

def _test_connection(self, host: str) -> str | None:
"""Test the connection to the Smarty API."""
smarty = Smarty(host=host)
try:
if smarty.update():
return None
except Exception: # noqa: BLE001
return "unknown"
else:
return "cannot_connect"

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}

if user_input is not None:
self._async_abort_entries_match(user_input)
error = await self.hass.async_add_executor_job(
self._test_connection, user_input[CONF_HOST]
)
if not error:
return self.async_create_entry(
title=user_input[CONF_HOST], data=user_input
)
errors["base"] = error
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors,
)

async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Handle a flow initialized by import."""
self._async_abort_entries_match({CONF_HOST: import_config[CONF_HOST]})
error = await self.hass.async_add_executor_job(
self._test_connection, import_config[CONF_HOST]
)
if not error:
return self.async_create_entry(
title=import_config[CONF_NAME],
data={CONF_HOST: import_config[CONF_HOST]},
)
return self.async_abort(reason=error)
5 changes: 5 additions & 0 deletions homeassistant/components/smarty/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Constants for the Smarty component."""

DOMAIN = "smarty"

SIGNAL_UPDATE_SMARTY = "smarty_update"
18 changes: 7 additions & 11 deletions homeassistant/components/smarty/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,35 @@
import math
from typing import Any

from pysmarty2 import Smarty

from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.percentage import (
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from . import DOMAIN, SIGNAL_UPDATE_SMARTY
from . import SIGNAL_UPDATE_SMARTY, SmartyConfigEntry

Check warning on line 20 in homeassistant/components/smarty/fan.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/fan.py#L20

Added line #L20 was not covered by tests

_LOGGER = logging.getLogger(__name__)

DEFAULT_ON_PERCENTAGE = 66
SPEED_RANGE = (1, 3) # off is not included


async def async_setup_platform(
async def async_setup_entry(

Check warning on line 28 in homeassistant/components/smarty/fan.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/fan.py#L28

Added line #L28 was not covered by tests
hass: HomeAssistant,
config: ConfigType,
entry: SmartyConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Smarty Fan Platform."""
smarty: Smarty = hass.data[DOMAIN]["api"]
name: str = hass.data[DOMAIN]["name"]
"""Set up the Smarty Binary Sensor Platform."""
joostlek marked this conversation as resolved.
Show resolved Hide resolved

smarty = entry.runtime_data

Check warning on line 35 in homeassistant/components/smarty/fan.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/fan.py#L35

Added line #L35 was not covered by tests

async_add_entities([SmartyFan(name, smarty)], True)
async_add_entities([SmartyFan(entry.title, smarty)], True)

Check warning on line 37 in homeassistant/components/smarty/fan.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/fan.py#L37

Added line #L37 was not covered by tests


class SmartyFan(FanEntity):
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/smarty/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"domain": "smarty",
"name": "Salda Smarty",
"codeowners": ["@z0mbieprocess"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/smarty",
"integration_type": "hub",
"iot_class": "local_polling",
Expand Down
22 changes: 21 additions & 1 deletion homeassistant/components/smarty/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,31 @@
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util

from . import DOMAIN, SIGNAL_UPDATE_SMARTY
from . import DOMAIN, SIGNAL_UPDATE_SMARTY, SmartyConfigEntry

Check warning on line 18 in homeassistant/components/smarty/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/sensor.py#L18

Added line #L18 was not covered by tests

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(

Check warning on line 23 in homeassistant/components/smarty/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/sensor.py#L23

Added line #L23 was not covered by tests
hass: HomeAssistant,
entry: SmartyConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Smarty Binary Sensor Platform."""
joostlek marked this conversation as resolved.
Show resolved Hide resolved

smarty = entry.runtime_data
sensors = [

Check warning on line 31 in homeassistant/components/smarty/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/sensor.py#L30-L31

Added lines #L30 - L31 were not covered by tests
SupplyAirTemperatureSensor(entry.title, smarty),
ExtractAirTemperatureSensor(entry.title, smarty),
OutdoorAirTemperatureSensor(entry.title, smarty),
SupplyFanSpeedSensor(entry.title, smarty),
ExtractFanSpeedSensor(entry.title, smarty),
FilterDaysLeftSensor(entry.title, smarty),
]

async_add_entities(sensors, True)

Check warning on line 40 in homeassistant/components/smarty/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/smarty/sensor.py#L40

Added line #L40 was not covered by tests


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
Expand Down
33 changes: 33 additions & 0 deletions homeassistant/components/smarty/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "The hostname or IP address of the Smarty device"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"issues": {
"deprecated_yaml_import_issue_unknown": {
"title": "YAML import failed with unknown error",
"description": "Configuring {integration_title} using YAML is being removed but there was an unknown error while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your {integration_title} is operating correctly and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the `{domain}` configuration from your configuration.yaml entirely, restart Home Assistant, and add the {integration_title} integration manually."
},
"deprecated_yaml_import_issue_auth_error": {
"title": "YAML import failed due to an authentication error",
"description": "Configuring {integration_title} using YAML is being removed but there was an authentication error while importing your existing configuration.\nSetup will not proceed.\n\nVerify that your {integration_title} is operating correctly and restart Home Assistant to attempt the import again.\n\nAlternatively, you may remove the `{domain}` configuration from your configuration.yaml entirely, restart Home Assistant, and add the {integration_title} integration manually."
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@
"smart_meter_texas",
"smartthings",
"smarttub",
"smarty",
"smhi",
"smlight",
"sms",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -5654,7 +5654,7 @@
"smarty": {
"name": "Salda Smarty",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "local_polling"
},
"smhi": {
Expand Down
Loading