pycupra 0.0.14__tar.gz → 0.1.0__tar.gz
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-0.0.14/pycupra.egg-info → pycupra-0.1.0}/PKG-INFO +12 -2
- {pycupra-0.0.14 → pycupra-0.1.0}/README.md +11 -1
- {pycupra-0.0.14 → pycupra-0.1.0}/example/PyCupra.py +24 -8
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/__init__.py +1 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/__version__.py +1 -1
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/connection.py +66 -9
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/const.py +11 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/dashboard.py +34 -0
- pycupra-0.1.0/pycupra/firebase.py +73 -0
- pycupra-0.1.0/pycupra/firebase_messaging/__init__.py +9 -0
- pycupra-0.1.0/pycupra/firebase_messaging/const.py +32 -0
- pycupra-0.1.0/pycupra/firebase_messaging/fcmpushclient.py +796 -0
- pycupra-0.1.0/pycupra/firebase_messaging/fcmregister.py +519 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/android_checkin.proto +96 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/android_checkin_pb2.py +36 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/android_checkin_pb2.pyi +257 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/checkin.proto +155 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/checkin_pb2.py +31 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/checkin_pb2.pyi +424 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/mcs.proto +328 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/mcs_pb2.py +65 -0
- pycupra-0.1.0/pycupra/firebase_messaging/proto/mcs_pb2.pyi +1118 -0
- pycupra-0.1.0/pycupra/firebase_messaging/py.typed +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/vehicle.py +272 -13
- {pycupra-0.0.14 → pycupra-0.1.0/pycupra.egg-info}/PKG-INFO +12 -2
- pycupra-0.1.0/pycupra.egg-info/SOURCES.txt +36 -0
- pycupra-0.0.14/pycupra.egg-info/SOURCES.txt +0 -21
- {pycupra-0.0.14 → pycupra-0.1.0}/.gitignore +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/LICENSE +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/exceptions.py +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra/utilities.py +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra.egg-info/dependency_links.txt +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra.egg-info/requires.txt +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/pycupra.egg-info/top_level.txt +0 -0
- /pycupra-0.0.14/cupra_credentials.json.demo → /pycupra-0.1.0/pycupra_credentials.json.demo +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/requirements.txt +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/setup.cfg +0 -0
- {pycupra-0.0.14 → pycupra-0.1.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pycupra
|
3
|
-
Version: 0.0
|
3
|
+
Version: 0.1.0
|
4
4
|
Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
|
5
5
|
Home-page: https://github.com/WulfgarW/pycupra
|
6
6
|
Author: WulfgarW
|
@@ -39,11 +39,21 @@ No licence, public domain, no guarantees, feel free to use for anything. Please
|
|
39
39
|
|
40
40
|
## Breaking changes
|
41
41
|
|
42
|
+
- The method vehicle.update(updateType) supports 3 different update types:
|
43
|
+
- updateType=0: Small update (=only get_basiccardata() and get_statusreport are called). If the last full update is more than 1100 seconds ago, then a full update is performed.
|
44
|
+
- updateType=1: Full update (nearly all get-methods are called. The model images and the capabilitites are refreshed only every 2 hours.)
|
45
|
+
- updateType=2: Like updateType=0, but ignoring the nightly update reduction
|
46
|
+
|
47
|
+
- Nightly update reduction: If nightly reduction is activated and the current time is within the time frame between 22:00 and 05:00, then vehicle.update(0) performs a full update, if the last full update is more than 1700 seconds ago. If that's not the case, vehicle.update(0) does nothing.
|
48
|
+
|
49
|
+
- PyCupra can ask the Seat/Cupra portal to send push notifications to PyCupra if the charging status or the climatisation status has changed or when the API has finished a request like lock or unlock vehicle, start or stop charging or change departure timers or ....
|
50
|
+
|
42
51
|
## Thanks to
|
43
52
|
|
44
53
|
- [RobinostLund](https://github.com/robinostlund/volkswagencarnet) for initial project for Volkswagen Carnet I was able to fork
|
45
54
|
- [Farfar](https://github.com/Farfar) for modifications related to electric engines
|
46
55
|
- [tanelvakker](https://github.com/tanelvakker) for modifications related to correct SPIN handling for various actions and using correct URLs also for MY2021
|
56
|
+
- [sdb9696](https://github.com/sdb9696) for the firebase-messaging package that is used in PyCupra with only minor modifications
|
47
57
|
|
48
58
|
### Example
|
49
59
|
|
@@ -52,6 +62,6 @@ When logged in the library will automatically create a vehicle object for every
|
|
52
62
|
Method get_vehicles will fetch vehicle basic information and create Vehicle class objects for all associated vehicles in account.
|
53
63
|
To update all available data use the update_all method of the Connect class. This will call the update function for all registered vehicles, which in turn will fetch data from all available API endpoints.
|
54
64
|
|
55
|
-
The file *
|
65
|
+
The file *pycupra_credentials.json.demo* explains the data structure of the credentials file.
|
56
66
|
|
57
67
|
|
@@ -14,11 +14,21 @@ No licence, public domain, no guarantees, feel free to use for anything. Please
|
|
14
14
|
|
15
15
|
## Breaking changes
|
16
16
|
|
17
|
+
- The method vehicle.update(updateType) supports 3 different update types:
|
18
|
+
- updateType=0: Small update (=only get_basiccardata() and get_statusreport are called). If the last full update is more than 1100 seconds ago, then a full update is performed.
|
19
|
+
- updateType=1: Full update (nearly all get-methods are called. The model images and the capabilitites are refreshed only every 2 hours.)
|
20
|
+
- updateType=2: Like updateType=0, but ignoring the nightly update reduction
|
21
|
+
|
22
|
+
- Nightly update reduction: If nightly reduction is activated and the current time is within the time frame between 22:00 and 05:00, then vehicle.update(0) performs a full update, if the last full update is more than 1700 seconds ago. If that's not the case, vehicle.update(0) does nothing.
|
23
|
+
|
24
|
+
- PyCupra can ask the Seat/Cupra portal to send push notifications to PyCupra if the charging status or the climatisation status has changed or when the API has finished a request like lock or unlock vehicle, start or stop charging or change departure timers or ....
|
25
|
+
|
17
26
|
## Thanks to
|
18
27
|
|
19
28
|
- [RobinostLund](https://github.com/robinostlund/volkswagencarnet) for initial project for Volkswagen Carnet I was able to fork
|
20
29
|
- [Farfar](https://github.com/Farfar) for modifications related to electric engines
|
21
30
|
- [tanelvakker](https://github.com/tanelvakker) for modifications related to correct SPIN handling for various actions and using correct URLs also for MY2021
|
31
|
+
- [sdb9696](https://github.com/sdb9696) for the firebase-messaging package that is used in PyCupra with only minor modifications
|
22
32
|
|
23
33
|
### Example
|
24
34
|
|
@@ -27,6 +37,6 @@ When logged in the library will automatically create a vehicle object for every
|
|
27
37
|
Method get_vehicles will fetch vehicle basic information and create Vehicle class objects for all associated vehicles in account.
|
28
38
|
To update all available data use the update_all method of the Connect class. This will call the update function for all registered vehicles, which in turn will fetch data from all available API endpoints.
|
29
39
|
|
30
|
-
The file *
|
40
|
+
The file *pycupra_credentials.json.demo* explains the data structure of the credentials file.
|
31
41
|
|
32
42
|
|
@@ -26,9 +26,10 @@ BRAND = 'cupra' # or 'seat' (Change it to 'seat' if you want to connect to the M
|
|
26
26
|
|
27
27
|
PRINTRESPONSE = True
|
28
28
|
INTERVAL = 5
|
29
|
-
TOKEN_FILE_NAME_AND_PATH='./
|
30
|
-
CREDENTIALS_FILE_NAME_AND_PATH='./
|
31
|
-
|
29
|
+
TOKEN_FILE_NAME_AND_PATH='./pycupra_token.json'
|
30
|
+
CREDENTIALS_FILE_NAME_AND_PATH='./pycupra_credentials.json'
|
31
|
+
FIREBASE_CREDENTIALS_FILE_NAME_AND_PATH='./pycupra_firebase_credentials.json'
|
32
|
+
ALL_ATTRIBUTES_FILE_NAME_AND_PATH='./pycupra_all_attributes.txt'
|
32
33
|
|
33
34
|
|
34
35
|
COMPONENTS = {
|
@@ -97,7 +98,9 @@ RESOURCES = [
|
|
97
98
|
"requests_remaining",
|
98
99
|
"service_inspection",
|
99
100
|
"service_inspection_distance",
|
101
|
+
"slow_charge",
|
100
102
|
"sunroof_closed",
|
103
|
+
"target_soc",
|
101
104
|
"trip_last_average_auxillary_consumption",
|
102
105
|
"trip_last_average_electric_consumption",
|
103
106
|
"trip_last_average_fuel_consumption",
|
@@ -374,7 +377,7 @@ async def main():
|
|
374
377
|
print('# Logging on to seat.cloud.vwgroup.com #')
|
375
378
|
print('########################################')
|
376
379
|
print(f"Initiating new session to Seat Cloud with {credentials.get('username')} as username")
|
377
|
-
connection = Connection(session, BRAND, credentials.get('username'), credentials.get('password'), PRINTRESPONSE)
|
380
|
+
connection = Connection(session, BRAND, credentials.get('username'), credentials.get('password'), PRINTRESPONSE, nightlyUpdateReduction=False)
|
378
381
|
print("Attempting to login to the Seat Cloud service")
|
379
382
|
print(datetime.now())
|
380
383
|
if await connection.doLogin(tokenFile=TOKEN_FILE_NAME_AND_PATH, apiKey=credentials.get('apiKey',None)):
|
@@ -393,6 +396,13 @@ async def main():
|
|
393
396
|
instruments = set()
|
394
397
|
for vehicle in connection.vehicles:
|
395
398
|
txt = vehicle.vin
|
399
|
+
if vehicle == connection.vehicles[0]: # Firebase can only be activated for one vehicle. So we use it for the first one
|
400
|
+
newStatus = await vehicle.initialiseFirebase(FIREBASE_CREDENTIALS_FILE_NAME_AND_PATH, vehicle.update)
|
401
|
+
print('########################################')
|
402
|
+
print('# Initialisation of firebase #')
|
403
|
+
print(txt.center(40, '#'))
|
404
|
+
print(f"New status of firebase={newStatus}")
|
405
|
+
|
396
406
|
print('')
|
397
407
|
print('########################################')
|
398
408
|
print('# Setting up dashboard #')
|
@@ -523,7 +533,7 @@ async def main():
|
|
523
533
|
# Examples for using set functions:
|
524
534
|
|
525
535
|
#await demo_set_charger(vehicle, action = "start") # action = "start" or "stop"
|
526
|
-
#await demo_set_charger_current(vehicle, value='reduced')
|
536
|
+
#await demo_set_charger_current(vehicle, value='reduced') # value = 1-255/Maximum/Reduced (PHEV: 252 for reduced and 254 for max, EV: Maximum/Reduced)
|
527
537
|
|
528
538
|
#await demo_set_climatisation(vehicle, action = "start", temp=18.0) # action = "auxilliary", "electric" or "off". spin is S-PIN and only needed for aux heating
|
529
539
|
#await demo_set_climatisation_temp(vehicle, temp = 18.0) # temp = integer from 16 to 30
|
@@ -561,9 +571,15 @@ async def main():
|
|
561
571
|
print('Export of all attributes successfully completed')
|
562
572
|
else:
|
563
573
|
print('Export of all attributes failed')
|
564
|
-
|
565
|
-
|
566
|
-
|
574
|
+
|
575
|
+
if vehicle.firebaseStatus== 1: # firebase messaging activated
|
576
|
+
# Do an endless loop to wait and receive firebase messages
|
577
|
+
i=0
|
578
|
+
while True:
|
579
|
+
print(f"Sleeping for {6*INTERVAL} seconds")
|
580
|
+
await asyncio.sleep(6*INTERVAL)
|
581
|
+
i=i+1
|
582
|
+
_LOGGER.debug(f'Round {i}')
|
567
583
|
|
568
584
|
exit
|
569
585
|
|
@@ -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'}:
|
@@ -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
|
@@ -715,6 +715,32 @@ class PHeaterVentilation(Switch):
|
|
715
715
|
return dict(last_result = self.vehicle.pheater_action_status)
|
716
716
|
|
717
717
|
|
718
|
+
class SlowCharge(Switch):
|
719
|
+
def __init__(self):
|
720
|
+
super().__init__(attr="slow_charge", name="Slow charge", icon="mdi:battery")
|
721
|
+
|
722
|
+
@property
|
723
|
+
def state(self):
|
724
|
+
return self.vehicle.slow_charge
|
725
|
+
|
726
|
+
async def turn_on(self):
|
727
|
+
await self.vehicle.set_charger_current('reduced')
|
728
|
+
#await self.vehicle.update()
|
729
|
+
|
730
|
+
async def turn_off(self):
|
731
|
+
await self.vehicle.set_charger_current('maximum')
|
732
|
+
#await self.vehicle.update()
|
733
|
+
|
734
|
+
@property
|
735
|
+
def assumed_state(self):
|
736
|
+
return False
|
737
|
+
|
738
|
+
|
739
|
+
@property
|
740
|
+
def attributes(self):
|
741
|
+
return dict(last_result = self.vehicle.charger_action_status)
|
742
|
+
|
743
|
+
|
718
744
|
class Warnings(Sensor):
|
719
745
|
def __init__(self):
|
720
746
|
super().__init__(attr="warnings", name="Warnings", icon="mdi:alarm-light")
|
@@ -981,6 +1007,7 @@ def create_instruments():
|
|
981
1007
|
#CombustionClimatisationClimate(),
|
982
1008
|
Charging(),
|
983
1009
|
Warnings(),
|
1010
|
+
SlowCharge(),
|
984
1011
|
RequestResults(),
|
985
1012
|
DepartureTimer1(),
|
986
1013
|
DepartureTimer2(),
|
@@ -1010,6 +1037,13 @@ def create_instruments():
|
|
1010
1037
|
unit="%",
|
1011
1038
|
device_class="battery"
|
1012
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
|
+
),
|
1013
1047
|
Sensor(
|
1014
1048
|
attr="adblue_level",
|
1015
1049
|
name="Adblue level",
|
@@ -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
|