pycupra 0.0.15__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycupra/__init__.py +1 -0
- pycupra/__version__.py +1 -1
- pycupra/connection.py +66 -9
- pycupra/const.py +11 -0
- pycupra/dashboard.py +7 -0
- pycupra/firebase.py +73 -0
- pycupra/firebase_messaging/__init__.py +9 -0
- pycupra/firebase_messaging/const.py +32 -0
- pycupra/firebase_messaging/fcmpushclient.py +796 -0
- pycupra/firebase_messaging/fcmregister.py +519 -0
- pycupra/firebase_messaging/py.typed +0 -0
- pycupra/vehicle.py +257 -13
- {pycupra-0.0.15.dist-info → pycupra-0.1.0.dist-info}/METADATA +12 -2
- pycupra-0.1.0.dist-info/RECORD +19 -0
- pycupra-0.0.15.dist-info/RECORD +0 -13
- {pycupra-0.0.15.dist-info → pycupra-0.1.0.dist-info}/WHEEL +0 -0
- {pycupra-0.0.15.dist-info → pycupra-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.0.15.dist-info → pycupra-0.1.0.dist-info}/top_level.txt +0 -0
pycupra/__init__.py
CHANGED
pycupra/__version__.py
CHANGED
pycupra/connection.py
CHANGED
@@ -46,7 +46,7 @@ from requests_oauthlib import OAuth2Session
|
|
46
46
|
from oauthlib.oauth2.rfc6749.parameters import parse_authorization_code_response, parse_token_response, prepare_grant_uri
|
47
47
|
|
48
48
|
from aiohttp import ClientSession, ClientTimeout
|
49
|
-
from aiohttp.hdrs import METH_GET, METH_POST, METH_PUT
|
49
|
+
from aiohttp.hdrs import METH_GET, METH_POST, METH_PUT, METH_DELETE
|
50
50
|
|
51
51
|
from .const import (
|
52
52
|
HEADERS_SESSION,
|
@@ -643,6 +643,8 @@ class Connection:
|
|
643
643
|
res = {'status_code': response.status}
|
644
644
|
elif response.status == 202 and method==METH_PUT:
|
645
645
|
res = response
|
646
|
+
elif response.status == 200 and method==METH_DELETE:
|
647
|
+
res = response
|
646
648
|
elif response.status >= 200 or response.status <= 300:
|
647
649
|
# If this is a revoke token url, expect Content-Length 0 and return
|
648
650
|
if int(response.headers.get('Content-Length', 0)) == 0 and 'revoke' in url:
|
@@ -759,7 +761,7 @@ class Connection:
|
|
759
761
|
# Check if user needs to update consent
|
760
762
|
try:
|
761
763
|
await self.set_token(self._session_auth_brand)
|
762
|
-
_LOGGER.debug('Achtung! getConsentInfo auskommentiert')
|
764
|
+
#_LOGGER.debug('Achtung! getConsentInfo auskommentiert')
|
763
765
|
response = await self.get(eval(f"f'{API_MBB_STATUSDATA}'"))
|
764
766
|
if response.get('profileCompleted','incomplete'):
|
765
767
|
if response.get('profileCompleted',False):
|
@@ -1182,6 +1184,8 @@ class Connection:
|
|
1182
1184
|
"""Get charger data."""
|
1183
1185
|
await self.set_token(self._session_auth_brand)
|
1184
1186
|
try:
|
1187
|
+
chargingStatus = {}
|
1188
|
+
chargingInfo = {}
|
1185
1189
|
response = await self.get(eval(f"f'{API_CHARGING}/status'"))
|
1186
1190
|
if response.get('battery', {}):
|
1187
1191
|
chargingStatus = response
|
@@ -1203,12 +1207,16 @@ class Connection:
|
|
1203
1207
|
_LOGGER.warning(f'Could not fetch charging modes, HTTP status code: {response.get("status_code")}')
|
1204
1208
|
else:
|
1205
1209
|
_LOGGER.info('Unhandled error while trying to fetch charging modes')"""
|
1206
|
-
|
1207
|
-
'
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1210
|
+
if chargingStatus != {} and chargingInfo != {}:
|
1211
|
+
data = {'charging': {
|
1212
|
+
'status': chargingStatus,
|
1213
|
+
'info' : chargingInfo,
|
1214
|
+
#'modes' : chargingModes,
|
1215
|
+
}
|
1216
|
+
}
|
1217
|
+
else:
|
1218
|
+
_LOGGER.warning(f'getCharger() got no valid data. Returning None')
|
1219
|
+
return None
|
1212
1220
|
return data
|
1213
1221
|
except Exception as error:
|
1214
1222
|
_LOGGER.warning(f'Could not fetch charger, error: {error}')
|
@@ -1321,7 +1329,7 @@ class Connection:
|
|
1321
1329
|
if 'state' in k.lower():
|
1322
1330
|
data['state'] = response.get(key).get(k)
|
1323
1331
|
else:
|
1324
|
-
if 'Id' in key:
|
1332
|
+
if 'Id' in key or 'id' in key:
|
1325
1333
|
data['id'] = str(response.get(key))
|
1326
1334
|
if 'State' in key:
|
1327
1335
|
data['state'] = response.get(key)
|
@@ -1365,6 +1373,55 @@ class Connection:
|
|
1365
1373
|
raise
|
1366
1374
|
return False
|
1367
1375
|
|
1376
|
+
async def subscribe(self, vin, credentials):
|
1377
|
+
url = f'{APP_URI}/v2/subscriptions'
|
1378
|
+
deviceId = credentials.get('gcm',{}).get('app_id','')
|
1379
|
+
token = credentials.get('fcm',{}).get('registration',{}).get('token','')
|
1380
|
+
|
1381
|
+
data = {
|
1382
|
+
"deviceId": deviceId,
|
1383
|
+
"locale":"en_GB",
|
1384
|
+
"services":{"charging":True,"climatisation":True},
|
1385
|
+
"token": token,
|
1386
|
+
"userId": self._user_id,
|
1387
|
+
"vin":vin
|
1388
|
+
}
|
1389
|
+
return await self._setViaAPI(url, json=data)
|
1390
|
+
|
1391
|
+
async def deleteSubscription(self, credentials):
|
1392
|
+
await self.set_token(self._session_auth_brand)
|
1393
|
+
try:
|
1394
|
+
id = credentials.get('subscription',{}).get('id','')
|
1395
|
+
url = f'{APP_URI}/v1/subscriptions/{id}'
|
1396
|
+
response = await self._request(METH_DELETE, url)
|
1397
|
+
if response.status==200:
|
1398
|
+
_LOGGER.debug(f'Subscription {id} successfully deleted.')
|
1399
|
+
return response
|
1400
|
+
else:
|
1401
|
+
_LOGGER.debug(f'API did not successfully delete subscription.')
|
1402
|
+
raise SeatException(f'Invalid or no response for endpoint {url}')
|
1403
|
+
return response
|
1404
|
+
except aiohttp.client_exceptions.ClientResponseError as error:
|
1405
|
+
_LOGGER.debug(f'Request failed. Id: {id}, HTTP request headers: {self._session_headers}')
|
1406
|
+
if error.status == 401:
|
1407
|
+
_LOGGER.error('Unauthorized')
|
1408
|
+
elif error.status == 400:
|
1409
|
+
_LOGGER.error(f'Bad request')
|
1410
|
+
elif error.status == 429:
|
1411
|
+
_LOGGER.warning('Too many requests. Further requests can only be made after the end of next trip in order to protect your vehicles battery.')
|
1412
|
+
return 429
|
1413
|
+
elif error.status == 500:
|
1414
|
+
_LOGGER.error('Internal server error, server might be temporarily unavailable')
|
1415
|
+
elif error.status == 502:
|
1416
|
+
_LOGGER.error('Bad gateway, this function may not be implemented for this vehicle')
|
1417
|
+
else:
|
1418
|
+
_LOGGER.error(f'Unhandled HTTP exception: {error}')
|
1419
|
+
#return False
|
1420
|
+
except Exception as error:
|
1421
|
+
_LOGGER.error(f'Error: {error}')
|
1422
|
+
raise
|
1423
|
+
return False
|
1424
|
+
|
1368
1425
|
async def setCharger(self, vin, baseurl, mode, data):
|
1369
1426
|
"""Start/Stop charger."""
|
1370
1427
|
if mode in {'start', 'stop'}:
|
pycupra/const.py
CHANGED
@@ -181,3 +181,14 @@ REQ_STATUS = {
|
|
181
181
|
'vsr': 'fs-car/bs/vsr/v1/{BRAND}/{COUNTRY}/vehicles/{vin}/requests/{id}/jobstatus',
|
182
182
|
'default': 'fs-car/bs/{section}/v1/{BRAND}/{COUNTRY}/vehicles/{vin}/requests/{id}/status'
|
183
183
|
}
|
184
|
+
|
185
|
+
FCM_PROJECT_ID='ola-apps-prod'
|
186
|
+
FCM_APP_ID={
|
187
|
+
'cupra': '1:530284123617:android:9b9ba5a87c7ffd37fbeea0',
|
188
|
+
'seat': '1:530284123617:android:d6187613ac3d7b08fbeea0'
|
189
|
+
}
|
190
|
+
FCM_API_KEY='AIzaSyCoSp1zitklb1EDj5yQumN0VNhDizJQHLk'
|
191
|
+
FIREBASE_STATUS_NOT_INITIALISED= 0
|
192
|
+
FIREBASE_STATUS_ACTIVATED= 1
|
193
|
+
FIREBASE_STATUS_NOT_WANTED= -2
|
194
|
+
FIREBASE_STATUS_ACTIVATION_FAILED= -1
|
pycupra/dashboard.py
CHANGED
@@ -1037,6 +1037,13 @@ def create_instruments():
|
|
1037
1037
|
unit="%",
|
1038
1038
|
device_class="battery"
|
1039
1039
|
),
|
1040
|
+
Sensor(
|
1041
|
+
attr="target_soc",
|
1042
|
+
name="Target state of charge",
|
1043
|
+
icon="mdi:battery-positive",
|
1044
|
+
unit="%",
|
1045
|
+
device_class="battery"
|
1046
|
+
),
|
1040
1047
|
Sensor(
|
1041
1048
|
attr="adblue_level",
|
1042
1049
|
name="Adblue level",
|
pycupra/firebase.py
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
import logging
|
2
|
+
import asyncio
|
3
|
+
import os
|
4
|
+
import json
|
5
|
+
import string
|
6
|
+
import secrets
|
7
|
+
|
8
|
+
from .firebase_messaging import FcmPushClient, FcmRegisterConfig
|
9
|
+
|
10
|
+
from .const import (
|
11
|
+
FCM_PROJECT_ID,
|
12
|
+
FCM_API_KEY,
|
13
|
+
FCM_APP_ID
|
14
|
+
)
|
15
|
+
|
16
|
+
_LOGGER = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
class Firebase():
|
19
|
+
async def firebaseStart(self, onNotificationFunc, firebaseCredentialsFileName, brand='cupra'):
|
20
|
+
""" Starts the firebase cloud messaging receiver """
|
21
|
+
loop = asyncio.get_running_loop()
|
22
|
+
credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
|
23
|
+
#credentials = readFCMCredsFile(firebaseCredentialsFileName)
|
24
|
+
if credentials == {}:
|
25
|
+
credentials =''
|
26
|
+
|
27
|
+
fcm_project_id=FCM_PROJECT_ID
|
28
|
+
fcm_app_id=FCM_APP_ID[brand]
|
29
|
+
fcm_api_key=FCM_API_KEY
|
30
|
+
chars = string.ascii_letters + string.digits
|
31
|
+
fcmMessageSenderId = ''.join(secrets.choice(chars) for i in range(16))
|
32
|
+
fcmMessageSenderId= 'fxpWQ_'+fcmMessageSenderId
|
33
|
+
|
34
|
+
|
35
|
+
fcm_config = FcmRegisterConfig(fcm_project_id, fcm_app_id, fcm_api_key, fcmMessageSenderId)
|
36
|
+
pc = FcmPushClient(onNotificationFunc, fcm_config, credentials, onFCMCredentialsUpdated)
|
37
|
+
fcm_token = await pc.checkin_or_register(firebaseCredentialsFileName)
|
38
|
+
_LOGGER.debug(f'Firebase.checkin_or_register() returned a token:{fcm_token}')
|
39
|
+
await pc.start()
|
40
|
+
await asyncio.sleep(5)
|
41
|
+
return pc.is_started()
|
42
|
+
|
43
|
+
def readFCMCredsFile(credsFile):
|
44
|
+
""" Reads the firebase cloud messaging credentials from file"""
|
45
|
+
try:
|
46
|
+
if os.path.isfile(credsFile):
|
47
|
+
with open(credsFile, "r") as f:
|
48
|
+
credString=f.read()
|
49
|
+
f.close()
|
50
|
+
creds=json.loads(credString)
|
51
|
+
return creds
|
52
|
+
else:
|
53
|
+
_LOGGER.debug(f'{credsFile} not found.')
|
54
|
+
return {}
|
55
|
+
except:
|
56
|
+
_LOGGER.warning('readFCMCredsFile() not successful.')
|
57
|
+
return ''
|
58
|
+
|
59
|
+
def writeFCMCredsFile(creds, firebaseCredentialsFileName):
|
60
|
+
""" Saves the firebase cloud messaging credentials to a file for future use """
|
61
|
+
try:
|
62
|
+
with open(firebaseCredentialsFileName, "w") as f:
|
63
|
+
f.write(json.dumps(creds))
|
64
|
+
f.close()
|
65
|
+
except Exception as e:
|
66
|
+
_LOGGER.warning(f'writeFCMCredsFile() not successful. Error: {e}')
|
67
|
+
|
68
|
+
async def onFCMCredentialsUpdated(creds, firebaseCredentialsFileName):
|
69
|
+
""" Is called from firebase-messaging package """
|
70
|
+
loop = asyncio.get_running_loop()
|
71
|
+
await loop.run_in_executor(None, writeFCMCredsFile, creds, firebaseCredentialsFileName)
|
72
|
+
#writeFCMCredsFile(creds, firebaseCredentialsFileName)
|
73
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"""Constants module."""
|
2
|
+
|
3
|
+
GCM_REGISTER_URL = "https://android.clients.google.com/c2dm/register3"
|
4
|
+
GCM_CHECKIN_URL = "https://android.clients.google.com/checkin"
|
5
|
+
GCM_SERVER_KEY_BIN = (
|
6
|
+
b"\x04\x33\x94\xf7\xdf\xa1\xeb\xb1\xdc\x03\xa2\x5e\x15\x71\xdb\x48\xd3"
|
7
|
+
+ b"\x2e\xed\xed\xb2\x34\xdb\xb7\x47\x3a\x0c\x8f\xc4\xcc\xe1\x6f\x3c"
|
8
|
+
+ b"\x8c\x84\xdf\xab\xb6\x66\x3e\xf2\x0c\xd4\x8b\xfe\xe3\xf9\x76\x2f"
|
9
|
+
+ b"\x14\x1c\x63\x08\x6a\x6f\x2d\xb1\x1a\x95\xb0\xce\x37\xc0\x9c\x6e"
|
10
|
+
)
|
11
|
+
# urlsafe b64 encoding of the binary key with = padding removed
|
12
|
+
GCM_SERVER_KEY_B64 = (
|
13
|
+
"BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoM"
|
14
|
+
+ "j8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4"
|
15
|
+
)
|
16
|
+
|
17
|
+
FCM_SUBSCRIBE_URL = "https://fcm.googleapis.com/fcm/connect/subscribe/"
|
18
|
+
FCM_SEND_URL = "https://fcm.googleapis.com/fcm/send/"
|
19
|
+
|
20
|
+
FCM_API = "https://fcm.googleapis.com/v1/"
|
21
|
+
FCM_REGISTRATION = "https://fcmregistrations.googleapis.com/v1/"
|
22
|
+
FCM_INSTALLATION = "https://firebaseinstallations.googleapis.com/v1/"
|
23
|
+
AUTH_VERSION = "FIS_v2"
|
24
|
+
SDK_VERSION = "w:0.6.6"
|
25
|
+
|
26
|
+
DOORBELLS_ENDPOINT = "/clients_api/doorbots/{0}"
|
27
|
+
|
28
|
+
MCS_VERSION = 41
|
29
|
+
MCS_HOST = "mtalk.google.com"
|
30
|
+
MCS_PORT = 5228
|
31
|
+
MCS_SELECTIVE_ACK_ID = 12
|
32
|
+
MCS_STREAM_ACK_ID = 13
|