-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move api call to pypolestar, split api call #14
- Loading branch information
Tuen Lee
committed
Dec 22, 2023
1 parent
8f8b3c0
commit 5211125
Showing
10 changed files
with
346 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,4 @@ | ||
DOMAIN = "polestar_api" | ||
TIMEOUT = 90 | ||
|
||
|
||
ACCESS_TOKEN_MANAGER_ID = "JWTh4Yf0b" | ||
GRANT_TYPE = "password" | ||
AUTHORIZATION = "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=" | ||
|
||
HEADER_AUTHORIZATION = "authorization" | ||
|
||
CACHE_TIME = 30 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,58 @@ | ||
from datetime import timedelta | ||
import json | ||
|
||
import logging | ||
|
||
from .pypolestar.polestar import PolestarApi | ||
|
||
|
||
from .const import ( | ||
CACHE_TIME, | ||
) | ||
|
||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from urllib3 import disable_warnings | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.util import Throttle | ||
from .polestar_api import PolestarApi | ||
|
||
|
||
POST_HEADER_JSON = {"Content-Type": "application/json"} | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class Polestar: | ||
|
||
QUERY_PAYLOAD = '{"query": "{ me { homes { electricVehicles {id name shortName lastSeen lastSeenText isAlive hasNoSmartChargingCapability imgUrl schedule {isEnabled isSuspended localTimeTo minBatteryLevel} batteryText chargingText consumptionText consumptionUnitText energyCostUnitText chargeRightAwayButton chargeRightAwayAlert {imgUrl title description okText cancelText}backgroundStyle energyDealCallToAction{text url redirectUrlStartsWith link action enabled} settingsScreen{settings {key value valueType valueIsArray isReadOnly inputOptions{type title description pickerOptions {values postFix} rangeOptions{max min step defaultValue displayText displayTextPlural} selectOptions {value title description imgUrl iconName isRecommendedOption} textFieldOptions{imgUrl format placeholder} timeOptions{doNotSetATimeText}}} settingsLayout{uid type title description valueText imgUrl iconName isUpdated isEnabled callToAction {text url redirectUrlStartsWith link action enabled} childItems{uid type title description valueText imgUrl iconName isUpdated isEnabled callToAction {text url redirectUrlStartsWith link action enabled} settingKey settingKeyForIsHidden} settingKey settingKeyForIsHidden}} settingsButtonText settingsButton {text url redirectUrlStartsWith link action enabled}enterPincode message {id title description style iconName iconSrc callToAction {text url redirectUrlStartsWith link action enabled} dismissButtonText} scheduleSuspendedText faqUrl battery { percent percentColor isCharging chargeLimit}}}}}"}' | ||
|
||
def __init__(self, | ||
hass: HomeAssistant, | ||
raw_data: str, | ||
polestar_api: PolestarApi) -> None: | ||
|
||
ev_id = raw_data.get("id").replace("-", "")[:8] | ||
ev_name = raw_data.get("name") | ||
self.id = ev_id | ||
self.name = ev_name | ||
self.raw_data = raw_data | ||
self.polestar_api = polestar_api | ||
username: str, | ||
password: str | ||
) -> None: | ||
self.id = None | ||
self.name = "Polestar " | ||
self._session = async_get_clientsession(hass, verify_ssl=False) | ||
self.polestarApi = PolestarApi(username, password) | ||
|
||
self.vin = None | ||
self.cache_data = {} | ||
self.latest_call_code = None | ||
self.updating = False | ||
disable_warnings() | ||
|
||
async def init(self) -> None: | ||
self.id = "polestar{}".format(self.name) | ||
if self.name is None: | ||
self.name = f"{self.info.identity} ({self.host})" | ||
async def init(self): | ||
await self.polestarApi.init() | ||
vin = self.get_cache_data('getConsumerCarsV2', 'vin') | ||
if vin: | ||
# fill the vin and id in the constructor | ||
self.vin = vin | ||
self.id = vin[:8] | ||
self.name = "Polestar " + vin[-4:] | ||
|
||
def get_cache_data(self, query: str, field_name: str, skip_cache: bool = False): | ||
return self.polestarApi.get_cache_data(query, field_name, skip_cache) | ||
|
||
async def get_ev_data(self): | ||
await self.polestarApi.get_ev_data(self.vin) | ||
|
||
@property | ||
def status(self) -> str: | ||
return self._status | ||
def get_latest_data(self, query: str, field_name: str): | ||
return self.polestarApi.get_latest_data(query, field_name) | ||
|
||
@Throttle(timedelta(seconds=10)) | ||
async def async_update(self) -> None: | ||
self.raw_data = await self.polestar_api.get_ev_data() | ||
def get_latest_call_code(self): | ||
return self.polestarApi.latest_call_code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "1.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
from datetime import datetime, timedelta | ||
import logging | ||
import json | ||
from urllib.parse import parse_qs, urlparse | ||
import aiohttp | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class PolestarAuth: | ||
""" base class for Polestar authentication""" | ||
|
||
def __init__(self, username: str, password: str) -> None: | ||
self.username = username | ||
self.password = password | ||
self.access_token = None | ||
self.refresh_token = None | ||
self.token_expiry = None | ||
self.latest_call_code = None | ||
self.session = aiohttp.ClientSession() | ||
|
||
async def get_token(self) -> None: | ||
code = await self._get_code() | ||
if code is None: | ||
return | ||
|
||
# get token | ||
params = { | ||
"query": "query getAuthToken($code: String!) { getAuthToken(code: $code) { id_token access_token refresh_token expires_in }}", | ||
"operationName": "getAuthToken", | ||
"variables": json.dumps({"code": code}) | ||
} | ||
|
||
headers = { | ||
"Content-Type": "application/json" | ||
} | ||
result = await self.session.get("https://pc-api.polestar.com/eu-north-1/auth/", params=params, headers=headers) | ||
self.latest_call_code = result.status | ||
if result.status != 200: | ||
_LOGGER.error(f"Error getting token {result.status}") | ||
return | ||
resultData = await result.json() | ||
_LOGGER.debug(resultData) | ||
|
||
if resultData['data']: | ||
self.access_token = resultData['data']['getAuthToken']['access_token'] | ||
self.refresh_token = resultData['data']['getAuthToken']['refresh_token'] | ||
self.token_expiry = datetime.now( | ||
) + timedelta(seconds=resultData['data']['getAuthToken']['expires_in']) | ||
# ID Token | ||
|
||
_LOGGER.debug(f"Response {self.access_token}") | ||
|
||
async def _get_code(self) -> None: | ||
resumePath = await self._get_resume_path() | ||
parsed_url = urlparse(resumePath) | ||
query_params = parse_qs(parsed_url.query) | ||
|
||
# check if code is in query_params | ||
if query_params.get('code'): | ||
return query_params.get(('code'))[0] | ||
|
||
# get the resumePath | ||
if query_params.get('resumePath'): | ||
resumePath = query_params.get(('resumePath'))[0] | ||
|
||
if resumePath is None: | ||
return | ||
|
||
params = { | ||
'client_id': 'polmystar' | ||
} | ||
data = { | ||
'pf.username': self.username, | ||
'pf.pass': self.password | ||
} | ||
result = await self.session.post( | ||
f"https://polestarid.eu.polestar.com/as/{resumePath}/resume/as/authorization.ping", | ||
params=params, | ||
data=data | ||
) | ||
self.latest_call_code = result.status | ||
if result.status != 200: | ||
_LOGGER.error(f"Error getting code {result.status}") | ||
return | ||
# get the realUrl | ||
url = result.url | ||
|
||
parsed_url = urlparse(result.real_url.raw_path_qs) | ||
query_params = parse_qs(parsed_url.query) | ||
|
||
if not query_params.get('code'): | ||
_LOGGER.error(f"Error getting code in {query_params}") | ||
_LOGGER.warning("Check if username and password are correct") | ||
return | ||
|
||
code = query_params.get(('code'))[0] | ||
|
||
# sign-in-callback | ||
result = await self.session.get("https://www.polestar.com/sign-in-callback?code=" + code) | ||
self.latest_call_code = result.status | ||
if result.status != 200: | ||
_LOGGER.error(f"Error getting code callback {result.status}") | ||
return | ||
# url encode the code | ||
result = await self.session.get(url) | ||
self.latest_call_code = result.status | ||
|
||
return code | ||
|
||
async def _get_resume_path(self): | ||
# Get Resume Path | ||
params = { | ||
"response_type": "code", | ||
"client_id": "polmystar", | ||
"redirect_uri": "https://www.polestar.com/sign-in-callback" | ||
} | ||
result = await self.session.get("https://polestarid.eu.polestar.com/as/authorization.oauth2", params=params) | ||
if result.status != 200: | ||
_LOGGER.error(f"Error getting resume path {result.status}") | ||
return | ||
return result.real_url.raw_path_qs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
CACHE_TIME = 30 | ||
|
||
CAR_INFO_DATA = "getConsumerCarsV2" | ||
ODO_METER_DATA = "getOdometerData" | ||
BATTERY_DATA = "getBatteryData" |
Oops, something went wrong.