Add binary_sensor for elevator states to hvv_departures (#36822)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Tom Schneider 2020-09-22 17:42:50 +02:00 committed by GitHub
parent 0400754270
commit 6e8e4eedb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 207 additions and 4 deletions

View file

@ -374,6 +374,7 @@ omit =
homeassistant/components/hunterdouglas_powerview/sensor.py
homeassistant/components/hunterdouglas_powerview/cover.py
homeassistant/components/hunterdouglas_powerview/entity.py
homeassistant/components/hvv_departures/binary_sensor.py
homeassistant/components/hvv_departures/sensor.py
homeassistant/components/hvv_departures/__init__.py
homeassistant/components/hydrawise/*

View file

@ -1,6 +1,7 @@
"""The HVV integration."""
import asyncio
from homeassistant.components.binary_sensor import DOMAIN as DOMAIN_BINARY_SENSOR
from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
@ -10,7 +11,7 @@ from homeassistant.helpers import aiohttp_client
from .const import DOMAIN
from .hub import GTIHub
PLATFORMS = [DOMAIN_SENSOR]
PLATFORMS = [DOMAIN_SENSOR, DOMAIN_BINARY_SENSOR]
async def async_setup(hass: HomeAssistant, config: dict):

View file

@ -0,0 +1,201 @@
"""Binary sensor platform for hvv_departures."""
from datetime import timedelta
import logging
from aiohttp import ClientConnectorError
import async_timeout
from pygti.exceptions import InvalidAuth
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_PROBLEM,
BinarySensorEntity,
)
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import ATTRIBUTION, CONF_STATION, DOMAIN, MANUFACTURER
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the binary_sensor platform."""
hub = hass.data[DOMAIN][entry.entry_id]
station_name = entry.data[CONF_STATION]["name"]
station = entry.data[CONF_STATION]
def get_elevator_entities_from_station_information(
station_name, station_information
):
"""Convert station information into a list of elevators."""
elevators = {}
if station_information is None:
return {}
for partial_station in station_information.get("partialStations", []):
for elevator in partial_station.get("elevators", []):
state = elevator.get("state") != "READY"
available = elevator.get("state") != "UNKNOWN"
label = elevator.get("label")
description = elevator.get("description")
if label is not None:
name = f"Elevator {label} at {station_name}"
else:
name = f"Unknown elevator at {station_name}"
if description is not None:
name += f" ({description})"
lines = elevator.get("lines")
idx = f"{station_name}-{label}-{lines}"
elevators[idx] = {
"state": state,
"name": name,
"available": available,
"attributes": {
"cabin_width": elevator.get("cabinWidth"),
"cabin_length": elevator.get("cabinLength"),
"door_width": elevator.get("doorWidth"),
"elevator_type": elevator.get("elevatorType"),
"button_type": elevator.get("buttonType"),
"cause": elevator.get("cause"),
"lines": lines,
ATTR_ATTRIBUTION: ATTRIBUTION,
},
}
return elevators
async def async_update_data():
"""Fetch data from API endpoint.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
"""
payload = {"station": station}
try:
async with async_timeout.timeout(10):
return get_elevator_entities_from_station_information(
station_name, await hub.gti.stationInformation(payload)
)
except InvalidAuth as err:
raise UpdateFailed(f"Authentication failed: {err}") from err
except ClientConnectorError as err:
raise UpdateFailed(f"Network not available: {err}") from err
except Exception as err: # pylint: disable=broad-except
raise UpdateFailed(f"Error occurred while fetching data: {err}") from err
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
# Name of the data. For logging purposes.
name="hvv_departures.binary_sensor",
update_method=async_update_data,
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(hours=1),
)
# Fetch initial data so we have data when entities subscribe
await coordinator.async_refresh()
async_add_entities(
HvvDepartureBinarySensor(coordinator, idx, entry)
for (idx, ent) in coordinator.data.items()
)
class HvvDepartureBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""HVVDepartureBinarySensor class."""
def __init__(self, coordinator, idx, config_entry):
"""Initialize."""
super().__init__(coordinator)
self.coordinator = coordinator
self.idx = idx
self.config_entry = config_entry
@property
def is_on(self):
"""Return entity state."""
return self.coordinator.data[self.idx]["state"]
@property
def should_poll(self):
"""No need to poll. Coordinator notifies entity of updates."""
return False
@property
def available(self):
"""Return if entity is available."""
return (
self.coordinator.last_update_success
and self.coordinator.data[self.idx]["available"]
)
@property
def device_info(self):
"""Return the device info for this sensor."""
return {
"identifiers": {
(
DOMAIN,
self.config_entry.entry_id,
self.config_entry.data[CONF_STATION]["id"],
self.config_entry.data[CONF_STATION]["type"],
)
},
"name": f"Departures at {self.config_entry.data[CONF_STATION]['name']}",
"manufacturer": MANUFACTURER,
}
@property
def name(self):
"""Return the name of the sensor."""
return self.coordinator.data[self.idx]["name"]
@property
def unique_id(self):
"""Return a unique ID to use for this sensor."""
return self.idx
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return DEVICE_CLASS_PROBLEM
@property
def device_state_attributes(self):
"""Return the state attributes."""
if not (
self.coordinator.last_update_success
and self.coordinator.data[self.idx]["available"]
):
return None
return {
k: v
for k, v in self.coordinator.data[self.idx]["attributes"].items()
if v is not None
}
async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_update(self):
"""Update the entity.
Only used by the generic entity update service.
"""
await self.coordinator.async_request_refresh()

View file

@ -267,7 +267,7 @@ async def test_options_flow(hass):
)
config_entry.add_to_hass(hass)
with patch(
with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch(
"homeassistant.components.hvv_departures.hub.GTI.init",
return_value=True,
), patch(
@ -318,7 +318,7 @@ async def test_options_flow_invalid_auth(hass):
)
config_entry.add_to_hass(hass)
with patch(
with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch(
"homeassistant.components.hvv_departures.hub.GTI.init", return_value=True
), patch(
"homeassistant.components.hvv_departures.hub.GTI.departureList",
@ -359,7 +359,7 @@ async def test_options_flow_cannot_connect(hass):
)
config_entry.add_to_hass(hass)
with patch(
with patch("homeassistant.components.hvv_departures.PLATFORMS", new=[]), patch(
"homeassistant.components.hvv_departures.hub.GTI.init", return_value=True
), patch(
"homeassistant.components.hvv_departures.hub.GTI.departureList",