Skip to content

Commit

Permalink
Merge branch 'async'
Browse files Browse the repository at this point in the history
  • Loading branch information
gazoodle committed Mar 15, 2022
2 parents 4336313 + aba5d52 commit 3b9b75d
Show file tree
Hide file tree
Showing 15 changed files with 356 additions and 186 deletions.
11 changes: 8 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"--network",
"mynet",
"--mount",
"type=bind,source=/home/gary/Source/geckolib/src/geckolib,target=/usr/local/python/lib/python3.10/site-packages/geckolib,readonly"
"type=bind,source=/home/gary/Source/geckolib/src/geckolib,target=/usr/local/python/lib/python3.10/site-packages/geckolib,readonly",
"--mount",
"type=bind,source=/home/gary/Source/geckolib,target=/usr/src/geckolib"
],
"extensions": [
"ms-python.python",
Expand All @@ -23,12 +25,15 @@
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.linting.pylintEnabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.defaultFormatter": null,
"python.formatting.provider": "black",
"files.trimTrailingWhitespace": true
}
}
9 changes: 6 additions & 3 deletions .devcontainer/local-devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"--network",
"mynet",
"--mount",
"type=bind,source=/home/gary/Source/geckolib/src/geckolib,target=/usr/lib/python3.8/site-packages/geckolib,readonly"
"type=bind,source=/home/gary/Source/geckolib/src/geckolib,target=/usr/local/python/lib/python3.10/site-packages/geckolib,readonly"
],
"extensions": [
"ms-python.python",
Expand All @@ -23,12 +23,15 @@
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.linting.pylintEnabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.defaultFormatter": null,
"python.formatting.provider": "black",
"files.trimTrailingWhitespace": true
}
}
125 changes: 60 additions & 65 deletions custom_components/gecko/__init__.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
"""
Custom integration to integrate Gecko with Home Assistant.
Custom integration to integrate Gecko Alliance spa with Home Assistant.
For more details about this integration, please refer to
https://github.com/gazoodle/gecko-home-assistant
"""
import asyncio
from datetime import timedelta
import logging
import uuid

from geckolib import GeckoLocator

from .spa_manager import GeckoSpaManager
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import (
CONF_SPA_ADDRESS,
CONF_SPA_IDENTIFIER,
CONF_CLIENT_ID,
CONF_SPA_NAME,
DOMAIN,
GECKOLIB_MANAGER_UUID,
PLATFORMS,
STARTUP_MESSAGE,
)

SCAN_INTERVAL = timedelta(seconds=30)
MAX_RETRIES = 5

_LOGGER = logging.getLogger(__name__)


Expand All @@ -32,6 +30,23 @@ async def async_setup(_hass: HomeAssistant, _config: Config):
return True


async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)

if config_entry.version == 1:

new = {**config_entry.data}
new[CONF_CLIENT_ID] = f"{uuid.uuid4()}"

config_entry.version = 2
hass.config_entries.async_update_entry(config_entry, data=new)

_LOGGER.debug("Migration to version %s successful", config_entry.version)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up this integration using UI."""
if hass.data.get(DOMAIN) is None:
Expand All @@ -40,79 +55,59 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

spa_identifier = None
spa_address = None
spa_name = None

if CONF_SPA_ADDRESS in entry.data:
spa_address = entry.data.get(CONF_SPA_ADDRESS)
if CONF_SPA_NAME in entry.data:
spa_name = entry.data.get(CONF_SPA_NAME)
spa_identifier = entry.data.get(CONF_SPA_IDENTIFIER)
client_id = entry.data.get(CONF_CLIENT_ID)

_LOGGER.debug(
"Setup entry for UUID %s, ID %s, address %s (%s)",
client_id,
spa_identifier,
spa_address,
spa_name,
)

_LOGGER.info("Setup entry for ID %s, address %s", spa_identifier, spa_address)

retry_count = 1
with GeckoLocator(
GECKOLIB_MANAGER_UUID, spa_to_find=spa_identifier, static_ip=spa_address
) as locator:
_LOGGER.info("Locator %s ready", locator)
try:
spa = None
spa = await hass.async_add_executor_job(
locator.get_spa_from_identifier, spa_identifier
)
facade = await hass.async_add_executor_job(spa.get_facade, False)
_LOGGER.info("Waiting for facade to be ready")
while not facade.is_connected:
await asyncio.sleep(0.1)
if facade.is_in_error:
facade.complete()
_LOGGER.warning("Facade went into error, lets retry")
retry_count = retry_count + 1
if retry_count >= MAX_RETRIES:
raise Exception("Too many retries")
facade = await hass.async_add_executor_job(spa.get_facade, False)

_LOGGER.info("Facade is ready")
datablock = GeckoDataBlock(facade, entry)
hass.data[DOMAIN][entry.entry_id] = datablock
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Exception during entry setup")
return False

for platform in datablock.platforms:
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(entry, platform)
)

entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
spaman = GeckoSpaManager(
client_id,
hass,
entry,
spa_identifier=spa_identifier,
spa_address=spa_address,
spa_name=spa_name,
)
await spaman.__aenter__()
# We always wait for the facade because otherwise the
# device info is not available for the first entity
if not await spaman.wait_for_facade():
_LOGGER.error(
"Failed to connect to spa %s address %s", spa_identifier, spa_address
)
raise ConfigEntryNotReady

hass.data[DOMAIN][entry.entry_id] = spaman

class GeckoDataBlock:
def __init__(self, facade, entry: ConfigEntry):
self.facade = facade
self.platforms = [
platform for platform in PLATFORMS if entry.options.get(platform, True)
]
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Handle removal of an entry."""
datablock = hass.data[DOMAIN][entry.entry_id]
unloaded = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in datablock.platforms
]
)
)
spaman: GeckoSpaManager = hass.data[DOMAIN][entry.entry_id]
unloaded = await spaman.unload_platforms()
if unloaded:
_LOGGER.debug("Finalize facade %r", datablock.facade)
datablock.facade.complete()
_LOGGER.debug("Close SpaMan")
await spaman.async_reset()
await spaman.__aexit__()
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Reload config entry."""
_LOGGER.info("async_reload_entry called")
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
11 changes: 6 additions & 5 deletions custom_components/gecko/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@

from .const import DOMAIN
from .entity import GeckoEntity
from .spa_manager import GeckoSpaManager


async def async_setup_entry(hass, entry, async_add_entities):
"""Setup binary_sensor platform."""
facade = hass.data[DOMAIN][entry.entry_id].facade
spaman: GeckoSpaManager = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[GeckoBinarySensor(entry, sensor) for sensor in facade.binary_sensors], True
[
GeckoBinarySensor(spaman, entry, sensor)
for sensor in spaman.facade.binary_sensors
]
)


class GeckoBinarySensor(GeckoEntity, BinarySensorEntity):
"""gecko binary_sensor class."""

def __init__(self, config_entry, automation_entity):
super().__init__(config_entry, automation_entity)

@property
def is_on(self):
"""Return true if the binary_sensor is on."""
Expand Down
31 changes: 31 additions & 0 deletions custom_components/gecko/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Button platform for Gecko."""
from homeassistant.components.button import ButtonEntity

from .const import DOMAIN
from .entity import GeckoEntity
from .spa_manager import GeckoSpaManager


async def async_setup_entry(hass, entry, async_add_entities):
"""Setup sensor platform."""
spaman: GeckoSpaManager = hass.data[DOMAIN][entry.entry_id]
if spaman.reconnect_button is not None:
async_add_entities([GeckoReconnectButton(entry, spaman)])


class GeckoButton(GeckoEntity, ButtonEntity):
"""Gecko button class."""

pass


class GeckoReconnectButton(GeckoButton):
def __init__(self, config_entry, spaman) -> None:
super().__init__(spaman, config_entry, spaman.reconnect_button)

async def async_press(self) -> None:
await self._automation_entity.async_press()

@property
def icon(self) -> str | None:
return "mdi:connection"
29 changes: 18 additions & 11 deletions custom_components/gecko/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,28 @@

from .const import DOMAIN
from .entity import GeckoEntity
from .spa_manager import GeckoSpaManager

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, entry, async_add_entities):
"""Setup sensor platform."""
facade = hass.data[DOMAIN][entry.entry_id].facade
"""Setup climate platform."""
spaman: GeckoSpaManager = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[GeckoClimate(entry, facade.water_heater, facade.water_care)], True
[
GeckoClimate(
spaman, entry, spaman.facade.water_heater, spaman.facade.water_care
)
]
)


class GeckoClimate(GeckoEntity, ClimateEntity):
"""Gecko Climate class."""

def __init__(self, config_entry, automation_entity, water_care):
super().__init__(config_entry, automation_entity)
def __init__(self, spaman, config_entry, automation_entity, water_care):
super().__init__(spaman, config_entry, automation_entity)
self._water_care = water_care
self._water_care.watch(self._on_change)

Expand All @@ -53,8 +58,8 @@ def hvac_modes(self):
def hvac_mode(self):
return HVAC_MODE_AUTO

def set_hvac_mode(self, hvac_mode):
del hvac_mode
def set_hvac_mode(self, _hvac_mode):
pass

@property
def hvac_action(self):
Expand All @@ -73,8 +78,8 @@ def preset_mode(self):
return "Waiting..."
return self._water_care.modes[self._water_care.mode]

def set_preset_mode(self, preset_mode):
self._water_care.set_mode(preset_mode)
async def async_set_preset_mode(self, preset_mode: str) -> None:
await self._water_care.async_set_mode(preset_mode)

@property
def temperature_unit(self):
Expand All @@ -96,6 +101,8 @@ def min_temp(self):
def max_temp(self):
return self._automation_entity.max_temp

def set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs) -> None:
"""Set the target temperature"""
self._automation_entity.set_target_temperature(kwargs["temperature"])
await self._automation_entity.async_set_target_temperature(
kwargs["temperature"]
)
Loading

0 comments on commit 3b9b75d

Please sign in to comment.