diff --git a/custom_components/polestar_api/config_flow.py b/custom_components/polestar_api/config_flow.py index 3a08ccf..9feeee4 100644 --- a/custom_components/polestar_api/config_flow.py +++ b/custom_components/polestar_api/config_flow.py @@ -57,7 +57,7 @@ async def _create_device(self, username: str, password: str) -> None: _LOGGER.exception("Unexpected error creating device") return self.async_abort(reason="api_failed") - return await self._create_entry(username, password, ) + return await self._create_entry(username, password) async def async_step_user(self, user_input: dict = None) -> None: """User initiated config flow.""" diff --git a/custom_components/polestar_api/polestar.py b/custom_components/polestar_api/polestar.py index ab80c60..2e5d630 100644 --- a/custom_components/polestar_api/polestar.py +++ b/custom_components/polestar_api/polestar.py @@ -48,13 +48,27 @@ def get_latest_data(self, query: str, field_name: str): """ Get the latest data from the Polestar API.""" return self.polestarApi.get_latest_data(query, field_name) + def get_latest_call_code_v1(self): + """Get the latest call code mystar API.""" + return self.polestarApi.latest_call_code + + def get_latest_call_code_v2(self): + """Get the latest call code mystar-v2 API.""" + return self.polestarApi.latest_call_code_2 + + def get_latest_call_code_auth(self): + """Get the latest call code mystar API.""" + return self.polestarApi.auth.latest_call_code + def get_latest_call_code(self): """Get the latest call code.""" # if AUTH code last code is not 200 then we return that error code, - # otherwise just give the call_code in API + # otherwise just give the call_code in API from v1 and then v2 if self.polestarApi.auth.latest_call_code != 200: return self.polestarApi.auth.latest_call_code - return self.polestarApi.latest_call_code + if self.polestarApi.latest_call_code != 200: + return self.polestarApi.latest_call_code + return self.polestarApi.latest_call_code_2 async def async_update(self) -> None: """Update data from Polestar.""" @@ -80,7 +94,7 @@ async def async_update(self) -> None: except Exception as e: _LOGGER.error("Unexpected Error on update data %s", str(e)) self.polestarApi.next_update = datetime.now() + timedelta(seconds=60) - self.polestarApi.latest_call_code = 500 + self.polestarApi.latest_call_code_v2 = 500 self.polestarApi.updating = False def set_config_unit(self, unit:UnitSystem): diff --git a/custom_components/polestar_api/pypolestar/const.py b/custom_components/polestar_api/pypolestar/const.py index afe5406..bda80b4 100644 --- a/custom_components/polestar_api/pypolestar/const.py +++ b/custom_components/polestar_api/pypolestar/const.py @@ -6,3 +6,6 @@ BATTERY_DATA = "getBatteryData" HTTPX_TIMEOUT = 30 + +BASE_URL = "https://pc-api.polestar.com/eu-north-1/my-star/" +BASE_URL_V2 = "https://pc-api.polestar.com/eu-north-1/mystar-v2/" diff --git a/custom_components/polestar_api/pypolestar/polestar.py b/custom_components/polestar_api/pypolestar/polestar.py index bcc7a3b..1c944bd 100644 --- a/custom_components/polestar_api/pypolestar/polestar.py +++ b/custom_components/polestar_api/pypolestar/polestar.py @@ -5,7 +5,14 @@ import httpx from .auth import PolestarAuth -from .const import BATTERY_DATA, CACHE_TIME, CAR_INFO_DATA, ODO_METER_DATA +from .const import ( + BASE_URL, + BASE_URL_V2, + BATTERY_DATA, + CACHE_TIME, + CAR_INFO_DATA, + ODO_METER_DATA, +) from .exception import ( PolestarApiException, PolestarAuthException, @@ -25,6 +32,7 @@ def __init__(self, username: str, password: str) -> None: self.updating = False self.cache_data = {} self.latest_call_code = None + self.latest_call_code_2 = None self._client_session = httpx.AsyncClient() self.next_update = None @@ -75,7 +83,7 @@ async def _get_odometer_data(self, vin: str): "operationName": "GetOdometerData", "variables": "{\"vin\":\"" + vin + "\"}" } - result = await self.get_graph_ql(params, 'https://pc-api.polestar.com/eu-north-1/mystar-v2/') + result = await self.get_graph_ql(params, BASE_URL_V2) if result and result['data']: # put result in cache @@ -89,7 +97,7 @@ async def _get_battery_data(self, vin: str): "variables": "{\"vin\":\"" + vin + "\"}" } - result = await self.get_graph_ql(params, 'https://pc-api.polestar.com/eu-north-1/mystar-v2/') + result = await self.get_graph_ql(params, BASE_URL_V2) if result and result['data']: @@ -101,9 +109,9 @@ async def _get_vehicle_data(self): """" Get the latest vehicle data from the Polestar API.""" # get Vehicle Data params = { - "query": "query GetConsumerCarsV2($locale:String){getConsumerCarsV2(locale:$locale){vin internalVehicleIdentifier salesType currentPlannedDeliveryDate market originalMarket pno34 modelYear belongsToFleet registrationNo metaOrderNumber factoryCompleteDate registrationDate deliveryDate serviceHistory{claimType market mileage mileageUnit operations{id code description quantity performedDate}orderEndDate orderNumber orderStartDate parts{id code description quantity performedDate}status statusDMS symptomCode vehicleAge workshopId}content{exterior{code name description excluded}exteriorDetails{code name description excluded}interior{code name description excluded}performancePackage{code name description excluded}performanceOptimizationSpecification{power{value unit}torqueMax{value unit}acceleration{value unit description}}wheels{code name description excluded}plusPackage{code name description excluded}pilotPackage{code name description excluded}motor{name description excluded}model{name code}images{studio{url angles resolutions}location{url angles resolutions}interior{url angles resolutions}}specification{battery bodyType brakes combustionEngine electricMotors performance suspension tireSizes torque totalHp totalKw trunkCapacity{label value}}dimensions{wheelbase{label value}groundClearanceWithPerformance{label value}groundClearanceWithoutPerformance{label value}dimensions{label value}}towbar{code name description excluded}}primaryDriver primaryDriverRegistrationTimestamp owners{id registeredAt information{polestarId ownerType}}wltpNedcData{wltpCO2Unit wltpElecEnergyConsumption wltpElecEnergyUnit wltpElecRange wltpElecRangeUnit wltpWeightedCombinedCO2 wltpWeightedCombinedFuelConsumption wltpWeightedCombinedFuelConsumptionUnit}energy{elecRange elecRangeUnit elecEnergyConsumption elecEnergyUnit weightedCombinedCO2 weightedCombinedCO2Unit weightedCombinedFuelConsumption weightedCombinedFuelConsumptionUnit}fuelType drivetrain numberOfDoors numberOfSeats motor{description code}maxTrailerWeight{value unit}curbWeight{value unit}hasPerformancePackage numberOfCylinders cylinderVolume cylinderVolumeUnit transmission numberOfGears structureWeek hardware{nodeAddress partNo description{text short}software{partNo}}software{version versionTimestamp performanceOptimization{value description timestamp}}claims{type validFromDate validUntilDate validUntilMileage performedJobs{repairDate}}performedClaims{claimType workshopId market orderNumber claimPerformedManually orderEndDate mileage mileageUnit vehicleAge symptomCode parts{code}operations{code}}latestClaimStatus{mileage mileageUnit registeredDate vehicleAge}internalCar{origin registeredAt}edition commonStatusPoint{code timestamp description}brandStatus{code timestamp description}intermediateDestinationCode partnerDestinationCode features{type code name description excluded galleryImage{url alt}thumbnail{url alt}}electricalEngineNumbers{number placement}}}", + "query": "query GetConsumerCarsV2($locale:String){getConsumerCarsV2(locale:$locale){vin internalVehicleIdentifier salesType currentPlannedDeliveryDate market originalMarket pno34 modelYear belongsToFleet registrationNo metaOrderNumber factoryCompleteDate registrationDate deliveryDate serviceHistory{claimType market mileage mileageUnit operations{id code description quantity performedDate}orderEndDate orderNumber orderStartDate parts{id code description quantity performedDate} statusDMS symptomCode vehicleAge workshopId}content{exterior{code name description excluded}exteriorDetails{code name description excluded}interior{code name description excluded}performancePackage{code name description excluded}performanceOptimizationSpecification{power{value unit}torqueMax{value unit}acceleration{value unit description}}wheels{code name description excluded}plusPackage{code name description excluded}pilotPackage{code name description excluded}motor{name description excluded}model{name code}images{studio{url angles resolutions}location{url angles resolutions}interior{url angles resolutions}}specification{battery bodyType brakes combustionEngine electricMotors performance suspension tireSizes torque totalHp totalKw trunkCapacity{label value}}dimensions{wheelbase{label value}groundClearanceWithPerformance{label value}groundClearanceWithoutPerformance{label value}dimensions{label value}}towbar{code name description excluded}}primaryDriver primaryDriverRegistrationTimestamp owners{id registeredAt information{polestarId ownerType}}wltpNedcData{wltpCO2Unit wltpElecEnergyConsumption wltpElecEnergyUnit wltpElecRange wltpElecRangeUnit wltpWeightedCombinedCO2 wltpWeightedCombinedFuelConsumption wltpWeightedCombinedFuelConsumptionUnit}energy{elecRange elecRangeUnit elecEnergyConsumption elecEnergyUnit weightedCombinedCO2 weightedCombinedCO2Unit weightedCombinedFuelConsumption weightedCombinedFuelConsumptionUnit}fuelType drivetrain numberOfDoors numberOfSeats motor{description code}maxTrailerWeight{value unit}curbWeight{value unit}hasPerformancePackage numberOfCylinders cylinderVolume cylinderVolumeUnit transmission numberOfGears structureWeek hardware{nodeAddress partNo description{text short}software{partNo}}software{version versionTimestamp performanceOptimization{value description timestamp}}claims{type validFromDate validUntilDate validUntilMileage performedJobs{repairDate}}performedClaims{claimType workshopId market orderNumber claimPerformedManually orderEndDate mileage mileageUnit vehicleAge symptomCode parts{code}operations{code}}latestClaimStatus{mileage mileageUnit registeredDate vehicleAge}internalCar{origin registeredAt}edition commonStatusPoint{code timestamp description}brandStatus{code timestamp description}intermediateDestinationCode partnerDestinationCode features{type code name description excluded galleryImage{url alt}thumbnail{url alt}}electricalEngineNumbers{number placement}}}", "operationName": "GetConsumerCarsV2", - "variables": "{\"local\":\"en_GB\"}" + "variables": "{\"locale\":\"en_GB\"}" } result = await self.get_graph_ql(params) @@ -134,7 +142,7 @@ async def get_ev_data(self, vin: str): if (self.auth.token_expiry - datetime.now()).total_seconds() < 300: await self.auth.get_token(refresh=True) except PolestarAuthException as e: - self.latest_call_code = 500 + self._set_latest_call_code(BASE_URL, 500) _LOGGER.warning("Auth Exception: %s", str(e)) self.updating = False return @@ -145,7 +153,7 @@ async def call_api(func): except PolestarNotAuthorizedException: await self.auth.get_token() except PolestarApiException as e: - self.latest_call_code = 500 + self._set_latest_call_code(BASE_URL_V2, 500) _LOGGER.warning('Failed to get %s data %s', func.__name__, str(e)) @@ -168,7 +176,15 @@ def get_cache_data(self, query: str, field_name: str, skip_cache: bool = False): return self._get_field_name_value(field_name, data) return None - async def get_graph_ql(self, params: dict, url:str = "https://pc-api.polestar.com/eu-north-1/my-star/"): + def _set_latest_call_code(self, url:str, code: int): + if url == BASE_URL: + self.latest_call_code = code + else: + self.latest_call_code_2 = code + + + + async def get_graph_ql(self, params: dict, url:str = BASE_URL): """Get the latest data from the Polestar API.""" headers = { "Content-Type": "application/json", @@ -176,7 +192,7 @@ async def get_graph_ql(self, params: dict, url:str = "https://pc-api.polestar.co } result = await self._client_session.get(url, params=params, headers=headers) - self.latest_call_code = result.status_code + self._set_latest_call_code(url, result.status_code) if result.status_code == 401: raise PolestarNotAuthorizedException("Unauthorized Exception") @@ -186,7 +202,7 @@ async def get_graph_ql(self, params: dict, url:str = "https://pc-api.polestar.co resultData = result.json() if resultData.get('errors'): - self.latest_call_code = 500 + self._set_latest_call_code(url, 500) error_message = resultData['errors'][0]['message'] if error_message == "User not authenticated": raise PolestarNotAuthorizedException("Unauthorized Exception") diff --git a/custom_components/polestar_api/sensor.py b/custom_components/polestar_api/sensor.py index 1ac854d..30e0132 100644 --- a/custom_components/polestar_api/sensor.py +++ b/custom_components/polestar_api/sensor.py @@ -24,7 +24,11 @@ from homeassistant.helpers import entity_platform from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from homeassistant.util.unit_conversion import DistanceConverter, EnergyConverter, SpeedConverter +from homeassistant.util.unit_conversion import ( + DistanceConverter, + EnergyConverter, + SpeedConverter, +) from . import DOMAIN as POLESTAR_API_DOMAIN from .entity import PolestarEntity @@ -121,7 +125,7 @@ class PolestarSensorDescription( round_digits=2, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.DISTANCE, - max_value=100000000, + max_value=1000000000, dict_data=None ), PolestarSensorDescription( @@ -183,7 +187,7 @@ class PolestarSensorDescription( field_name="estimatedChargingTimeToFullMinutes", native_unit_of_measurement=UnitOfTime.MINUTES, round_digits=None, - max_value=1500, + max_value=None, dict_data=None ), PolestarSensorDescription( @@ -232,7 +236,6 @@ class PolestarSensorDescription( native_unit_of_measurement=None, round_digits=None, max_value=None, - state_class=SensorStateClass.MEASUREMENT, device_class=None, dict_data=CHARGING_CONNECTION_STATUS_DICT ), @@ -406,7 +409,7 @@ class PolestarSensorDescription( ), PolestarSensorDescription( key="api_status_code", - name="API Status", + name="API Status Code V1", icon="mdi:heart", query=None, field_name=None, @@ -416,14 +419,36 @@ class PolestarSensorDescription( dict_data=API_STATUS_DICT ), PolestarSensorDescription( - key="api_token_expires_at", - name="API Token Expired At", + key="api_status_code_v2", + name="API Status Code V2", + icon="mdi:heart", + query=None, + field_name=None, + native_unit_of_measurement=None, + round_digits=None, + max_value=None, + dict_data=API_STATUS_DICT + ), + PolestarSensorDescription( + key="api_status_code_auth", + name="Auth API Status Code", icon="mdi:heart", query=None, field_name=None, native_unit_of_measurement=None, round_digits=None, max_value=None, + dict_data=API_STATUS_DICT + ), + PolestarSensorDescription( + key="api_token_expires_at", + name="Auth Token Expired At", + icon="mdi:clock-time-eight", + query=None, + field_name=None, + native_unit_of_measurement=None, + round_digits=None, + max_value=None, dict_data=None ), PolestarSensorDescription( @@ -547,7 +572,11 @@ def state(self) -> StateType: if self.entity_description.dict_data is not None: # exception for api_status_code if self.entity_description.key == 'api_status_code': - return self.entity_description.dict_data.get(self._device.get_latest_call_code(), "Error") + return self.entity_description.dict_data.get(self._device.get_latest_call_code_v1(), "Error") + elif self.entity_description.key == 'api_status_code_v2': + return self.entity_description.dict_data.get(self._device.get_latest_call_code_v2(), "Error") + elif self.entity_description.key == 'api_status_code_auth': + return self.entity_description.dict_data.get(self._device.get_latest_call_code_auth(), "Error") self._attr_native_value = self.entity_description.dict_data.get( self._attr_native_value, self._attr_native_value) @@ -619,8 +648,8 @@ def state(self) -> StateType: # prevent exponentianal value, we only give state value that is lower than the max value if self.entity_description.max_value is not None: if isinstance(self._sensor_data, str): - self._attr_native_value = int(self._attr_native_value) - if self._sensor_data > self.entity_description.max_value: + self._attr_native_value = int(self._sensor_data) + if self._attr_native_value > self.entity_description.max_value: _LOGGER.warning("%s: Value %s is higher than max value %s", self.entity_description.key, self._attr_native_value, self.entity_description.max_value) return None diff --git a/custom_components/polestar_api/translations/da.json b/custom_components/polestar_api/translations/da.json index 9d5682b..812dd46 100644 --- a/custom_components/polestar_api/translations/da.json +++ b/custom_components/polestar_api/translations/da.json @@ -26,11 +26,14 @@ "polestar_api_token_expires_at": { "name": "API token udløber" }, + "polestar_average_speed": { + "name": "Gennemsnitsfart" + }, "polestar_average_energy_consumption_kwh_per_100": { "name": "Gennemsnitligt energiforbrug" }, - "polestar_average_speed": { - "name": "Gennemsnitsfart" + "polestar_battery_capacity": { + "name": "Batterikapacitet" }, "polestar_battery_charge_level": { "name": "Batteriniveau" @@ -71,6 +74,12 @@ "polestar_estimated_fully_charged_time": { "name": "Estimeret total ladetid" }, + "polestar_factory_complete": { + "name": "Færdigsamlet dato" + }, + "polestar_internal_vebicle_id": { + "name": "Internt køretøjs ID" + }, "polestar_last_updated_battery_data": { "name": "Sidst opdateret batteriniveau" }, @@ -80,12 +89,21 @@ "polestar_model_name": { "name": "Modelnavn" }, + "polestar_registration_date": { + "name": "Registreringsdato" + }, "polestar_registration_number": { "name": "Registreringsnummer" }, "polestar_software_version": { "name": "Software version" }, + "polestar_software_version_release": { + "name": "Software dato" + }, + "polestar_torque": { + "name": "Moment" + }, "polestar_vin": { "name": "Stelnummer" } diff --git a/custom_components/polestar_api/translations/fr.json b/custom_components/polestar_api/translations/fr.json index 478448e..59c8d9a 100644 --- a/custom_components/polestar_api/translations/fr.json +++ b/custom_components/polestar_api/translations/fr.json @@ -5,10 +5,8 @@ "title": "Configuration Polestar VE", "description": "Entrez les paramètres d'authentification pour Polestar", "data": { - "name": "Nom personnalisé", "username": "Nom d'utilisateur", - "password": "Mot de passe", - "vin": "NIV" + "password": "Mot de passe" } } }, @@ -21,10 +19,13 @@ }, "entity": { "sensor": { - "polestar_current_odometer_meters": { + "polestar_estimate_range": { + "name": "Estimé de l'autonomie" + }, + "polestar_current_odometer": { "name": "Odomètre" }, - "polestar_average_speed_per_hour": { + "polestar_average_speed": { "name": "Vitesse moyenne" }, "polestar_current_trip_meter_automatic": { @@ -36,17 +37,17 @@ "polestar_battery_charge_level": { "name": "Niveau de charge de la betterie" }, - "polestar_estimated_charging_time_to_full_minutes": { + "polestar_estimated_charging_time_to_full": { "name": "Temps de recharge jusqu'à recharge complète" }, "polestar_charging_status": { "name": "État de la recharge" }, - "polestar_charging_power_watts": { + "polestar_charging_power": { "name": "Puissance de recharge" }, - "polestar_charging_current_amps": { - "name": "Puissance de recharge" + "polestar_charging_current": { + "name": "Courant de recharge" }, "polestar_charger_connection_status": { "name": "État de connexion du chargeur" @@ -63,6 +64,9 @@ "polestar_registration_number": { "name": "Numéro d'enregistrement" }, + "polestar_software_version": { + "name": "Version du logiciel" + }, "polestar_estimated_fully_charged_time": { "name": "Temps de recharge restant" }, @@ -78,9 +82,6 @@ "polestar_estimate_full_charge_range": { "name": "Estimé de l'autonomie complète" }, - "polestar_estimate_range": { - "name": "Estimé de l'autonomie" - }, "polestar_api_status_code": { "name": "Code d'état de l'API" },