"""Generals models used for bimmer_connected."""
import datetime
import logging
from dataclasses import dataclass
from typing import Any, Dict, Optional
from bimmer_connected.const import ATTR_ATTRIBUTES, ATTR_STATE, Regions
from bimmer_connected.coord_convert import gcj2wgs
from bimmer_connected.models import GPSPosition, VehicleDataBase
from bimmer_connected.utils import parse_datetime
_LOGGER = logging.getLogger(__name__)
[docs]@dataclass
class VehicleLocation(VehicleDataBase):
"""The current position of a vehicle."""
location: Optional[GPSPosition] = None
"""The last known position of the vehicle."""
heading: Optional[int] = None
"""The last known heading/direction of the vehicle."""
vehicle_update_timestamp: Optional[datetime.datetime] = None
account_region: Optional[Regions] = None
remote_service_position: Optional[Dict] = None
[docs] @classmethod
def from_vehicle_data(cls, vehicle_data: Dict):
"""Create the class based on vehicle data from API."""
parsed = cls._parse_vehicle_data(vehicle_data) or {}
if len(parsed) > 1: # must be greater than 1 due to timestamp dummy
return cls(**parsed)
return None
@classmethod
def _parse_vehicle_data(cls, vehicle_data: Dict):
date_dummy = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
retval: Dict[str, Any] = {}
retval["vehicle_update_timestamp"] = max(
parse_datetime(vehicle_data.get(ATTR_STATE, {}).get("lastFetched")) or date_dummy,
parse_datetime(vehicle_data.get(ATTR_ATTRIBUTES, {}).get("lastFetched")) or date_dummy,
)
if ATTR_STATE in vehicle_data and "location" in vehicle_data[ATTR_STATE]:
location = vehicle_data[ATTR_STATE]["location"]
retval["location"] = GPSPosition(location["coordinates"]["latitude"], location["coordinates"]["longitude"])
retval["heading"] = location["heading"]
return retval
def _update_after_parse(self, parsed: Dict) -> Dict:
"""Update parsed vehicle data with attributes stored in class if needed."""
retval = parsed
# Overwrite vehicle data with remote service position if available & newer
if self.remote_service_position is not None:
t_remote = self.remote_service_position.get(
"timestamp", datetime.datetime(1900, 1, 1, tzinfo=datetime.timezone.utc)
)
if t_remote > self.vehicle_update_timestamp:
retval["location"] = GPSPosition(
self.remote_service_position["latitude"], self.remote_service_position["longitude"]
)
retval["heading"] = self.remote_service_position["heading"]
# Convert GCJ02 to WGS84 for positions in China
if self.account_region == Regions.CHINA and "location" in retval and retval["location"].latitude is not None:
gcj_lon, gcj_lat = gcj2wgs(gcjLon=retval["location"].longitude, gcjLat=retval["location"].latitude)
retval["location"] = GPSPosition(gcj_lat, gcj_lon)
return retval
[docs] def set_remote_service_position(self, remote_service_dict: Dict):
"""Store remote service position returned from vehicle finder service."""
if remote_service_dict.get("errorDetails"):
error = remote_service_dict["errorDetails"]
_LOGGER.error("Error retrieving vehicle position. %s: %s", error["title"], error["description"])
else:
pos = remote_service_dict["positionData"]["position"]
pos["timestamp"] = datetime.datetime.utcnow()
pos["timestamp"] = pos["timestamp"].replace(tzinfo=datetime.timezone.utc)
self.remote_service_position = pos
self.__dict__.update(self._update_after_parse({}))