From d7809c5398ac04a70fbe3dabb4832e51c27d037b Mon Sep 17 00:00:00 2001 From: Erik Eriksson <8228319+molobrakos@users.noreply.github.com> Date: Fri, 30 Nov 2018 19:07:42 +0100 Subject: [PATCH] Update of volvooncall component (#18702) --- .../components/binary_sensor/volvooncall.py | 21 +- .../components/device_tracker/volvooncall.py | 31 ++- homeassistant/components/lock/volvooncall.py | 17 +- .../components/sensor/volvooncall.py | 43 +---- .../components/switch/volvooncall.py | 22 +-- homeassistant/components/volvooncall.py | 180 ++++++++++++------ requirements_all.txt | 2 +- 7 files changed, 175 insertions(+), 141 deletions(-) diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py index e70d3098874..e7092ff16d5 100644 --- a/homeassistant/components/binary_sensor/volvooncall.py +++ b/homeassistant/components/binary_sensor/volvooncall.py @@ -6,17 +6,19 @@ https://home-assistant.io/components/binary_sensor.volvooncall/ """ import logging -from homeassistant.components.volvooncall import VolvoEntity -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, DEVICE_CLASSES) _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): """Set up the Volvo sensors.""" if discovery_info is None: return - add_entities([VolvoSensor(hass, *discovery_info)]) + async_add_entities([VolvoSensor(hass.data[DATA_KEY], *discovery_info)]) class VolvoSensor(VolvoEntity, BinarySensorDevice): @@ -25,14 +27,11 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice): @property def is_on(self): """Return True if the binary sensor is on.""" - val = getattr(self.vehicle, self._attribute) - if self._attribute == 'bulb_failures': - return bool(val) - if self._attribute in ['doors', 'windows']: - return any([val[key] for key in val if 'Open' in key]) - return val != 'Normal' + return self.instrument.is_on @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'safety' + if self.instrument.device_class in DEVICE_CLASSES: + return self.instrument.device_class + return None diff --git a/homeassistant/components/device_tracker/volvooncall.py b/homeassistant/components/device_tracker/volvooncall.py index 7872f8f1f1c..395b539a065 100644 --- a/homeassistant/components/device_tracker/volvooncall.py +++ b/homeassistant/components/device_tracker/volvooncall.py @@ -7,33 +7,32 @@ https://home-assistant.io/components/device_tracker.volvooncall/ import logging from homeassistant.util import slugify -from homeassistant.helpers.dispatcher import ( - dispatcher_connect, dispatcher_send) -from homeassistant.components.volvooncall import DATA_KEY, SIGNAL_VEHICLE_SEEN +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.components.device_tracker import SOURCE_TYPE_GPS +from homeassistant.components.volvooncall import DATA_KEY, SIGNAL_STATE_UPDATED _LOGGER = logging.getLogger(__name__) -def setup_scanner(hass, config, see, discovery_info=None): +async def async_setup_scanner(hass, config, async_see, discovery_info=None): """Set up the Volvo tracker.""" if discovery_info is None: return - vin, _ = discovery_info - voc = hass.data[DATA_KEY] - vehicle = voc.vehicles[vin] + vin, component, attr = discovery_info + data = hass.data[DATA_KEY] + instrument = data.instrument(vin, component, attr) - def see_vehicle(vehicle): + async def see_vehicle(): """Handle the reporting of the vehicle position.""" - host_name = voc.vehicle_name(vehicle) + host_name = instrument.vehicle_name dev_id = 'volvo_{}'.format(slugify(host_name)) - see(dev_id=dev_id, - host_name=host_name, - gps=(vehicle.position['latitude'], - vehicle.position['longitude']), - icon='mdi:car') + await async_see(dev_id=dev_id, + host_name=host_name, + source_type=SOURCE_TYPE_GPS, + gps=instrument.state, + icon='mdi:car') - dispatcher_connect(hass, SIGNAL_VEHICLE_SEEN, see_vehicle) - dispatcher_send(hass, SIGNAL_VEHICLE_SEEN, vehicle) + async_dispatcher_connect(hass, SIGNAL_STATE_UPDATED, see_vehicle) return True diff --git a/homeassistant/components/lock/volvooncall.py b/homeassistant/components/lock/volvooncall.py index 58fa83cef30..83301aa3d4e 100644 --- a/homeassistant/components/lock/volvooncall.py +++ b/homeassistant/components/lock/volvooncall.py @@ -7,17 +7,18 @@ https://home-assistant.io/components/lock.volvooncall/ import logging from homeassistant.components.lock import LockDevice -from homeassistant.components.volvooncall import VolvoEntity +from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): """Set up the Volvo On Call lock.""" if discovery_info is None: return - add_entities([VolvoLock(hass, *discovery_info)]) + async_add_entities([VolvoLock(hass.data[DATA_KEY], *discovery_info)]) class VolvoLock(VolvoEntity, LockDevice): @@ -26,12 +27,12 @@ class VolvoLock(VolvoEntity, LockDevice): @property def is_locked(self): """Return true if lock is locked.""" - return self.vehicle.is_locked + return self.instrument.is_locked - def lock(self, **kwargs): + async def async_lock(self, **kwargs): """Lock the car.""" - self.vehicle.lock() + await self.instrument.lock() - def unlock(self, **kwargs): + async def async_unlock(self, **kwargs): """Unlock the car.""" - self.vehicle.unlock() + await self.instrument.unlock() diff --git a/homeassistant/components/sensor/volvooncall.py b/homeassistant/components/sensor/volvooncall.py index a3f0c55b954..65b996a5bd5 100644 --- a/homeassistant/components/sensor/volvooncall.py +++ b/homeassistant/components/sensor/volvooncall.py @@ -6,19 +6,18 @@ https://home-assistant.io/components/sensor.volvooncall/ """ import logging -from math import floor -from homeassistant.components.volvooncall import ( - VolvoEntity, RESOURCES, CONF_SCANDINAVIAN_MILES) +from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): """Set up the Volvo sensors.""" if discovery_info is None: return - add_entities([VolvoSensor(hass, *discovery_info)]) + async_add_entities([VolvoSensor(hass.data[DATA_KEY], *discovery_info)]) class VolvoSensor(VolvoEntity): @@ -26,38 +25,10 @@ class VolvoSensor(VolvoEntity): @property def state(self): - """Return the state of the sensor.""" - val = getattr(self.vehicle, self._attribute) - - if val is None: - return val - - if self._attribute == 'odometer': - val /= 1000 # m -> km - - if 'mil' in self.unit_of_measurement: - val /= 10 # km -> mil - - if self._attribute == 'average_fuel_consumption': - val /= 10 # L/1000km -> L/100km - if 'mil' in self.unit_of_measurement: - return round(val, 2) - return round(val, 1) - if self._attribute == 'distance_to_empty': - return int(floor(val)) - return int(round(val)) + """Return the state.""" + return self.instrument.state @property def unit_of_measurement(self): """Return the unit of measurement.""" - unit = RESOURCES[self._attribute][3] - if self._state.config[CONF_SCANDINAVIAN_MILES] and 'km' in unit: - if self._attribute == 'average_fuel_consumption': - return 'L/mil' - return unit.replace('km', 'mil') - return unit - - @property - def icon(self): - """Return the icon.""" - return RESOURCES[self._attribute][2] + return self.instrument.unit diff --git a/homeassistant/components/switch/volvooncall.py b/homeassistant/components/switch/volvooncall.py index 42c753725ab..81abf7d0e6c 100644 --- a/homeassistant/components/switch/volvooncall.py +++ b/homeassistant/components/switch/volvooncall.py @@ -8,17 +8,18 @@ https://home-assistant.io/components/switch.volvooncall/ """ import logging -from homeassistant.components.volvooncall import VolvoEntity, RESOURCES +from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY from homeassistant.helpers.entity import ToggleEntity _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): """Set up a Volvo switch.""" if discovery_info is None: return - add_entities([VolvoSwitch(hass, *discovery_info)]) + async_add_entities([VolvoSwitch(hass.data[DATA_KEY], *discovery_info)]) class VolvoSwitch(VolvoEntity, ToggleEntity): @@ -27,17 +28,12 @@ class VolvoSwitch(VolvoEntity, ToggleEntity): @property def is_on(self): """Return true if switch is on.""" - return self.vehicle.is_heater_on + return self.instrument.state - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Turn the switch on.""" - self.vehicle.start_heater() + await self.instrument.turn_on() - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Turn the switch off.""" - self.vehicle.stop_heater() - - @property - def icon(self): - """Return the icon.""" - return RESOURCES[self._attribute][2] + await self.instrument.turn_off() diff --git a/homeassistant/components/volvooncall.py b/homeassistant/components/volvooncall.py index 0ce8870bedf..fe7ec460674 100644 --- a/homeassistant/components/volvooncall.py +++ b/homeassistant/components/volvooncall.py @@ -14,15 +14,17 @@ from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import track_point_in_utc_time -from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_point_in_utc_time +from homeassistant.helpers.dispatcher import ( + async_dispatcher_send, + async_dispatcher_connect) from homeassistant.util.dt import utcnow DOMAIN = 'volvooncall' DATA_KEY = DOMAIN -REQUIREMENTS = ['volvooncall==0.4.0'] +REQUIREMENTS = ['volvooncall==0.7.4'] _LOGGER = logging.getLogger(__name__) @@ -33,25 +35,56 @@ CONF_UPDATE_INTERVAL = 'update_interval' CONF_REGION = 'region' CONF_SERVICE_URL = 'service_url' CONF_SCANDINAVIAN_MILES = 'scandinavian_miles' +CONF_MUTABLE = 'mutable' -SIGNAL_VEHICLE_SEEN = '{}.vehicle_seen'.format(DOMAIN) +SIGNAL_STATE_UPDATED = '{}.updated'.format(DOMAIN) -RESOURCES = {'position': ('device_tracker',), - 'lock': ('lock', 'Lock'), - 'heater': ('switch', 'Heater', 'mdi:radiator'), - 'odometer': ('sensor', 'Odometer', 'mdi:speedometer', 'km'), - 'fuel_amount': ('sensor', 'Fuel amount', 'mdi:gas-station', 'L'), - 'fuel_amount_level': ( - 'sensor', 'Fuel level', 'mdi:water-percent', '%'), - 'average_fuel_consumption': ( - 'sensor', 'Fuel consumption', 'mdi:gas-station', 'L/100 km'), - 'distance_to_empty': ('sensor', 'Range', 'mdi:ruler', 'km'), - 'washer_fluid_level': ('binary_sensor', 'Washer fluid'), - 'brake_fluid': ('binary_sensor', 'Brake Fluid'), - 'service_warning_status': ('binary_sensor', 'Service'), - 'bulb_failures': ('binary_sensor', 'Bulbs'), - 'doors': ('binary_sensor', 'Doors'), - 'windows': ('binary_sensor', 'Windows')} +COMPONENTS = { + 'sensor': 'sensor', + 'binary_sensor': 'binary_sensor', + 'lock': 'lock', + 'device_tracker': 'device_tracker', + 'switch': 'switch' +} + +RESOURCES = [ + 'position', + 'lock', + 'heater', + 'odometer', + 'trip_meter1', + 'trip_meter2', + 'fuel_amount', + 'fuel_amount_level', + 'average_fuel_consumption', + 'distance_to_empty', + 'washer_fluid_level', + 'brake_fluid', + 'service_warning_status', + 'bulb_failures', + 'battery_range', + 'battery_level', + 'time_to_fully_charged', + 'battery_charge_status', + 'engine_start', + 'last_trip', + 'is_engine_running', + 'doors.hood_open', + 'doors.front_left_door_open', + 'doors.front_right_door_open', + 'doors.rear_left_door_open', + 'doors.rear_right_door_open', + 'windows.front_left_window_open', + 'windows.front_right_window_open', + 'windows.rear_left_window_open', + 'windows.rear_right_window_open', + 'tyre_pressure.front_left_tyre_pressure', + 'tyre_pressure.front_right_tyre_pressure', + 'tyre_pressure.rear_left_tyre_pressure', + 'tyre_pressure.rear_right_tyre_pressure', + 'any_door_open', + 'any_window_open' +] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -65,12 +98,13 @@ CONFIG_SCHEMA = vol.Schema({ cv.ensure_list, [vol.In(RESOURCES)]), vol.Optional(CONF_REGION): cv.string, vol.Optional(CONF_SERVICE_URL): cv.string, + vol.Optional(CONF_MUTABLE, default=True): cv.boolean, vol.Optional(CONF_SCANDINAVIAN_MILES, default=False): cv.boolean, }), }, extra=vol.ALLOW_EXTRA) -def setup(hass, config): +async def async_setup(hass, config): """Set up the Volvo On Call component.""" from volvooncall import Connection connection = Connection( @@ -81,44 +115,57 @@ def setup(hass, config): interval = config[DOMAIN].get(CONF_UPDATE_INTERVAL) - state = hass.data[DATA_KEY] = VolvoData(config) + data = hass.data[DATA_KEY] = VolvoData(config) def discover_vehicle(vehicle): """Load relevant platforms.""" - state.entities[vehicle.vin] = [] - for attr, (component, *_) in RESOURCES.items(): - if (getattr(vehicle, attr + '_supported', True) and - attr in config[DOMAIN].get(CONF_RESOURCES, [attr])): - discovery.load_platform( - hass, component, DOMAIN, (vehicle.vin, attr), config) + data.vehicles.add(vehicle.vin) - def update_vehicle(vehicle): - """Receive updated information on vehicle.""" - state.vehicles[vehicle.vin] = vehicle - if vehicle.vin not in state.entities: - discover_vehicle(vehicle) + dashboard = vehicle.dashboard( + mutable=config[DOMAIN][CONF_MUTABLE], + scandinavian_miles=config[DOMAIN][CONF_SCANDINAVIAN_MILES]) - for entity in state.entities[vehicle.vin]: - entity.schedule_update_ha_state() + def is_enabled(attr): + """Return true if the user has enabled the resource.""" + return attr in config[DOMAIN].get(CONF_RESOURCES, [attr]) - dispatcher_send(hass, SIGNAL_VEHICLE_SEEN, vehicle) + for instrument in ( + instrument + for instrument in dashboard.instruments + if instrument.component in COMPONENTS and + is_enabled(instrument.slug_attr)): - def update(now): + data.instruments.append(instrument) + + hass.async_create_task( + discovery.async_load_platform( + hass, + COMPONENTS[instrument.component], + DOMAIN, + (vehicle.vin, + instrument.component, + instrument.attr), + config)) + + async def update(now): """Update status from the online service.""" try: - if not connection.update(): + if not await connection.update(journal=True): _LOGGER.warning("Could not query server") return False for vehicle in connection.vehicles: - update_vehicle(vehicle) + if vehicle.vin not in data.vehicles: + discover_vehicle(vehicle) + + async_dispatcher_send(hass, SIGNAL_STATE_UPDATED) return True finally: - track_point_in_utc_time(hass, update, utcnow() + interval) + async_track_point_in_utc_time(hass, update, utcnow() + interval) _LOGGER.info("Logging in to service") - return update(utcnow()) + return await update(utcnow()) class VolvoData: @@ -126,11 +173,19 @@ class VolvoData: def __init__(self, config): """Initialize the component state.""" - self.entities = {} - self.vehicles = {} + self.vehicles = set() + self.instruments = [] self.config = config[DOMAIN] self.names = self.config.get(CONF_NAME) + def instrument(self, vin, component, attr): + """Return corresponding instrument.""" + return next((instrument + for instrument in self.instruments + if instrument.vehicle.vin == vin and + instrument.component == component and + instrument.attr == attr), None) + def vehicle_name(self, vehicle): """Provide a friendly name for a vehicle.""" if (vehicle.registration_number and @@ -148,29 +203,41 @@ class VolvoData: class VolvoEntity(Entity): """Base class for all VOC entities.""" - def __init__(self, hass, vin, attribute): + def __init__(self, data, vin, component, attribute): """Initialize the entity.""" - self._hass = hass - self._vin = vin - self._attribute = attribute - self._state.entities[self._vin].append(self) + self.data = data + self.vin = vin + self.component = component + self.attribute = attribute + + async def async_added_to_hass(self): + """Register update dispatcher.""" + async_dispatcher_connect( + self.hass, SIGNAL_STATE_UPDATED, + self.async_schedule_update_ha_state) @property - def _state(self): - return self._hass.data[DATA_KEY] + def instrument(self): + """Return corresponding instrument.""" + return self.data.instrument(self.vin, self.component, self.attribute) + + @property + def icon(self): + """Return the icon.""" + return self.instrument.icon @property def vehicle(self): """Return vehicle.""" - return self._state.vehicles[self._vin] + return self.instrument.vehicle @property def _entity_name(self): - return RESOURCES[self._attribute][1] + return self.instrument.name @property def _vehicle_name(self): - return self._state.vehicle_name(self.vehicle) + return self.data.vehicle_name(self.vehicle) @property def name(self): @@ -192,6 +259,7 @@ class VolvoEntity(Entity): @property def device_state_attributes(self): """Return device specific state attributes.""" - return dict(model='{}/{}'.format( - self.vehicle.vehicle_type, - self.vehicle.model_year)) + return dict(self.instrument.attributes, + model='{}/{}'.format( + self.vehicle.vehicle_type, + self.vehicle.model_year)) diff --git a/requirements_all.txt b/requirements_all.txt index bc8740cc4c6..8609f1aeda1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1584,7 +1584,7 @@ venstarcolortouch==0.6 volkszaehler==0.1.2 # homeassistant.components.volvooncall -volvooncall==0.4.0 +volvooncall==0.7.4 # homeassistant.components.verisure vsure==1.5.2