Skip to content

Commit

Permalink
Update to make more objects use the data coordinator
Browse files Browse the repository at this point in the history
  • Loading branch information
corporategoth committed Mar 19, 2022
1 parent a1e4faa commit bd6582a
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 127 deletions.
28 changes: 2 additions & 26 deletions custom_components/powerpetdoor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from .client import PowerPetDoorClient
Expand Down Expand Up @@ -37,13 +36,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
name = entry.data.get(CONF_NAME)
device_id = f"{host}:{port}"

timeout = entry.options.get(CONF_TIMEOUT, entry.data.get(CONF_TIMEOUT))
client = PowerPetDoorClient(
host=host,
port=port,
keepalive=entry.options.get(CONF_KEEP_ALIVE, entry.data.get(CONF_KEEP_ALIVE)),
timeout=timeout,
timeout=entry.options.get(CONF_TIMEOUT, entry.data.get(CONF_TIMEOUT)),
reconnect=entry.options.get(CONF_RECONNECT, entry.data.get(CONF_RECONNECT)),
keepalive=entry.options.get(CONF_KEEP_ALIVE, entry.data.get(CONF_KEEP_ALIVE)),
loop=hass.loop,
)

Expand All @@ -54,31 +52,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
name=name
)

async def async_update_data() -> str | None:
future = client.send_message(CONFIG, CMD_GET_DOOR_STATUS, notify=True)
if future is not None:
try:
return await client.wait_for(future, timeout)
except TimeoutError:
_LOGGER.error("Timed out waiting for status update!")
future.cancel()
return None
else:
return None

update = entry.options.get(CONF_UPDATE, entry.data.get(CONF_UPDATE))
coordinator = DataUpdateCoordinator(
hass=hass,
logger=_LOGGER,
name=name,
update_method=async_update_data,
update_interval=datetime.datetime(seconds=update) if update else None
)

hass.data[DOMAIN][device_id] = {
"client": client,
"device": device_info,
"coordinator": coordinator
}

hass.config_entries.async_setup_platforms(entry, PLATFORMS)
Expand Down
4 changes: 3 additions & 1 deletion custom_components/powerpetdoor/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ def connection_made(self, transport) -> None:
if self.cfg_keepalive:
self._keepalive = self.ensure_future(self.keepalive())

self.dequeue_data();

# Caller code
if self.on_connect:
self.on_connect()
Expand Down Expand Up @@ -295,7 +297,7 @@ async def check_receipt(self) -> None:

def enqueue_data(self, data) -> None:
self._queue.put(data)
if not self._check_receipt:
if self._transport and not self._check_receipt:
self.dequeue_data();

def dequeue_data(self) -> None:
Expand Down
6 changes: 3 additions & 3 deletions custom_components/powerpetdoor/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ def __init__(self, entry: config_entries.ConfigEntry) -> None:
self.entry = entry
options = entry.options
for schema in (PP_OPT_SCHEMA, PP_OPT_SCHEMA_ADV):
for entry in schema:
if entry["field"] not in options and entry["field"] in entry.data:
options[entry["field"]] = entry.data.get(entry["field"])
for ent in schema:
if ent["field"] not in options and ent["field"] in entry.data:
options[ent["field"]] = entry.data.get(ent["field"])

self.DATA_SCHEMA = vol.Schema(get_input_schema(PP_OPT_SCHEMA, defaults=entry.options))
self.DATA_SCHEMA_ADV = self.DATA_SCHEMA.extend(get_input_schema(PP_OPT_SCHEMA_ADV, defaults=entry.options))
Expand Down
2 changes: 1 addition & 1 deletion custom_components/powerpetdoor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"codeowners": [ "@corporategoth" ],
"iot_class": "local_push",
"config_flow": true,
"version": "0.2.2"
"version": "0.2.3"
}
143 changes: 70 additions & 73 deletions custom_components/powerpetdoor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import json
import copy
from datetime import datetime, timezone, timedelta

from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo
Expand Down Expand Up @@ -48,68 +49,68 @@
_LOGGER = logging.getLogger(__name__)

class PetDoorCoordinator(CoordinatorEntity, SensorEntity):
_attr_should_poll = False
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_native_unit_of_measurement = TIME_MILLISECONDS

settings = {}

update_settings_interval: float | None = None
_update_settings = None

last_change = None
def __init__(self,
coordinator: DataUpdateCoordinator,
hass: HomeAssistant,
client: PowerPetDoorClient,
name: str,
device: DeviceInfo | None = None,
update_settings_interval: float | None = None) -> None:
update_interval: float | None = None) -> None:
coordinator = DataUpdateCoordinator(
hass=hass,
logger=_LOGGER,
name=name,
update_method=self.update_method,
update_interval=timedelta(seconds=update_interval) if update_interval else None)

super().__init__(coordinator)
self.client = client
self.update_settings_interval = update_settings_interval

self.client.on_connect = self.on_connect
self.client.on_ping = self.on_ping
self.client.on_disconnect = self.on_disconnect

self._attr_name = coordinator.name
self._attr_device_info = device
self._attr_unique_id = f"{client.host}:{client.port}"

self.client.add_listener(name=self.unique_id,
door_status_update=coordinator.async_set_updated_data,
settings_update=self.handle_settings,
hw_info_update=self.handle_hw_info)

async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
self.client.start()
await self.coordinator.async_refresh()

async def async_will_remove_from_hass(self) -> None:
self.client.stop()
await super().async_will_remove_from_hass()

@callback
async def update_method(self) -> str:
_LOGGER.debug("Requesting update of door status")
future = self.send_message(CONFIG, CMD_GET_DOOR_STATUS, notify=True)
async def update_method(self) -> dict:
_LOGGER.debug("Requesting update of door settings")
future = self.client.send_message(CONFIG, CMD_GET_SETTINGS, notify=True)
return await future

@property
def available(self) -> bool:
return self.client.available
return (self.client.available and super().available)

@property
def icon(self) -> str | None:
if self.available:
if self.native_value is None:
return "mdi:lan-pending"
else:
if self.client.available:
if super().available:
return "mdi:lan-connect"
else:
return "mdi:lan-pending"
else:
return "mdi:lan-disconnect"

@property
def extra_state_attributes(self) -> dict | None:
rv = copy.deepcopy(self.settings)
rv = copy.deepcopy(self.coordinator.data if self.coordinator.data else {})
rv[CONF_HOST] = self.client.host
rv[CONF_PORT] = self.client.port
if self.coordinator.data:
Expand All @@ -118,31 +119,14 @@ def extra_state_attributes(self) -> dict | None:
rv[ATTR_HW_VERSION] = self.device_info[ATTR_HW_VERSION]
if ATTR_SW_VERSION in self.device_info:
rv[ATTR_SW_VERSION] = self.device_info[ATTR_SW_VERSION]
if self.last_change:
rv[STATE_LAST_CHANGE] = self.last_change.isoformat()
return rv

async def update_settings(self) -> None:
_update_settings = self._update_settings
await self.client.sleep(self.update_settings_interval)
if _update_settings and not _update_settings.cancelled():
# Wait between requesting info.
self.client.send_message(CONFIG, CMD_GET_HW_INFO)
await self.client.sleep(5.0)
self.client.send_message(CONFIG, CMD_GET_SETTINGS)
await self.client.sleep(5.0)
self.client.send_message(CONFIG, CMD_GET_DOOR_BATTERY)


def handle_settings(self, settings: dict) -> None:
if self._update_settings:
self._update_settings.cancel()
self._update_settings = None

self.settings = settings

_LOGGER.info("DOOR SETTINGS - {}".format(json.dumps(self.settings)))
self.async_schedule_update_ha_state(self.coordinator.data is None)
if self.update_settings_interval:
self._update_settings = self.client.ensure_future(self.update_settings())
@callback
def _handle_coordinator_update(self) -> None:
self.last_change = datetime.now(timezone.utc)
super()._handle_coordinator_update()

def handle_hw_info(self, fwinfo: dict) -> None:
hw_version = "{0} rev {1}".format(fwinfo[FIELD_FW_VER], fwinfo[FIELD_FW_REV])
Expand All @@ -156,51 +140,52 @@ def handle_hw_info(self, fwinfo: dict) -> None:
device = registry.async_get_device(identifiers=self.device_info[ATTR_IDENTIFIERS])
registry.async_update_device(device.id, hw_version=hw_version, sw_version=sw_version)


def on_connect(self) -> None:
self.client.send_message(CONFIG, CMD_GET_HW_INFO)
self.client.send_message(CONFIG, CMD_GET_SETTINGS)
self.client.send_message(CONFIG, CMD_GET_DOOR_BATTERY)

def on_disconnect(self) -> None:
if self._update_settings:
self._update_settings.cancel()
self._update_settings = None

def on_ping(self, value: int) -> None:
self._attr_native_value = value
self.async_schedule_update_ha_state()

class PetDoorBattery(SensorEntity):
class PetDoorBattery(CoordinatorEntity, SensorEntity):
_attr_device_class = SensorDeviceClass.BATTERY
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_native_unit_of_measurement = PERCENTAGE
_attr_should_poll = False

ac_present = False
battery_present = False

last_change = None
def __init__(self,
hass: HomeAssistant,
client: PowerPetDoorClient,
name: str,
device: DeviceInfo | None = None) -> None:
device: DeviceInfo | None = None,
update_interval: float | None= None) -> None:
coordinator = DataUpdateCoordinator(
hass=hass,
logger=_LOGGER,
name=name,
update_method=self.update_method,
update_interval=timedelta(seconds=update_interval) if update_interval else None)
super().__init__(coordinator)

self.client = client

self._attr_name = name
self._attr_device_info = device
self._attr_unique_id = f"{client.host}:{client.port}-battery"

client.add_listener(name=self.unique_id, battery_update=self.handle_battery_update)
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
await self.coordinator.async_refresh()

@callback
async def async_update(self) -> None:
async def update_method(self) -> dict:
_LOGGER.debug("Requesting update of door battery status")
self.client.send_message(CONFIG, CMD_GET_DOOR_BATTERY)
future = self.client.send_message(CONFIG, CMD_GET_DOOR_BATTERY, notify=True)
return await future

@property
def available(self) -> bool:
return (self.client.available and self.battery_present)
return (self.client.available and super().available and self.battery_present)

@property
def icon(self) -> str | None:
Expand Down Expand Up @@ -262,6 +247,11 @@ def icon(self) -> str | None:
else:
return "mdi:battery-off-outline"

@callback
def _handle_coordinator_update(self) -> None:
self.last_change = datetime.now(timezone.utc)
super()._handle_coordinator_update()

@property
def extra_state_attributes(self) -> dict | None:
rv = {}
Expand All @@ -275,13 +265,17 @@ def extra_state_attributes(self) -> dict | None:
rv[STATE_LAST_CHANGE] = self.last_change.isoformat()
return rv

def handle_battery_update(self, battery: dict) -> None:
if self._attr_native_value is not None and self._attr_native_value != battery[FIELD_BATTERY_PERCENT]:
self.last_change = datetime.now(timezone.utc)
self._attr_native_value = battery[FIELD_BATTERY_PERCENT]
self.battery_present = battery[FIELD_BATTERY_PRESENT]
self.ac_present = battery[FIELD_AC_PRESENT]
self.async_schedule_update_ha_state()
@property
def native_value(self) -> float:
return self.coordinator.data.get(FIELD_BATTERY_PERCENT) if self.coordinator.data else None

@property
def battery_present(self) -> bool:
return self.coordinator.data.get(FIELD_BATTERY_PRESENT) if self.coordinator.data else None

@property
def ac_present(self) -> bool:
return self.coordinator.data.get(FIELD_AC_PRESENT) if self.coordinator.data else None

# Right now this can be an alias for the above
async def async_setup_entry(hass: HomeAssistant,
Expand All @@ -296,11 +290,14 @@ async def async_setup_entry(hass: HomeAssistant,
obj = hass.data[DOMAIN][device_id]

async_add_entities([
PetDoorCoordinator(coordinator=obj["coordinator"],
PetDoorCoordinator(hass=hass,
client=obj["client"],
name=name,
device=obj["device"],
update_settings_interval=entry.options.get(CONF_REFRESH, entry.data.get(CONF_REFRESH))),
PetDoorBattery(client=obj["client"],
update_interval=entry.options.get(CONF_REFRESH, entry.data.get(CONF_REFRESH))),
PetDoorBattery(hass=hass,
client=obj["client"],
name=f"{name} - Battery",
device=obj["device"]),
device=obj["device"],
update_interval=entry.options.get(CONF_REFRESH, entry.data.get(CONF_REFRESH))),
])
Loading

0 comments on commit bd6582a

Please sign in to comment.