mirror of https://github.com/simbaja/ha_gehome.git
Merge pull request #16 from bendavis/water-filter-support
Add Support for GE Water Filters
This commit is contained in:
commit
459fc7f5cb
|
@ -5,16 +5,16 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from .const import (
|
||||
DOMAIN
|
||||
)
|
||||
from .const import DOMAIN
|
||||
from .update_coordinator import GeHomeUpdateCoordinator
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up the ge_home component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
@ -30,15 +30,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
ok = await coordinator.async_reset()
|
||||
if ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
async def async_update_options(hass, config_entry):
|
||||
"""Update options."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
|
|
@ -10,9 +10,11 @@ from .dishwasher import DishwasherApi
|
|||
from .washer import WasherApi
|
||||
from .dryer import DryerApi
|
||||
from .washer_dryer import WasherDryerApi
|
||||
from .waterfilter import WaterFilterApi
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_appliance_api_type(appliance_type: ErdApplianceType) -> Type:
|
||||
_LOGGER.debug(f"Found device type: {appliance_type}")
|
||||
"""Get the appropriate appliance type"""
|
||||
|
@ -28,5 +30,8 @@ def get_appliance_api_type(appliance_type: ErdApplianceType) -> Type:
|
|||
return DryerApi
|
||||
if appliance_type == ErdApplianceType.COMBINATION_WASHER_DRYER:
|
||||
return WasherDryerApi
|
||||
if appliance_type == ErdApplianceType.POE_WATER_FILTER:
|
||||
return WaterFilterApi
|
||||
|
||||
# Fallback
|
||||
return ApplianceApi
|
||||
|
|
|
@ -48,4 +48,4 @@ class WasherApi(ApplianceApi):
|
|||
if self.has_erd_code(ErdCode.LAUNDRY_WASHER_TANK_SELECTED):
|
||||
washer_entities.extend([GeErdSensor(self, ErdCode.LAUNDRY_WASHER_TANK_SELECTED)])
|
||||
|
||||
return washer_entities
|
||||
return washer_entities
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
from homeassistant.components.select import SelectEntity
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from gehomesdk.erd import ErdCode, ErdApplianceType
|
||||
|
||||
from .base import ApplianceApi
|
||||
from ..entities import (
|
||||
GeErdSensor,
|
||||
GeErdBinarySensor,
|
||||
ErdFlowRateSensor,
|
||||
ErdFilterLifeRemainingSensor,
|
||||
ErdFilterPositionSelect,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WaterFilterApi(ApplianceApi):
|
||||
"""API class for water filter objects"""
|
||||
|
||||
APPLIANCE_TYPE = ErdApplianceType.POE_WATER_FILTER
|
||||
|
||||
def get_all_entities(self) -> List[Entity]:
|
||||
base_entities = super().get_all_entities()
|
||||
|
||||
wf_entities = [
|
||||
GeErdSensor(self, ErdCode.WH_FILTER_MODE),
|
||||
GeErdSensor(self, ErdCode.WH_FILTER_VALVE_STATE),
|
||||
ErdFilterPositionSelect(self, ErdCode.WH_FILTER_POSITION),
|
||||
GeErdBinarySensor(self, ErdCode.WH_FILTER_MANUAL_MODE),
|
||||
ErdFlowRateSensor(self, ErdCode.WH_FILTER_FLOW_RATE),
|
||||
GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE),
|
||||
ErdFilterLifeRemainingSensor(self, ErdCode.WH_FILTER_LIFE_REMAINING),
|
||||
GeErdBinarySensor(self, ErdCode.WH_FILTER_FLOW_ALERT),
|
||||
]
|
||||
entities = base_entities + wf_entities
|
||||
return entities
|
|
@ -2,3 +2,4 @@ from .common import *
|
|||
from .dishwasher import *
|
||||
from .fridge import *
|
||||
from .oven import *
|
||||
from .waterfilter import *
|
||||
|
|
|
@ -5,4 +5,5 @@ from .ge_erd_property_binary_sensor import GeErdPropertyBinarySensor
|
|||
from .ge_erd_sensor import GeErdSensor
|
||||
from .ge_erd_property_sensor import GeErdPropertySensor
|
||||
from .ge_erd_switch import GeErdSwitch
|
||||
from .ge_water_heater import GeWaterHeater
|
||||
from .ge_water_heater import GeWaterHeater
|
||||
from .ge_erd_select import GeErdSelect
|
||||
|
|
|
@ -11,7 +11,15 @@ from .ge_entity import GeEntity
|
|||
|
||||
class GeErdEntity(GeEntity):
|
||||
"""Parent class for GE entities tied to a specific ERD"""
|
||||
def __init__(self, api: ApplianceApi, erd_code: ErdCodeType, erd_override: str = None, icon_override: str = None, device_class_override: str = None):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: ApplianceApi,
|
||||
erd_code: ErdCodeType,
|
||||
erd_override: str = None,
|
||||
icon_override: str = None,
|
||||
device_class_override: str = None,
|
||||
):
|
||||
super().__init__(api)
|
||||
self._erd_code = api.appliance.translate_erd_code(erd_code)
|
||||
self._erd_code_class = api.appliance.get_erd_code_class(self._erd_code)
|
||||
|
@ -21,11 +29,11 @@ class GeErdEntity(GeEntity):
|
|||
|
||||
if not self._erd_code_class:
|
||||
self._erd_code_class = ErdCodeClass.GENERAL
|
||||
|
||||
|
||||
@property
|
||||
def erd_code(self) -> ErdCodeType:
|
||||
return self._erd_code
|
||||
|
||||
|
||||
@property
|
||||
def erd_code_class(self) -> ErdCodeClass:
|
||||
return self._erd_code_class
|
||||
|
@ -40,8 +48,8 @@ class GeErdEntity(GeEntity):
|
|||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
erd_string = self.erd_string
|
||||
|
||||
#override the name if specified
|
||||
|
||||
# override the name if specified
|
||||
if self._erd_override != None:
|
||||
erd_string = self._erd_override
|
||||
|
||||
|
@ -53,10 +61,10 @@ class GeErdEntity(GeEntity):
|
|||
return f"{DOMAIN}_{self.serial_number}_{self.erd_string.lower()}"
|
||||
|
||||
def _stringify(self, value: any, **kwargs) -> Optional[str]:
|
||||
""" Stringify a value """
|
||||
"""Stringify a value"""
|
||||
# perform special processing before passing over to the default method
|
||||
if self.erd_code == ErdCode.CLOCK_TIME:
|
||||
return value.strftime("%H:%M:%S") if value else None
|
||||
return value.strftime("%H:%M:%S") if value else None
|
||||
if self.erd_code_class == ErdCodeClass.RAW_TEMPERATURE:
|
||||
return f"{value}"
|
||||
if self.erd_code_class == ErdCodeClass.NON_ZERO_TEMPERATURE:
|
||||
|
@ -106,5 +114,7 @@ class GeErdEntity(GeEntity):
|
|||
return "mdi:cup-water"
|
||||
if self.erd_code_class == ErdCodeClass.DISHWASHER_SENSOR:
|
||||
return "mdi:dishwasher"
|
||||
if self.erd_code_class == ErdCodeClass.WATERFILTER_SENSOR:
|
||||
return "mdi:water"
|
||||
|
||||
return None
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeErdSelect(SelectEntity):
|
||||
"""Switches for boolean ERD codes."""
|
||||
|
||||
device_class = "select"
|
|
@ -1,19 +1,21 @@
|
|||
from typing import Optional
|
||||
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from gehomesdk import ErdCode, ErdCodeClass, ErdMeasurementUnits
|
||||
|
||||
from .ge_erd_entity import GeErdEntity
|
||||
|
||||
class GeErdSensor(GeErdEntity, Entity):
|
||||
|
||||
class GeErdSensor(GeErdEntity, Entity):
|
||||
"""GE Entity for sensors"""
|
||||
|
||||
@property
|
||||
def state(self) -> Optional[str]:
|
||||
try:
|
||||
|
@ -35,15 +37,19 @@ class GeErdSensor(GeErdEntity, Entity):
|
|||
return TEMP_FAHRENHEIT
|
||||
|
||||
def _get_uom(self):
|
||||
""" Select appropriate units """
|
||||
"""Select appropriate units"""
|
||||
if (
|
||||
self.erd_code_class in [ErdCodeClass.RAW_TEMPERATURE,ErdCodeClass.NON_ZERO_TEMPERATURE] or
|
||||
self.device_class == DEVICE_CLASS_TEMPERATURE
|
||||
self.erd_code_class
|
||||
in [ErdCodeClass.RAW_TEMPERATURE, ErdCodeClass.NON_ZERO_TEMPERATURE]
|
||||
or self.device_class == DEVICE_CLASS_TEMPERATURE
|
||||
):
|
||||
if self._temp_measurement_system == ErdMeasurementUnits.METRIC:
|
||||
return TEMP_CELSIUS
|
||||
return TEMP_FAHRENHEIT
|
||||
if self.erd_code_class == ErdCodeClass.BATTERY or self.device_class == DEVICE_CLASS_BATTERY:
|
||||
if (
|
||||
self.erd_code_class == ErdCodeClass.BATTERY
|
||||
or self.device_class == DEVICE_CLASS_BATTERY
|
||||
):
|
||||
return "%"
|
||||
if self.erd_code_class == ErdCodeClass.PERCENTAGE:
|
||||
return "%"
|
||||
|
@ -54,7 +60,10 @@ class GeErdSensor(GeErdEntity, Entity):
|
|||
def _get_device_class(self) -> Optional[str]:
|
||||
if self._device_class_override:
|
||||
return self._device_class_override
|
||||
if self.erd_code_class in [ErdCodeClass.RAW_TEMPERATURE, ErdCodeClass.NON_ZERO_TEMPERATURE]:
|
||||
if self.erd_code_class in [
|
||||
ErdCodeClass.RAW_TEMPERATURE,
|
||||
ErdCodeClass.NON_ZERO_TEMPERATURE,
|
||||
]:
|
||||
return DEVICE_CLASS_TEMPERATURE
|
||||
if self.erd_code_class == ErdCodeClass.BATTERY:
|
||||
return DEVICE_CLASS_BATTERY
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from .flow_rate_sensor import ErdFlowRateSensor
|
||||
from .filter_life_remaining import ErdFilterLifeRemainingSensor
|
||||
from .filter_position import ErdFilterPositionSelect
|
|
@ -0,0 +1,19 @@
|
|||
from gehomesdk import ErdCode, ErdOperatingMode
|
||||
from gehomesdk.erd.values.common.erd_measurement_units import ErdMeasurementUnits
|
||||
from typing import Optional
|
||||
|
||||
from ..common import GeErdSensor
|
||||
|
||||
|
||||
class ErdFilterLifeRemainingSensor(GeErdSensor):
|
||||
@property
|
||||
def state(self) -> Optional[int]:
|
||||
try:
|
||||
value = self.appliance.get_erd_value(self.erd_code)
|
||||
except KeyError:
|
||||
return None
|
||||
return value.life_remaining
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> Optional[str]:
|
||||
return "%"
|
|
@ -0,0 +1,51 @@
|
|||
from homeassistant.components.ge_home.entities.common.ge_erd_select import GeErdSelect
|
||||
from homeassistant.components.ge_home.entities.common.ge_erd_entity import GeErdEntity
|
||||
from homeassistant.components.ge_home.devices.base import ApplianceApi
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from gehomesdk import ErdCode, ErdCodeClass, ErdCodeType
|
||||
from gehomesdk.erd.values.waterfilter.erd_waterfilter_position import (
|
||||
ErdWaterFilterPosition,
|
||||
)
|
||||
|
||||
from ...const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ErdFilterPositionSelect(GeErdEntity, GeErdSelect):
|
||||
def __init__(
|
||||
self,
|
||||
api: ApplianceApi,
|
||||
erd_code: ErdCodeType,
|
||||
):
|
||||
super().__init__(api=api, erd_code=erd_code)
|
||||
self._attr_hass = api._hass
|
||||
self.hass = api._hass
|
||||
self._attr_unique_id = self.unique_id
|
||||
self._attr_name = self.name
|
||||
self._attr_current_option = ErdWaterFilterPosition.UNKNOWN.name
|
||||
self._attr_icon = self.icon
|
||||
self._attr_device_class = self.device_class
|
||||
self._attr_options = [
|
||||
ErdWaterFilterPosition.BYPASS.name,
|
||||
ErdWaterFilterPosition.OFF.name,
|
||||
ErdWaterFilterPosition.FILTERED.name,
|
||||
ErdWaterFilterPosition.READY.name,
|
||||
]
|
||||
self._attr_device_info = self.device_info
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
if (
|
||||
option == ErdWaterFilterPosition.READY.name
|
||||
or option == ErdWaterFilterPosition.UNKNOWN.name
|
||||
):
|
||||
return
|
||||
await self.api.appliance.async_set_erd_value(
|
||||
self.erd_code, ErdWaterFilterPosition[option]
|
||||
)
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return "mdi:water"
|
|
@ -0,0 +1,21 @@
|
|||
from gehomesdk import ErdCode, ErdOperatingMode
|
||||
from gehomesdk.erd.values.common.erd_measurement_units import ErdMeasurementUnits
|
||||
from typing import Optional
|
||||
|
||||
from ..common import GeErdSensor
|
||||
|
||||
|
||||
class ErdFlowRateSensor(GeErdSensor):
|
||||
@property
|
||||
def state(self) -> Optional[float]:
|
||||
try:
|
||||
value = self.appliance.get_erd_value(self.erd_code)
|
||||
except KeyError:
|
||||
return None
|
||||
return value.flow_rate
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self) -> Optional[str]:
|
||||
if self._temp_measurement_system == ErdMeasurementUnits.METRIC:
|
||||
return "lpm"
|
||||
return "gpm"
|
|
@ -0,0 +1,38 @@
|
|||
"""GE Home Sensor Entities"""
|
||||
import async_timeout
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entities import GeErdSelect
|
||||
from .update_coordinator import GeHomeUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
):
|
||||
"""GE Home selects."""
|
||||
_LOGGER.debug("Adding GE Home selects")
|
||||
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
# This should be a NOP, but let's be safe
|
||||
with async_timeout.timeout(20):
|
||||
await coordinator.initialization_future
|
||||
_LOGGER.debug("Coordinator init future finished")
|
||||
|
||||
apis = list(coordinator.appliance_apis.values())
|
||||
_LOGGER.debug(f"Found {len(apis):d} appliance APIs")
|
||||
entities = [
|
||||
entity
|
||||
for api in apis
|
||||
for entity in api.entities
|
||||
if isinstance(entity, GeErdSelect)
|
||||
and entity.erd_code in api.appliance._property_cache
|
||||
]
|
||||
_LOGGER.debug(f"Found {len(entities):d} selectors")
|
||||
async_add_entities(entities)
|
|
@ -24,19 +24,20 @@ from homeassistant.core import HomeAssistant, callback
|
|||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
EVENT_ALL_APPLIANCES_READY,
|
||||
UPDATE_INTERVAL,
|
||||
MIN_RETRY_DELAY,
|
||||
MAX_RETRY_DELAY,
|
||||
DOMAIN,
|
||||
EVENT_ALL_APPLIANCES_READY,
|
||||
UPDATE_INTERVAL,
|
||||
MIN_RETRY_DELAY,
|
||||
MAX_RETRY_DELAY,
|
||||
RETRY_OFFLINE_COUNT,
|
||||
ASYNC_TIMEOUT
|
||||
ASYNC_TIMEOUT,
|
||||
)
|
||||
from .devices import ApplianceApi, get_appliance_api_type
|
||||
|
||||
PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater"]
|
||||
PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater", "select"]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define a wrapper class to update GE Home data."""
|
||||
|
||||
|
@ -65,15 +66,23 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
self._retry_count = 0
|
||||
self.initialization_future = asyncio.Future()
|
||||
|
||||
def create_ge_client(self, event_loop: Optional[asyncio.AbstractEventLoop]) -> GeWebsocketClient:
|
||||
def create_ge_client(
|
||||
self, event_loop: Optional[asyncio.AbstractEventLoop]
|
||||
) -> GeWebsocketClient:
|
||||
"""
|
||||
Create a new GeClient object with some helpful callbacks.
|
||||
|
||||
:param event_loop: Event loop
|
||||
:return: GeWebsocketClient
|
||||
"""
|
||||
client = GeWebsocketClient(self._username, self._password, event_loop=event_loop, )
|
||||
client.add_event_handler(EVENT_APPLIANCE_INITIAL_UPDATE, self.on_device_initial_update)
|
||||
client = GeWebsocketClient(
|
||||
self._username,
|
||||
self._password,
|
||||
event_loop=event_loop,
|
||||
)
|
||||
client.add_event_handler(
|
||||
EVENT_APPLIANCE_INITIAL_UPDATE, self.on_device_initial_update
|
||||
)
|
||||
client.add_event_handler(EVENT_APPLIANCE_UPDATE_RECEIVED, self.on_device_update)
|
||||
client.add_event_handler(EVENT_GOT_APPLIANCE_LIST, self.on_appliance_list)
|
||||
client.add_event_handler(EVENT_DISCONNECTED, self.on_disconnect)
|
||||
|
@ -87,10 +96,10 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
@property
|
||||
def appliance_apis(self) -> Dict[str, ApplianceApi]:
|
||||
return self._appliance_apis
|
||||
|
||||
|
||||
@property
|
||||
def online(self) -> bool:
|
||||
"""
|
||||
"""
|
||||
Indicates whether the services is online. If it's retried several times, it's assumed
|
||||
that it's offline for some reason
|
||||
"""
|
||||
|
@ -116,15 +125,17 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
def maybe_add_appliance_api(self, appliance: GeAppliance):
|
||||
mac_addr = appliance.mac_addr
|
||||
if mac_addr not in self.appliance_apis:
|
||||
_LOGGER.debug(f"Adding appliance api for appliance {mac_addr} ({appliance.appliance_type})")
|
||||
_LOGGER.debug(
|
||||
f"Adding appliance api for appliance {mac_addr} ({appliance.appliance_type})"
|
||||
)
|
||||
api = self._get_appliance_api(appliance)
|
||||
api.build_entities_list()
|
||||
self.appliance_apis[mac_addr] = api
|
||||
else:
|
||||
#if we already have the API, switch out its appliance reference for this one
|
||||
# if we already have the API, switch out its appliance reference for this one
|
||||
api = self.appliance_apis[mac_addr]
|
||||
api.appliance = appliance
|
||||
|
||||
api.appliance = appliance
|
||||
|
||||
async def get_client(self) -> GeWebsocketClient:
|
||||
"""Get a new GE Websocket client."""
|
||||
if self.client:
|
||||
|
@ -132,10 +143,10 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
self.client.clear_event_handlers()
|
||||
await self.client.disconnect()
|
||||
except Exception as err:
|
||||
_LOGGER.warn(f'exception while disconnecting client {err}')
|
||||
_LOGGER.warn(f"exception while disconnecting client {err}")
|
||||
finally:
|
||||
self._reset_initialization()
|
||||
|
||||
|
||||
loop = self._hass.loop
|
||||
self.client = self.create_ge_client(event_loop=loop)
|
||||
return self.client
|
||||
|
@ -146,44 +157,48 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
|
||||
for component in PLATFORMS:
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_forward_entry_setup(self._config_entry, component)
|
||||
self.hass.config_entries.async_forward_entry_setup(
|
||||
self._config_entry, component
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
await self.async_start_client()
|
||||
except (GeNotAuthenticatedError, GeAuthFailedError):
|
||||
raise HaAuthError('Authentication failure')
|
||||
raise HaAuthError("Authentication failure")
|
||||
except GeGeneralServerError:
|
||||
raise HaCannotConnect('Cannot connect (server error)')
|
||||
raise HaCannotConnect("Cannot connect (server error)")
|
||||
except Exception:
|
||||
raise HaCannotConnect('Unknown connection failure')
|
||||
raise HaCannotConnect("Unknown connection failure")
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(ASYNC_TIMEOUT):
|
||||
await self.initialization_future
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
raise HaCannotConnect('Initialization timed out')
|
||||
raise HaCannotConnect("Initialization timed out")
|
||||
|
||||
return True
|
||||
|
||||
async def async_start_client(self):
|
||||
"""Start a new GeClient in the HASS event loop."""
|
||||
try:
|
||||
_LOGGER.debug('Creating and starting client')
|
||||
_LOGGER.debug("Creating and starting client")
|
||||
await self.get_client()
|
||||
await self.async_begin_session()
|
||||
except:
|
||||
_LOGGER.debug('could not start the client')
|
||||
_LOGGER.debug("could not start the client")
|
||||
self.client = None
|
||||
raise
|
||||
|
||||
|
||||
async def async_begin_session(self):
|
||||
"""Begins the ge_home session."""
|
||||
_LOGGER.debug("Beginning session")
|
||||
session = self._hass.helpers.aiohttp_client.async_get_clientsession()
|
||||
await self.client.async_get_credentials(session)
|
||||
fut = asyncio.ensure_future(self.client.async_run_client(), loop=self._hass.loop)
|
||||
_LOGGER.debug('Client running')
|
||||
fut = asyncio.ensure_future(
|
||||
self.client.async_run_client(), loop=self._hass.loop
|
||||
)
|
||||
_LOGGER.debug("Client running")
|
||||
return fut
|
||||
|
||||
async def async_reset(self):
|
||||
|
@ -193,17 +208,19 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
self.hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
self.hass.config_entries.async_forward_entry_unload(
|
||||
entry, component
|
||||
)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
return unload_ok
|
||||
return unload_ok
|
||||
|
||||
async def _kill_client(self):
|
||||
"""Kill the client. Leaving this in for testing purposes."""
|
||||
await asyncio.sleep(30)
|
||||
_LOGGER.critical('Killing the connection. Popcorn time.')
|
||||
_LOGGER.critical("Killing the connection. Popcorn time.")
|
||||
await self.client.disconnect()
|
||||
|
||||
@callback
|
||||
|
@ -216,13 +233,17 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
async def async_reconnect(self) -> None:
|
||||
"""Try to reconnect ge_home session."""
|
||||
self._retry_count += 1
|
||||
_LOGGER.info(f"attempting to reconnect to ge_home service (attempt {self._retry_count})")
|
||||
|
||||
_LOGGER.info(
|
||||
f"attempting to reconnect to ge_home service (attempt {self._retry_count})"
|
||||
)
|
||||
|
||||
try:
|
||||
with async_timeout.timeout(ASYNC_TIMEOUT):
|
||||
await self.async_start_client()
|
||||
except Exception as err:
|
||||
_LOGGER.warn(f"could not reconnect: {err}, will retry in {self._get_retry_delay()} seconds")
|
||||
_LOGGER.warn(
|
||||
f"could not reconnect: {err}, will retry in {self._get_retry_delay()} seconds"
|
||||
)
|
||||
self.hass.loop.call_later(self._get_retry_delay(), self.reconnect)
|
||||
_LOGGER.debug("forcing a state refresh while disconnected")
|
||||
try:
|
||||
|
@ -249,21 +270,23 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
except KeyError:
|
||||
return
|
||||
for entity in api.entities:
|
||||
_LOGGER.debug(f'Updating {entity} ({entity.unique_id}, {entity.entity_id})')
|
||||
_LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})")
|
||||
entity.async_write_ha_state()
|
||||
|
||||
async def _refresh_ha_state(self):
|
||||
entities = [
|
||||
entity
|
||||
for api in self.appliance_apis.values()
|
||||
for entity in api.entities
|
||||
entity for api in self.appliance_apis.values() for entity in api.entities
|
||||
]
|
||||
for entity in entities:
|
||||
try:
|
||||
_LOGGER.debug(f'Refreshing state for {entity} ({entity.unique_id}, {entity.entity_id}')
|
||||
_LOGGER.debug(
|
||||
f"Refreshing state for {entity} ({entity.unique_id}, {entity.entity_id}"
|
||||
)
|
||||
entity.async_write_ha_state()
|
||||
except:
|
||||
_LOGGER.debug(f'Could not refresh state for {entity} ({entity.unique_id}, {entity.entity_id}')
|
||||
_LOGGER.debug(
|
||||
f"Could not refresh state for {entity} ({entity.unique_id}, {entity.entity_id}"
|
||||
)
|
||||
|
||||
@property
|
||||
def all_appliances_updated(self) -> bool:
|
||||
|
@ -272,31 +295,35 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
|
||||
async def on_appliance_list(self, _):
|
||||
"""When we get an appliance list, mark it and maybe trigger all ready."""
|
||||
_LOGGER.debug('Got roster update')
|
||||
_LOGGER.debug("Got roster update")
|
||||
self.last_update_success = True
|
||||
if not self._got_roster:
|
||||
self._got_roster = True
|
||||
#TODO: Probably should have a better way of confirming we're good to go...
|
||||
await asyncio.sleep(5) # After the initial roster update, wait a bit and hit go
|
||||
# TODO: Probably should have a better way of confirming we're good to go...
|
||||
await asyncio.sleep(
|
||||
5
|
||||
) # After the initial roster update, wait a bit and hit go
|
||||
await self.async_maybe_trigger_all_ready()
|
||||
|
||||
async def on_device_initial_update(self, appliance: GeAppliance):
|
||||
"""When an appliance first becomes ready, let the system know and schedule periodic updates."""
|
||||
_LOGGER.debug(f'Got initial update for {appliance.mac_addr}')
|
||||
_LOGGER.debug(f"Got initial update for {appliance.mac_addr}")
|
||||
self.last_update_success = True
|
||||
self.maybe_add_appliance_api(appliance)
|
||||
await self.async_maybe_trigger_all_ready()
|
||||
_LOGGER.debug(f'Requesting updates for {appliance.mac_addr}')
|
||||
_LOGGER.debug(f"Requesting updates for {appliance.mac_addr}")
|
||||
while self.connected:
|
||||
await asyncio.sleep(UPDATE_INTERVAL)
|
||||
if self.connected and self.client.available:
|
||||
await appliance.async_request_update()
|
||||
|
||||
_LOGGER.debug(f'No longer requesting updates for {appliance.mac_addr}')
|
||||
_LOGGER.debug(f"No longer requesting updates for {appliance.mac_addr}")
|
||||
|
||||
async def on_disconnect(self, _):
|
||||
"""Handle disconnection."""
|
||||
_LOGGER.debug(f"Disconnected. Attempting to reconnect in {MIN_RETRY_DELAY} seconds")
|
||||
_LOGGER.debug(
|
||||
f"Disconnected. Attempting to reconnect in {MIN_RETRY_DELAY} seconds"
|
||||
)
|
||||
self.last_update_success = False
|
||||
self.hass.loop.call_later(MIN_RETRY_DELAY, self.reconnect, True)
|
||||
|
||||
|
@ -311,7 +338,9 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
# Been here, done this
|
||||
return
|
||||
if self._got_roster and self.all_appliances_updated:
|
||||
_LOGGER.debug('Ready to go. Waiting 2 seconds and setting init future result.')
|
||||
_LOGGER.debug(
|
||||
"Ready to go. Waiting 2 seconds and setting init future result."
|
||||
)
|
||||
# The the flag and wait to prevent two different fun race conditions
|
||||
self._init_done = True
|
||||
await asyncio.sleep(2)
|
||||
|
@ -320,4 +349,4 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
|||
|
||||
def _get_retry_delay(self) -> int:
|
||||
delay = MIN_RETRY_DELAY * 2 ** (self._retry_count - 1)
|
||||
return min(delay, MAX_RETRY_DELAY)
|
||||
return min(delay, MAX_RETRY_DELAY)
|
||||
|
|
Loading…
Reference in New Issue