diff --git a/homeassistant/components/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive/__init__.py similarity index 55% rename from homeassistant/components/bmw_connected_drive.py rename to homeassistant/components/bmw_connected_drive/__init__.py index 48452b6d79b..347bab6f529 100644 --- a/homeassistant/components/bmw_connected_drive.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = 'bmw_connected_drive' CONF_REGION = 'region' - +ATTR_VIN = 'vin' ACCOUNT_SCHEMA = vol.Schema({ vol.Required(CONF_USERNAME): cv.string, @@ -35,35 +35,40 @@ CONFIG_SCHEMA = vol.Schema({ }, }, extra=vol.ALLOW_EXTRA) +SERVICE_SCHEMA = vol.Schema({ + vol.Required(ATTR_VIN): cv.string, +}) + BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor'] UPDATE_INTERVAL = 5 # in minutes +SERVICE_UPDATE_STATE = 'update_state' -def setup(hass, config): +_SERVICE_MAP = { + 'light_flash': 'trigger_remote_light_flash', + 'sound_horn': 'trigger_remote_horn', + 'activate_air_conditioning': 'trigger_remote_air_conditioning', +} + + +def setup(hass, config: dict): """Set up the BMW connected drive components.""" accounts = [] for name, account_config in config[DOMAIN].items(): - username = account_config[CONF_USERNAME] - password = account_config[CONF_PASSWORD] - region = account_config[CONF_REGION] - _LOGGER.debug('Adding new account %s', name) - bimmer = BMWConnectedDriveAccount(username, password, region, name) - accounts.append(bimmer) - - # update every UPDATE_INTERVAL minutes, starting now - # this should even out the load on the servers - - now = datetime.datetime.now() - track_utc_time_change( - hass, bimmer.update, - minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), - second=now.second) + accounts.append(setup_account(account_config, hass, name)) hass.data[DOMAIN] = accounts - for account in accounts: - account.update() + def _update_all(call) -> None: + """Update all BMW accounts.""" + for cd_account in hass.data[DOMAIN]: + cd_account.update() + + # Service to manually trigger updates for all accounts. + hass.services.register(DOMAIN, SERVICE_UPDATE_STATE, _update_all) + + _update_all(None) for component in BMW_COMPONENTS: discovery.load_platform(hass, component, DOMAIN, {}, config) @@ -71,6 +76,48 @@ def setup(hass, config): return True +def setup_account(account_config: dict, hass, name: str) \ + -> 'BMWConnectedDriveAccount': + """Set up a new BMWConnectedDriveAccount based on the config.""" + username = account_config[CONF_USERNAME] + password = account_config[CONF_PASSWORD] + region = account_config[CONF_REGION] + _LOGGER.debug('Adding new account %s', name) + cd_account = BMWConnectedDriveAccount(username, password, region, name) + + def execute_service(call): + """Execute a service for a vehicle. + + This must be a member function as we need access to the cd_account + object here. + """ + vin = call.data[ATTR_VIN] + vehicle = cd_account.account.get_vehicle(vin) + if not vehicle: + _LOGGER.error('Could not find a vehicle for VIN "%s"!', vin) + return + function_name = _SERVICE_MAP[call.service] + function_call = getattr(vehicle.remote_services, function_name) + function_call() + + # register the remote services + for service in _SERVICE_MAP: + hass.services.register( + DOMAIN, service, + execute_service, + schema=SERVICE_SCHEMA) + + # update every UPDATE_INTERVAL minutes, starting now + # this should even out the load on the servers + now = datetime.datetime.now() + track_utc_time_change( + hass, cd_account.update, + minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), + second=now.second) + + return cd_account + + class BMWConnectedDriveAccount(object): """Representation of a BMW vehicle.""" diff --git a/homeassistant/components/bmw_connected_drive/services.yaml b/homeassistant/components/bmw_connected_drive/services.yaml new file mode 100644 index 00000000000..3c180271919 --- /dev/null +++ b/homeassistant/components/bmw_connected_drive/services.yaml @@ -0,0 +1,42 @@ +# Describes the format for available services for bmw_connected_drive +# +# The services related to locking/unlocking are implemented in the lock +# component to avoid redundancy. + +light_flash: + description: > + Flash the lights of the vehicle. The vehicle is identified via the vin + (see below). + fields: + vin: + description: > + The vehicle identification number (VIN) of the vehicle, 17 characters + example: WBANXXXXXX1234567 + +sound_horn: + description: > + Sound the horn of the vehicle. The vehicle is identified via the vin + (see below). + fields: + vin: + description: > + The vehicle identification number (VIN) of the vehicle, 17 characters + example: WBANXXXXXX1234567 + +activate_air_conditioning: + description: > + Start the air conditioning of the vehicle. What exactly is started here + depends on the type of vehicle. It might range from just ventilation over + auxilary heating to real air conditioning. The vehicle is identified via + the vin (see below). + fields: + vin: + description: > + The vehicle identification number (VIN) of the vehicle, 17 characters + example: WBANXXXXXX1234567 + +update_state: + description: > + Fetch the last state of the vehicles of all your accounts from the BMW + server. This does *not* trigger an update from the vehicle, it just gets + the data from the BMW servers. This service does not require any attributes. \ No newline at end of file diff --git a/homeassistant/components/device_tracker/bmw_connected_drive.py b/homeassistant/components/device_tracker/bmw_connected_drive.py index 2267bb51944..f36afc622ee 100644 --- a/homeassistant/components/device_tracker/bmw_connected_drive.py +++ b/homeassistant/components/device_tracker/bmw_connected_drive.py @@ -48,8 +48,11 @@ class BMWDeviceTracker(object): return _LOGGER.debug('Updating %s', dev_id) - + attrs = { + 'vin': self.vehicle.vin, + } self._see( dev_id=dev_id, host_name=self.vehicle.name, - gps=self.vehicle.state.gps_position, icon='mdi:car' + gps=self.vehicle.state.gps_position, attributes=attrs, + icon='mdi:car' )