Exemple #1
0
class BMWDataUpdateCoordinator(DataUpdateCoordinator):
    """Class to manage fetching BMW data."""

    account: MyBMWAccount

    def __init__(self, hass: HomeAssistant, *, entry: ConfigEntry) -> None:
        """Initialize account-wide BMW data updater."""
        self.account = MyBMWAccount(
            entry.data[CONF_USERNAME],
            entry.data[CONF_PASSWORD],
            get_region_from_name(entry.data[CONF_REGION]),
            observer_position=GPSPosition(hass.config.latitude, hass.config.longitude),
            use_metric_units=hass.config.units.is_metric,
        )
        self.read_only = entry.options[CONF_READ_ONLY]
        self._entry = entry

        if CONF_REFRESH_TOKEN in entry.data:
            self.account.set_refresh_token(entry.data[CONF_REFRESH_TOKEN])

        super().__init__(
            hass,
            _LOGGER,
            name=f"{DOMAIN}-{entry.data['username']}",
            update_interval=SCAN_INTERVAL,
        )

    async def _async_update_data(self) -> None:
        """Fetch data from BMW."""
        old_refresh_token = self.account.refresh_token

        try:
            await self.account.get_vehicles()
        except (HTTPError, TimeoutException) as err:
            self._update_config_entry_refresh_token(None)
            raise UpdateFailed(f"Error communicating with BMW API: {err}") from err

        if self.account.refresh_token != old_refresh_token:
            self._update_config_entry_refresh_token(self.account.refresh_token)
            _LOGGER.debug(
                "bimmer_connected: refresh token %s > %s",
                old_refresh_token,
                self.account.refresh_token,
            )

    def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
        """Update or delete the refresh_token in the Config Entry."""
        data = {
            **self._entry.data,
            CONF_REFRESH_TOKEN: refresh_token,
        }
        if not refresh_token:
            data.pop(CONF_REFRESH_TOKEN)
        self.hass.config_entries.async_update_entry(self._entry, data=data)

    def notify_listeners(self) -> None:
        """Notify all listeners to refresh HA state machine."""
        for update_callback in self._listeners:
            update_callback()
Exemple #2
0
async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
    """Load MyBMWVehicle from fixtures and add them to the account."""

    fixture_path = Path(get_fixture_path("", integration=BMW_DOMAIN))

    fixture_vehicles_bmw = list(fixture_path.rglob("vehicles_v2_bmw_*.json"))
    fixture_vehicles_mini = list(fixture_path.rglob("vehicles_v2_mini_*.json"))

    # Load vehicle base lists as provided by vehicles/v2 API
    vehicles = {
        "bmw": [
            vehicle for bmw_file in fixture_vehicles_bmw
            for vehicle in json.loads(
                load_fixture(bmw_file, integration=BMW_DOMAIN))
        ],
        "mini": [
            vehicle for mini_file in fixture_vehicles_mini
            for vehicle in json.loads(
                load_fixture(mini_file, integration=BMW_DOMAIN))
        ],
    }
    fetched_at = utcnow()

    # simulate storing fingerprints
    if account.config.log_response_path:
        for brand in ["bmw", "mini"]:
            log_to_to_file(
                json.dumps(vehicles[brand]),
                account.config.log_response_path,
                f"vehicles_v2_{brand}",
            )

    # Create a vehicle with base + specific state as provided by state/VIN API
    for vehicle_base in [
            vehicle for brand in vehicles.values() for vehicle in brand
    ]:
        vehicle_state_path = (Path("vehicles") /
                              vehicle_base["attributes"]["bodyType"] /
                              f"state_{vehicle_base['vin']}_0.json")
        vehicle_state = json.loads(
            load_fixture(
                vehicle_state_path,
                integration=BMW_DOMAIN,
            ))

        account.add_vehicle(
            vehicle_base,
            vehicle_state,
            fetched_at,
        )

        # simulate storing fingerprints
        if account.config.log_response_path:
            log_to_to_file(
                json.dumps(vehicle_state),
                account.config.log_response_path,
                f"state_{vehicle_base['vin']}",
            )
Exemple #3
0
    def __init__(self, hass: HomeAssistant, *, entry: ConfigEntry) -> None:
        """Initialize account-wide BMW data updater."""
        self.account = MyBMWAccount(
            entry.data[CONF_USERNAME],
            entry.data[CONF_PASSWORD],
            get_region_from_name(entry.data[CONF_REGION]),
            observer_position=GPSPosition(hass.config.latitude, hass.config.longitude),
            use_metric_units=hass.config.units.is_metric,
        )
        self.read_only = entry.options[CONF_READ_ONLY]
        self._entry = entry

        if CONF_REFRESH_TOKEN in entry.data:
            self.account.set_refresh_token(entry.data[CONF_REFRESH_TOKEN])

        super().__init__(
            hass,
            _LOGGER,
            name=f"{DOMAIN}-{entry.data['username']}",
            update_interval=SCAN_INTERVAL,
        )
Exemple #4
0
async def validate_input(hass: core.HomeAssistant,
                         data: dict[str, Any]) -> dict[str, str]:
    """Validate the user input allows us to connect.

    Data has the keys from DATA_SCHEMA with values provided by the user.
    """
    account = MyBMWAccount(
        data[CONF_USERNAME],
        data[CONF_PASSWORD],
        get_region_from_name(data[CONF_REGION]),
    )

    try:
        await account.get_vehicles()
    except HTTPError as ex:
        raise CannotConnect from ex

    # Return info that you want to store in the config entry.
    return {"title": f"{data[CONF_USERNAME]}{data.get(CONF_SOURCE, '')}"}
Exemple #5
0
async def collectData(user, password, region, attributes):
    # connect to bmw
    account = MyBMWAccount(user, password, get_region_from_name(region))
    if not account:
        print('ERROR: Unable to log on')
        exit(1)

    # update states of all vehicles
    await account.get_vehicles()

    if DEBUG:
        print('DEBUG: Found {} vehicles: {}'.format(
            len(account.vehicles),
            ','.join([v.name for v in account.vehicles])))

    # set gauges for each vehicle
    for vehicle in account.vehicles:
        vehicle_name = ''.join(e for e in vehicle.name if e.isalnum())
        for attr in attributes:
            if attr['group']:
                vehicle_attribute_group = getattr(vehicle.status.vehicle,
                                                  attr['group'])
                vehicle_attribute = getattr(vehicle_attribute_group,
                                            attr['name'])
            else:
                vehicle_attribute = getattr(vehicle.status.vehicle,
                                            attr['name'])
            if vehicle_attribute:
                if type(
                        vehicle_attribute
                ) == ValueWithUnit:  # some attributes are typed ValueWithUnit, some are just numbers
                    vehicle_attribute_value = vehicle_attribute.value
                else:
                    vehicle_attribute_value = vehicle_attribute
                if type(vehicle_attribute_value) == int or type(
                        vehicle_attribute_value) == float:
                    gauge = gauges[attr['name']]
                    if gauge:
                        gauge.labels(
                            vehicle=vehicle_name,
                            vin=vehicle.vin).set(vehicle_attribute_value)
Exemple #6
0
class BMWDataUpdateCoordinator(DataUpdateCoordinator):
    """Class to manage fetching BMW data."""

    account: MyBMWAccount

    def __init__(self, hass: HomeAssistant, *, entry: ConfigEntry) -> None:
        """Initialize account-wide BMW data updater."""
        self.account = MyBMWAccount(
            entry.data[CONF_USERNAME],
            entry.data[CONF_PASSWORD],
            get_region_from_name(entry.data[CONF_REGION]),
            observer_position=GPSPosition(hass.config.latitude,
                                          hass.config.longitude),
            use_metric_units=hass.config.units.is_metric,
        )
        self.read_only = entry.options[CONF_READ_ONLY]
        self._entry = entry

        if CONF_REFRESH_TOKEN in entry.data:
            self.account.set_refresh_token(entry.data[CONF_REFRESH_TOKEN])

        super().__init__(
            hass,
            _LOGGER,
            name=f"{DOMAIN}-{entry.data['username']}",
            update_interval=SCAN_INTERVAL,
        )

    async def _async_update_data(self) -> None:
        """Fetch data from BMW."""
        old_refresh_token = self.account.refresh_token

        try:
            await self.account.get_vehicles()
        except (HTTPError, HTTPStatusError, TimeoutException) as err:
            if isinstance(err,
                          HTTPStatusError) and err.response.status_code == 429:
                # Increase scan interval to not jump to not bring up the issue next time
                self.update_interval = timedelta(
                    seconds=DEFAULT_SCAN_INTERVAL_SECONDS * 3)
            if isinstance(err,
                          HTTPStatusError) and err.response.status_code in (
                              401,
                              403,
                          ):
                # Clear refresh token only on issues with authorization
                self._update_config_entry_refresh_token(None)
            raise UpdateFailed(
                f"Error communicating with BMW API: {err}") from err

        if self.account.refresh_token != old_refresh_token:
            self._update_config_entry_refresh_token(self.account.refresh_token)
            _LOGGER.debug(
                "bimmer_connected: refresh token %s > %s",
                old_refresh_token,
                self.account.refresh_token,
            )

        # Reset scan interval after successful update
        self.update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS)

    def _update_config_entry_refresh_token(self,
                                           refresh_token: str | None) -> None:
        """Update or delete the refresh_token in the Config Entry."""
        data = {
            **self._entry.data,
            CONF_REFRESH_TOKEN: refresh_token,
        }
        if not refresh_token:
            data.pop(CONF_REFRESH_TOKEN)
        self.hass.config_entries.async_update_entry(self._entry, data=data)