-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add config flow for Rain Bird #85271
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
Changes from 3 commits
48bbb21
ffd2b5f
e16b0c6
49dcb7c
797cf9e
fb8ce86
cf019cc
868a2de
8c1aa07
1e019a6
f82a533
9b12dee
0cc4ec9
9f8e2d7
d8eb12e
63f43fb
009c08d
b91275a
15edee1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,31 +4,40 @@ | |
| import asyncio | ||
| import logging | ||
|
|
||
| import async_timeout | ||
| from pyrainbird.async_client import ( | ||
| AsyncRainbirdClient, | ||
| AsyncRainbirdController, | ||
| RainbirdApiException, | ||
| ) | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry | ||
| from homeassistant.const import ( | ||
| CONF_FRIENDLY_NAME, | ||
| CONF_HOST, | ||
| CONF_PASSWORD, | ||
| CONF_TRIGGER_TIME, | ||
| Platform, | ||
| ) | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.helpers import discovery | ||
| from homeassistant.core import HomeAssistant, ServiceCall | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.helpers.entity import DeviceInfo | ||
| from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue | ||
| from homeassistant.helpers.typing import ConfigType | ||
|
|
||
| from .const import ( | ||
| ATTR_DURATION, | ||
| CONF_ZONES, | ||
| DEVICE_INFO, | ||
| MANUFACTURER, | ||
| RAINBIRD_CONTROLLER, | ||
| SENSOR_TYPE_RAINDELAY, | ||
| SENSOR_TYPE_RAINSENSOR, | ||
| SERIAL_NUMBER, | ||
| TIMEOUT_SECONDS, | ||
| ) | ||
| from .coordinator import RainbirdUpdateCoordinator | ||
|
|
||
|
|
@@ -61,47 +70,101 @@ | |
| extra=vol.ALLOW_EXTRA, | ||
| ) | ||
|
|
||
| SERVICE_SET_RAIN_DELAY = "set_rain_delay" | ||
| SERVICE_SCHEMA_RAIN_DELAY = vol.Schema( | ||
| { | ||
| vol.Required(ATTR_DURATION): cv.positive_float, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: | ||
| """Set up the Rain Bird component.""" | ||
| return all( | ||
| await asyncio.gather( | ||
| *[ | ||
| _setup_controller(hass, controller_config, config) | ||
| for controller_config in config[DOMAIN] | ||
| ] | ||
| if DOMAIN not in config: | ||
| return True | ||
|
|
||
| for controller_config in config[DOMAIN]: | ||
| hass.async_create_task( | ||
| hass.config_entries.flow.async_init( | ||
| DOMAIN, | ||
| context={"source": SOURCE_IMPORT}, | ||
| data=controller_config, | ||
| ) | ||
| ) | ||
|
|
||
| async_create_issue( | ||
| hass, | ||
| DOMAIN, | ||
| "deprecated_yaml", | ||
| breaks_in_ha_version="2023.3.0", | ||
| is_fixable=False, | ||
| severity=IssueSeverity.WARNING, | ||
| translation_key="deprecated_yaml", | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
| async def _setup_controller(hass, controller_config, config): | ||
| """Set up a controller.""" | ||
| server = controller_config[CONF_HOST] | ||
| password = controller_config[CONF_PASSWORD] | ||
| client = AsyncRainbirdClient(async_get_clientsession(hass), server, password) | ||
| controller = AsyncRainbirdController(client) | ||
| try: | ||
| await controller.get_serial_number() | ||
| except RainbirdApiException as exc: | ||
| _LOGGER.error("Unable to setup controller: %s", exc) | ||
| return False | ||
|
|
||
| rain_coordinator = RainbirdUpdateCoordinator(hass, controller.get_rain_sensor_state) | ||
| delay_coordinator = RainbirdUpdateCoordinator(hass, controller.get_rain_delay) | ||
| async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
| """Set up the config entry for Rain Bird.""" | ||
| hass.data.setdefault(DOMAIN, {}) | ||
|
|
||
| for platform in PLATFORMS: | ||
| hass.async_create_task( | ||
| discovery.async_load_platform( | ||
| hass, | ||
| platform, | ||
| DOMAIN, | ||
| { | ||
| RAINBIRD_CONTROLLER: controller, | ||
| SENSOR_TYPE_RAINSENSOR: rain_coordinator, | ||
| SENSOR_TYPE_RAINDELAY: delay_coordinator, | ||
| **controller_config, | ||
| }, | ||
| config, | ||
| ) | ||
| controller = AsyncRainbirdController( | ||
| AsyncRainbirdClient( | ||
| async_get_clientsession(hass), | ||
| entry.data[CONF_HOST], | ||
| entry.data[CONF_PASSWORD], | ||
| ) | ||
| ) | ||
|
|
||
| try: | ||
| async with async_timeout.timeout(TIMEOUT_SECONDS): | ||
| serial_number = await controller.get_serial_number() | ||
| except (RainbirdApiException, asyncio.TimeoutError) as err: | ||
| raise ConfigEntryNotReady(f"Error talking to controller: {str(err)}") from err | ||
|
|
||
| device_info = DeviceInfo( | ||
| default_name=MANUFACTURER, | ||
| identifiers={(DOMAIN, serial_number)}, | ||
| manufacturer=MANUFACTURER, | ||
| ) | ||
| rain_coordinator = RainbirdUpdateCoordinator( | ||
| hass, "Rain", controller.get_rain_sensor_state | ||
| ) | ||
| delay_coordinator = RainbirdUpdateCoordinator( | ||
| hass, "Rain delay", controller.get_rain_delay | ||
| ) | ||
|
|
||
| hass.data[DOMAIN][entry.entry_id] = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend using a dataclass to store multiple items in hass.data.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on this comment and the comment about initializing the update coordinators in the platforms, I decided to just simplify the overall interactions between the device and the integration, moving all the rpcs into a single data update coordinator. This required changing the tests, so i added additional test fixtures to help manage the request setup and ordering expected by the integration. The consequence of this is now a single update coordinator handles rpcs for all platforms so there isn't independence for each sensor, but in hindsight that is probably simpler overall (e.g. if the device is offline, there is just one coordinator trying to hit it rather than 3). While I was here, I also removed some unnecessary duplicate sensors. Today there is a rain sensor and a rain delay sensor both exported as a sensor and binary sensor and it doesn't really make a lot of sense to me. I've just left the sensors that provide unique value and have updated the breaking changes in the description.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is too much for a single PR, I can break into smaller chunks, or move into another branch to not have to worry about leaving dev in a bad state.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not too much. What did the four binary sensors and sensors represent? How would a user replace the removed sensors?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There were four total sensors representing two values that were redundant. I updated the PR to describe but can reword if it is not clear. The "rain" sensor was a binary sensor and a sensor with a string boolean value. This represents whether the device knows it is raining (I'm not sure if it's from a separate sensors only or also from a cloud weather report) The "rain delay" represents the number of days that the sprinkler is paused due to rain. This is a sensor and also had a binary sensor when it was non-zero. The rain delay service can update this value so maybe this should just be a Number?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. Yeah, if the rain delay service and sensor are in sync it should be a number entity.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can deprecate the service and raise a repair issue telling users to move to the number entity and service when that's in place. |
||
| SERIAL_NUMBER: serial_number, | ||
| DEVICE_INFO: device_info, | ||
| RAINBIRD_CONTROLLER: controller, | ||
| SENSOR_TYPE_RAINSENSOR: rain_coordinator, | ||
| SENSOR_TYPE_RAINDELAY: delay_coordinator, | ||
| } | ||
|
|
||
| await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
|
||
| async def set_rain_delay(service: ServiceCall) -> None: | ||
| await controller.set_rain_delay(service.data[ATTR_DURATION]) | ||
|
allenporter marked this conversation as resolved.
Outdated
|
||
|
|
||
| hass.services.async_register( | ||
| DOMAIN, | ||
| SERVICE_SET_RAIN_DELAY, | ||
| set_rain_delay, | ||
| schema=SERVICE_SCHEMA_RAIN_DELAY, | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
| """Unload a config entry.""" | ||
|
|
||
| if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): | ||
| hass.data[DOMAIN].pop(entry.entry_id) | ||
|
|
||
| if unload_ok and not hass.data[DOMAIN]: | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
| hass.services.async_remove(DOMAIN, SERVICE_SET_RAIN_DELAY) | ||
|
|
||
| return unload_ok | ||
Uh oh!
There was an error while loading. Please reload this page.