Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev to master #127

Merged
merged 10 commits into from
Apr 1, 2024
2 changes: 1 addition & 1 deletion custom_components/polestar_api/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
20 changes: 17 additions & 3 deletions custom_components/polestar_api/polestar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions custom_components/polestar_api/pypolestar/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
36 changes: 26 additions & 10 deletions custom_components/polestar_api/pypolestar/polestar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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']:
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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))

Expand All @@ -168,15 +176,23 @@ 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",
"authorization": f"Bearer {self.auth.access_token}"
}

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")
Expand All @@ -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")
Expand Down
49 changes: 39 additions & 10 deletions custom_components/polestar_api/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
),
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down
22 changes: 20 additions & 2 deletions custom_components/polestar_api/translations/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
},
Expand All @@ -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"
}
Expand Down
Loading
Loading