Skip to content

Commit

Permalink
Merge branch 'config-flow' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sebr committed May 17, 2022
2 parents 07c944b + fc46279 commit 9247151
Show file tree
Hide file tree
Showing 17 changed files with 825 additions and 350 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ __pycache__

tmp

debug/[0-9]*
debug/[0-9]*
debug/sync
19 changes: 15 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
{
"python.formatting.provider": "black",
"python.pythonPath": "/usr/local/bin/python3",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintArgs": [
"--disable",
"import-error"
],
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"python.analysis.autoImportCompletions": false,
"python.analysis.diagnosticSeverityOverrides": {
"reportMissingImports": "none"
}

}
182 changes: 86 additions & 96 deletions custom_components/bhyve/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
"""Support for Orbit BHyve irrigation devices."""
import json
import logging

import voluptuous as vol

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import DeviceInfo, Entity

from .const import (
CONF_ATTRIBUTION,
DATA_BHYVE,
CONF_DEVICES,
DOMAIN,
EVENT_PROGRAM_CHANGED,
EVENT_RAIN_DELAY,
Expand All @@ -30,31 +31,16 @@
SIGNAL_UPDATE_DEVICE,
SIGNAL_UPDATE_PROGRAM,
)
from .util import anonymize
from .pybhyve import Client
from .pybhyve.errors import BHyveError, WebsocketError
from .pybhyve.errors import AuthenticationError, BHyveError

_LOGGER = logging.getLogger(__name__)

DATA_CONFIG = "config"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)

PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]

async def async_setup(hass, config):
"""Set up the BHyve component."""

conf = config[DOMAIN]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up BHyve from a config entry."""

async def async_update_callback(data):
event = data.get("event")
Expand All @@ -76,45 +62,52 @@ async def async_update_callback(data):
hass, SIGNAL_UPDATE_PROGRAM.format(program_id), program_id, data
)

session = aiohttp_client.async_get_clientsession(hass)
client = Client(
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
session=async_get_clientsession(hass),
)

try:
bhyve = Client(
conf[CONF_USERNAME],
conf[CONF_PASSWORD],
loop=hass.loop,
session=session,
async_callback=async_update_callback,
)
if await client.login() is False:
raise ConfigEntryAuthFailed()

await bhyve.login()
client.listen(hass.loop, async_update_callback)
all_devices = await client.devices
programs = await client.timer_programs
except AuthenticationError as err:
raise ConfigEntryAuthFailed() from err
except BHyveError as err:
raise ConfigEntryNotReady() from err

devices = [anonymize(device) for device in await bhyve.devices]
programs = await bhyve.timer_programs
# Filter the device list to those that are enabled in options
devices = [d for d in all_devices if str(d["id"]) in entry.options[CONF_DEVICES]]

_LOGGER.debug("Devices: {}".format(json.dumps(devices)))
_LOGGER.debug("Programs: {}".format(json.dumps(programs)))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
"client": client,
"devices": devices,
"programs": programs,
}

hass.data[DATA_BHYVE] = bhyve
except WebsocketError as err:
_LOGGER.error("Config entry failed: %s", err)
raise ConfigEntryNotReady
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, bhyve.stop())
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, client.stop())

return True


def get_entity_from_domain(hass, domains, entity_id):
domains = domains if isinstance(domains, list) else [domains]
for domain in domains:
component = hass.data.get(domain)
if component is None:
raise HomeAssistantError("{} component not set up".format(domain))
entity = component.get_entity(entity_id)
if entity is not None:
return entity
raise HomeAssistantError("{} not found in {}".format(entity_id, ",".join(domains)))
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload to update options."""
await hass.config_entries.async_reload(entry.entry_id)


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)

return unload_ok


class BHyveEntity(Entity):
Expand All @@ -124,6 +117,7 @@ def __init__(
self,
hass,
bhyve,
device,
name,
icon,
device_class=None,
Expand All @@ -134,11 +128,25 @@ def __init__(
self._device_class = device_class

self._name = name
self._icon = "mdi:{}".format(icon)
self._icon = f"mdi:{icon}"
self._state = None
self._available = False
self._attrs = {}

self._device_id = device.get("id")
self._device_type = device.get("type")
self._device_name = device.get("name")

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
manufacturer=MANUFACTURER,
configuration_url=f"https://techsupport.orbitbhyve.com/dashboard/support/device/{self._device_id}",
name=self._device_name,
model=device.get("hardware_version"),
hw_version=device.get("hardware_version"),
sw_version=device.get("firmware_version"),
)

@property
def available(self):
"""Return True if entity is available."""
Expand Down Expand Up @@ -170,13 +178,9 @@ def should_poll(self):
return False

@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity."""
return {
"name": self._name,
"manufacturer": MANUFACTURER,
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
}
return self._attr_device_info


class BHyveWebsocketEntity(BHyveEntity):
Expand All @@ -186,19 +190,20 @@ def __init__(
self,
hass,
bhyve,
device,
name,
icon,
device_class=None,
):
self._async_unsub_dispatcher_connect = None
self._ws_unprocessed_events = []
super().__init__(hass, bhyve, name, icon, device_class)
super().__init__(hass, bhyve, device, name, icon, device_class)

def _on_ws_data(self, data):
pass

def _should_handle_event(self, event_name, data):
"""True if the websocket event should be handled"""
"""True if the websocket event should be handled."""
return True

async def async_update(self):
Expand All @@ -225,11 +230,8 @@ def __init__(
"""Initialize the sensor."""

self._mac_address = device.get("mac_address")
self._device_id = device.get("id")
self._device_type = device.get("type")
self._device_name = device.get("name")

super().__init__(hass, bhyve, name, icon, device_class)
super().__init__(hass, bhyve, device, name, icon, device_class)

self._setup(device)

Expand All @@ -247,7 +249,7 @@ async def _refetch_device(self, force_update=False):
self._setup(device)

except BHyveError as err:
_LOGGER.warning(f"Unable to retreive data for {self.name}: {err}")
_LOGGER.warning("Unable to retreive data for %s: %s", self.name, err)

async def _fetch_device_history(self, force_update=False):
try:
Expand All @@ -256,21 +258,11 @@ async def _fetch_device_history(self, force_update=False):
except BHyveError as err:
raise (err)

@property
def device_info(self):
"""Return device registry information for this entity."""
return {
"identifiers": {(DOMAIN, self._mac_address)},
"name": self._device_name,
"manufacturer": MANUFACTURER,
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
}

@property
def unique_id(self):
"""Return a unique, unchanging string that represents this sensor."""
raise HomeAssistantError(
"{} does not define a unique_id".format(self.__class__.__name__)
f"{self.__class__.__name__} does not define a unique_id"
)

async def async_added_to_hass(self):
Expand All @@ -282,21 +274,18 @@ def update(device_id, data):
event = data.get("event")
if event == "device_disconnected":
_LOGGER.warning(
"Device {} disconnected and is no longer available".format(
self.name
)
"Device %s disconnected and is no longer available", self.name
)
self._available = False
elif event == "device_connected":
_LOGGER.info(
"Device {} reconnected and is now available".format(self.name)
)
_LOGGER.info("Device %s reconnected and is now available", self.name)
self._available = True
if self._should_handle_event(event, data):
_LOGGER.info(
"Message received: {} - {} - {}".format(
self.name, self._device_id, str(data)
)
"Message received: %s - %s - %s",
self.name,
self._device_id,
str(data),
)
self._ws_unprocessed_events.append(data)
self.async_schedule_update_ha_state(True)
Expand All @@ -311,21 +300,22 @@ async def async_will_remove_from_hass(self):
self._async_unsub_dispatcher_connect()

async def set_manual_preset_runtime(self, minutes: int):
"""Sets the default watering runtime for the device."""
# {event: "set_manual_preset_runtime", device_id: "abc", seconds: 900}
payload = {
"event": EVENT_SET_MANUAL_PRESET_TIME,
"device_id": self._device_id,
"seconds": minutes * 60,
}
_LOGGER.info("Setting manual preset runtime: {}".format(payload))
_LOGGER.info("Setting manual preset runtime: %s", payload)
await self._bhyve.send_message(payload)

async def enable_rain_delay(self, hours: int = 24):
"""Enable rain delay"""
"""Enable rain delay."""
await self._set_rain_delay(hours)

async def disable_rain_delay(self):
"""Disable rain delay"""
"""Disable rain delay."""
await self._set_rain_delay(0)

async def _set_rain_delay(self, hours: int):
Expand All @@ -336,7 +326,7 @@ async def _set_rain_delay(self, hours: int):
"device_id": self._device_id,
"delay": hours,
}
_LOGGER.info("Setting rain delay: {}".format(payload))
_LOGGER.info("Setting rain delay: %s", payload)
await self._bhyve.send_message(payload)

except BHyveError as err:
Expand Down
Loading

0 comments on commit 9247151

Please sign in to comment.