Source code for bimmer_connected.vehicle.reports

"""Models the state of a vehicle."""

import datetime
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from bimmer_connected.const import ATTR_ATTRIBUTES, ATTR_STATE
from bimmer_connected.models import StrEnum, ValueWithUnit, VehicleDataBase
from bimmer_connected.utils import parse_datetime

_LOGGER = logging.getLogger(__name__)


[docs] class ConditionBasedServiceStatus(StrEnum): """Status of the condition based services.""" OK = "OK" OVERDUE = "OVERDUE" PENDING = "PENDING" UNKNOWN = "UNKNOWN"
[docs] @dataclass class ConditionBasedService: """Entry in the list of condition based services.""" service_type: str state: ConditionBasedServiceStatus due_date: Optional[datetime.datetime] due_distance: ValueWithUnit
[docs] @classmethod def from_api_entry( cls, type: str, # noqa: A002, pylint: disable=redefined-builtin status: str, dateTime: Optional[str] = None, # noqa: N803 mileage: Optional[int] = None, **kwargs, ): """Parse a condition based service entry from the API format to `ConditionBasedService`.""" due_distance = ValueWithUnit(mileage, "km") if mileage else ValueWithUnit(None, None) due_date = parse_datetime(dateTime) if dateTime else None return cls(type, ConditionBasedServiceStatus(status), due_date, due_distance)
[docs] @dataclass class ConditionBasedServiceReport(VehicleDataBase): """Parse and summarizes condition based services (e.g. next oil service).""" messages: List[ConditionBasedService] = field(default_factory=list) """List of the condition based services.""" is_service_required: bool = False """Indicate if a service is required.""" next_service_by_distance: Optional[ConditionBasedService] = None """Next service by distance.""" next_service_by_time: Optional[ConditionBasedService] = None """Next service by due date.""" @classmethod def _parse_vehicle_data(cls, vehicle_data: Dict) -> Optional[Dict]: """Parse doors and windows.""" retval: Dict[str, Any] = {} if ATTR_STATE in vehicle_data and (messages := vehicle_data[ATTR_STATE].get("requiredServices")): retval["messages"] = [ConditionBasedService.from_api_entry(**m) for m in messages] retval["is_service_required"] = any((m.state != ConditionBasedServiceStatus.OK) for m in retval["messages"]) retval["next_service_by_distance"] = next( iter( sorted( [m for m in retval["messages"] if m.due_distance.value is not None], key=lambda x: f"{x.due_distance.value:010}-{x.service_type}", ) ), None, ) retval["next_service_by_time"] = next( iter( sorted( [m for m in retval["messages"] if m.due_date is not None], key=lambda x: f"{x.due_date!s}-{x.service_type}", ) ), None, ) return retval
[docs] class CheckControlStatus(StrEnum): """Status of the condition based services.""" OK = "OK" LOW = "LOW" MEDIUM = "MEDIUM" HIGH = "HIGH" CRITICAL = "CRITICAL" UNKNOWN = "UNKNOWN"
[docs] @dataclass class CheckControlMessage: """Check control message sent from the server.""" description_short: str description_long: Optional[str] state: CheckControlStatus
[docs] @classmethod def from_api_entry( cls, type: str, # noqa: A002, pylint: disable=redefined-builtin severity: str, longDescription: Optional[str] = None, # noqa: N803 **kwargs, ): """Parse a check control entry from the API format to `CheckControlMessage`.""" return cls(type, longDescription, CheckControlStatus(severity))
[docs] @dataclass class CheckControlMessageReport(VehicleDataBase): """Parse and summarizes check control messages (e.g. low tire pressure).""" messages: List[CheckControlMessage] = field(default_factory=list) """List of check control messages.""" has_check_control_messages: bool = False """Indicate if check control messages are present.""" urgent_check_control_messages: Optional[str] = None @classmethod def _parse_vehicle_data(cls, vehicle_data: Dict) -> Optional[Dict]: """Parse doors and windows.""" retval: Dict[str, Any] = {} if ATTR_STATE in vehicle_data and (messages := vehicle_data[ATTR_STATE].get("checkControlMessages")): retval["messages"] = [CheckControlMessage.from_api_entry(**m) for m in messages if m["severity"] != "OK"] retval["has_check_control_messages"] = len([m for m in retval["messages"] if m.state != "LOW"]) > 0 retval["urgent_check_control_messages"] = ( ", ".join([m.description_short for m in retval["messages"] if m.state != "LOW"]) if retval["has_check_control_messages"] else None ) return retval
[docs] @dataclass class Headunit(VehicleDataBase): """Parse and summarizes headunit hard/software versions.""" idrive_version: str = "" """IDRIVE generation.""" headunit_type: str = "" """Type of headunit.""" software_version: str = "" """Current software revision of vehicle""" @classmethod def _parse_vehicle_data(cls, vehicle_data: Dict) -> Optional[Dict]: """Parse headunit hard/software.""" retval: Dict[str, Any] = {} if ATTR_ATTRIBUTES in vehicle_data and ( software_version := vehicle_data[ATTR_ATTRIBUTES].get("softwareVersionCurrent") ): retval["idrive_version"] = vehicle_data[ATTR_ATTRIBUTES]["hmiVersion"] retval["headunit_type"] = vehicle_data[ATTR_ATTRIBUTES]["headUnitType"] istep = software_version["iStep"] month = software_version["puStep"]["month"] year = software_version["puStep"]["year"] model_year = vehicle_data[ATTR_ATTRIBUTES]["year"] retval["software_version"] = f"{month:02d}/{str(model_year)[:2]}{year:02d}.{str(istep)[1:]}" return retval