pycupra 0.1.4__py3-none-any.whl → 0.1.6__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/__version__.py +1 -1
- pycupra/connection.py +1858 -1799
- pycupra/dashboard.py +2 -0
- pycupra/firebase.py +37 -20
- pycupra/utilities.py +116 -116
- pycupra/vehicle.py +96 -15
- {pycupra-0.1.4.dist-info → pycupra-0.1.6.dist-info}/METADATA +1 -1
- {pycupra-0.1.4.dist-info → pycupra-0.1.6.dist-info}/RECORD +11 -11
- {pycupra-0.1.4.dist-info → pycupra-0.1.6.dist-info}/WHEEL +0 -0
- {pycupra-0.1.4.dist-info → pycupra-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.1.4.dist-info → pycupra-0.1.6.dist-info}/top_level.txt +0 -0
pycupra/dashboard.py
CHANGED
@@ -453,6 +453,7 @@ class RequestRefresh(Switch):
|
|
453
453
|
return self.vehicle.refresh_data
|
454
454
|
|
455
455
|
async def turn_on(self):
|
456
|
+
_LOGGER.debug('User has called RequestRefresh().')
|
456
457
|
await self.vehicle.set_refresh()
|
457
458
|
await self.vehicle.update(updateType=1) #full update after set_refresh
|
458
459
|
if self.callback is not None:
|
@@ -479,6 +480,7 @@ class RequestUpdate(Switch):
|
|
479
480
|
return False #self.vehicle.update
|
480
481
|
|
481
482
|
async def turn_on(self):
|
483
|
+
_LOGGER.debug('User has called RequestUpdate().')
|
482
484
|
await self.vehicle.update(updateType=1) #full update after set_refresh
|
483
485
|
if self.callback is not None:
|
484
486
|
self.callback()
|
pycupra/firebase.py
CHANGED
@@ -16,29 +16,46 @@ from .const import (
|
|
16
16
|
_LOGGER = logging.getLogger(__name__)
|
17
17
|
|
18
18
|
class Firebase():
|
19
|
+
def __init__(self):
|
20
|
+
self._pushClient = None
|
21
|
+
|
19
22
|
async def firebaseStart(self, onNotificationFunc, firebaseCredentialsFileName, brand='cupra'):
|
20
23
|
""" Starts the firebase cloud messaging receiver """
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
credentials
|
24
|
+
try:
|
25
|
+
loop = asyncio.get_running_loop()
|
26
|
+
credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
|
27
|
+
#credentials = readFCMCredsFile(firebaseCredentialsFileName)
|
28
|
+
if credentials == {}:
|
29
|
+
credentials =''
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
fcm_project_id=FCM_PROJECT_ID
|
32
|
+
fcm_app_id=FCM_APP_ID[brand]
|
33
|
+
fcm_api_key=FCM_API_KEY
|
34
|
+
chars = string.ascii_letters + string.digits
|
35
|
+
fcmMessageSenderId = ''.join(secrets.choice(chars) for i in range(16))
|
36
|
+
fcmMessageSenderId= 'fxpWQ_'+fcmMessageSenderId
|
33
37
|
|
38
|
+
fcm_config = FcmRegisterConfig(fcm_project_id, fcm_app_id, fcm_api_key, fcmMessageSenderId)
|
39
|
+
self._pushClient = FcmPushClient(onNotificationFunc, fcm_config, credentials, onFCMCredentialsUpdated)
|
40
|
+
fcm_token = await self._pushClient.checkin_or_register(firebaseCredentialsFileName)
|
41
|
+
_LOGGER.debug(f'Firebase.checkin_or_register() returned a token:{fcm_token}')
|
42
|
+
await self._pushClient.start()
|
43
|
+
await asyncio.sleep(5)
|
44
|
+
return self._pushClient.is_started()
|
45
|
+
except Exception as e:
|
46
|
+
_LOGGER.error('Error in firebaseStart. Error: {e}')
|
47
|
+
return False
|
34
48
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
async def firebaseStop(self):
|
50
|
+
""" Stops the firebase cloud messaging receiver """
|
51
|
+
try:
|
52
|
+
await self._pushClient.stop()
|
53
|
+
#await asyncio.sleep(5)
|
54
|
+
self._pushClient = None
|
55
|
+
return True
|
56
|
+
except Exception as e:
|
57
|
+
_LOGGER.error('Error in firebaseStop. Error: {e}')
|
58
|
+
return False
|
42
59
|
|
43
60
|
def readFCMCredsFile(credsFile):
|
44
61
|
""" Reads the firebase cloud messaging credentials from file"""
|
@@ -52,8 +69,8 @@ def readFCMCredsFile(credsFile):
|
|
52
69
|
else:
|
53
70
|
_LOGGER.debug(f'{credsFile} not found.')
|
54
71
|
return {}
|
55
|
-
except:
|
56
|
-
_LOGGER.warning('readFCMCredsFile() not successful.')
|
72
|
+
except Exception as e:
|
73
|
+
_LOGGER.warning('readFCMCredsFile() not successful. Error: {e}')
|
57
74
|
return ''
|
58
75
|
|
59
76
|
def writeFCMCredsFile(creds, firebaseCredentialsFileName):
|
pycupra/utilities.py
CHANGED
@@ -1,116 +1,116 @@
|
|
1
|
-
from datetime import date, datetime
|
2
|
-
from base64 import b64encode
|
3
|
-
from string import ascii_letters as letters, digits
|
4
|
-
from sys import argv
|
5
|
-
from os import environ as env
|
6
|
-
from os.path import join, dirname, expanduser
|
7
|
-
from itertools import product
|
8
|
-
import json
|
9
|
-
import logging
|
10
|
-
import re
|
11
|
-
|
12
|
-
_LOGGER = logging.getLogger(__name__)
|
13
|
-
|
14
|
-
|
15
|
-
def read_config():
|
16
|
-
"""Read config from file."""
|
17
|
-
for directory, filename in product(
|
18
|
-
[
|
19
|
-
dirname(argv[0]),
|
20
|
-
expanduser("~"),
|
21
|
-
env.get("XDG_CONFIG_HOME", join(expanduser("~"), ".config")),
|
22
|
-
],
|
23
|
-
["seat.conf", ".seat.conf"],
|
24
|
-
):
|
25
|
-
try:
|
26
|
-
config = join(directory, filename)
|
27
|
-
_LOGGER.debug("checking for config file %s", config)
|
28
|
-
with open(config) as config:
|
29
|
-
return dict(
|
30
|
-
x.split(": ")
|
31
|
-
for x in config.read().strip().splitlines()
|
32
|
-
if not x.startswith("#")
|
33
|
-
)
|
34
|
-
except (IOError, OSError):
|
35
|
-
continue
|
36
|
-
return {}
|
37
|
-
|
38
|
-
|
39
|
-
def json_loads(s):
|
40
|
-
return json.loads(s, object_hook=obj_parser)
|
41
|
-
|
42
|
-
|
43
|
-
def obj_parser(obj):
|
44
|
-
"""Parse datetime."""
|
45
|
-
for key, val in obj.items():
|
46
|
-
try:
|
47
|
-
obj[key] = datetime.strptime(val, "%Y-%m-%dT%H:%M:%S%z")
|
48
|
-
except (TypeError, ValueError):
|
49
|
-
pass
|
50
|
-
return obj
|
51
|
-
|
52
|
-
|
53
|
-
def find_path(src, path):
|
54
|
-
"""Simple navigation of a hierarchical dict structure using XPATH-like syntax.
|
55
|
-
|
56
|
-
>>> find_path(dict(a=1), 'a')
|
57
|
-
1
|
58
|
-
|
59
|
-
>>> find_path(dict(a=1), '')
|
60
|
-
{'a': 1}
|
61
|
-
|
62
|
-
>>> find_path(dict(a=None), 'a')
|
63
|
-
|
64
|
-
|
65
|
-
>>> find_path(dict(a=1), 'b')
|
66
|
-
Traceback (most recent call last):
|
67
|
-
...
|
68
|
-
KeyError: 'b'
|
69
|
-
|
70
|
-
>>> find_path(dict(a=dict(b=1)), 'a.b')
|
71
|
-
1
|
72
|
-
|
73
|
-
>>> find_path(dict(a=dict(b=1)), 'a')
|
74
|
-
{'b': 1}
|
75
|
-
|
76
|
-
>>> find_path(dict(a=dict(b=1)), 'a.c')
|
77
|
-
Traceback (most recent call last):
|
78
|
-
...
|
79
|
-
KeyError: 'c'
|
80
|
-
|
81
|
-
"""
|
82
|
-
if not path:
|
83
|
-
return src
|
84
|
-
if isinstance(path, str):
|
85
|
-
path = path.split(".")
|
86
|
-
return find_path(src[path[0]], path[1:])
|
87
|
-
|
88
|
-
|
89
|
-
def is_valid_path(src, path):
|
90
|
-
"""
|
91
|
-
>>> is_valid_path(dict(a=1), 'a')
|
92
|
-
True
|
93
|
-
|
94
|
-
>>> is_valid_path(dict(a=1), '')
|
95
|
-
True
|
96
|
-
|
97
|
-
>>> is_valid_path(dict(a=1), None)
|
98
|
-
True
|
99
|
-
|
100
|
-
>>> is_valid_path(dict(a=1), 'b')
|
101
|
-
False
|
102
|
-
"""
|
103
|
-
try:
|
104
|
-
find_path(src, path)
|
105
|
-
return True
|
106
|
-
except KeyError:
|
107
|
-
return False
|
108
|
-
|
109
|
-
|
110
|
-
def camel2slug(s):
|
111
|
-
"""Convert camelCase to camel_case.
|
112
|
-
|
113
|
-
>>> camel2slug('fooBar')
|
114
|
-
'foo_bar'
|
115
|
-
"""
|
116
|
-
return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_")
|
1
|
+
from datetime import date, datetime
|
2
|
+
from base64 import b64encode
|
3
|
+
from string import ascii_letters as letters, digits
|
4
|
+
from sys import argv
|
5
|
+
from os import environ as env
|
6
|
+
from os.path import join, dirname, expanduser
|
7
|
+
from itertools import product
|
8
|
+
import json
|
9
|
+
import logging
|
10
|
+
import re
|
11
|
+
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
def read_config():
|
16
|
+
"""Read config from file."""
|
17
|
+
for directory, filename in product(
|
18
|
+
[
|
19
|
+
dirname(argv[0]),
|
20
|
+
expanduser("~"),
|
21
|
+
env.get("XDG_CONFIG_HOME", join(expanduser("~"), ".config")),
|
22
|
+
],
|
23
|
+
["seat.conf", ".seat.conf"],
|
24
|
+
):
|
25
|
+
try:
|
26
|
+
config = join(directory, filename)
|
27
|
+
_LOGGER.debug("checking for config file %s", config)
|
28
|
+
with open(config) as config:
|
29
|
+
return dict(
|
30
|
+
x.split(": ")
|
31
|
+
for x in config.read().strip().splitlines()
|
32
|
+
if not x.startswith("#")
|
33
|
+
)
|
34
|
+
except (IOError, OSError):
|
35
|
+
continue
|
36
|
+
return {}
|
37
|
+
|
38
|
+
|
39
|
+
def json_loads(s):
|
40
|
+
return json.loads(s, object_hook=obj_parser)
|
41
|
+
|
42
|
+
|
43
|
+
def obj_parser(obj):
|
44
|
+
"""Parse datetime."""
|
45
|
+
for key, val in obj.items():
|
46
|
+
try:
|
47
|
+
obj[key] = datetime.strptime(val, "%Y-%m-%dT%H:%M:%S%z")
|
48
|
+
except (TypeError, ValueError):
|
49
|
+
pass
|
50
|
+
return obj
|
51
|
+
|
52
|
+
|
53
|
+
def find_path(src, path):
|
54
|
+
"""Simple navigation of a hierarchical dict structure using XPATH-like syntax.
|
55
|
+
|
56
|
+
>>> find_path(dict(a=1), 'a')
|
57
|
+
1
|
58
|
+
|
59
|
+
>>> find_path(dict(a=1), '')
|
60
|
+
{'a': 1}
|
61
|
+
|
62
|
+
>>> find_path(dict(a=None), 'a')
|
63
|
+
|
64
|
+
|
65
|
+
>>> find_path(dict(a=1), 'b')
|
66
|
+
Traceback (most recent call last):
|
67
|
+
...
|
68
|
+
KeyError: 'b'
|
69
|
+
|
70
|
+
>>> find_path(dict(a=dict(b=1)), 'a.b')
|
71
|
+
1
|
72
|
+
|
73
|
+
>>> find_path(dict(a=dict(b=1)), 'a')
|
74
|
+
{'b': 1}
|
75
|
+
|
76
|
+
>>> find_path(dict(a=dict(b=1)), 'a.c')
|
77
|
+
Traceback (most recent call last):
|
78
|
+
...
|
79
|
+
KeyError: 'c'
|
80
|
+
|
81
|
+
"""
|
82
|
+
if not path:
|
83
|
+
return src
|
84
|
+
if isinstance(path, str):
|
85
|
+
path = path.split(".")
|
86
|
+
return find_path(src[path[0]], path[1:])
|
87
|
+
|
88
|
+
|
89
|
+
def is_valid_path(src, path):
|
90
|
+
"""
|
91
|
+
>>> is_valid_path(dict(a=1), 'a')
|
92
|
+
True
|
93
|
+
|
94
|
+
>>> is_valid_path(dict(a=1), '')
|
95
|
+
True
|
96
|
+
|
97
|
+
>>> is_valid_path(dict(a=1), None)
|
98
|
+
True
|
99
|
+
|
100
|
+
>>> is_valid_path(dict(a=1), 'b')
|
101
|
+
False
|
102
|
+
"""
|
103
|
+
try:
|
104
|
+
find_path(src, path)
|
105
|
+
return True
|
106
|
+
except KeyError:
|
107
|
+
return False
|
108
|
+
|
109
|
+
|
110
|
+
def camel2slug(s):
|
111
|
+
"""Convert camelCase to camel_case.
|
112
|
+
|
113
|
+
>>> camel2slug('fooBar')
|
114
|
+
'foo_bar'
|
115
|
+
"""
|
116
|
+
return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_")
|
pycupra/vehicle.py
CHANGED
@@ -34,7 +34,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
34
34
|
DATEZERO = datetime(1970,1,1)
|
35
35
|
class Vehicle:
|
36
36
|
def __init__(self, conn, data):
|
37
|
-
_LOGGER.debug(f'Creating Vehicle class object with data {data}')
|
37
|
+
_LOGGER.debug(conn.anonymise(f'Creating Vehicle class object with data {data}'))
|
38
38
|
self._connection = conn
|
39
39
|
self._url = data.get('vin', '')
|
40
40
|
self._connectivities = data.get('connectivities', '')
|
@@ -50,6 +50,7 @@ class Vehicle:
|
|
50
50
|
self._firebaseCredentialsFileName = None
|
51
51
|
self._firebaseLastMessageId = ''
|
52
52
|
self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
|
53
|
+
self.firebase = None
|
53
54
|
self.updateCallback = None
|
54
55
|
|
55
56
|
self._requests = {
|
@@ -76,6 +77,7 @@ class Vehicle:
|
|
76
77
|
'vehicleHealthWarnings': {'active': False, 'reason': 'not supported'},
|
77
78
|
'state': {'active': False, 'reason': 'not supported'},
|
78
79
|
'charging': {'active': False, 'reason': 'not supported', 'supportsTargetStateOfCharge': False},
|
80
|
+
'chargingProfiles': {'active': False, 'reason': 'not supported', "supportsTimerClimatisation": False,"supportsVehiclePositionedInProfileID": False,"supportsSingleTimer": False},
|
79
81
|
'honkAndFlash': {'active': False, 'reason': 'not supported'},
|
80
82
|
'parkingPosition': {'active': False, 'reason': 'not supported'},
|
81
83
|
'departureTimers': {'active': False, 'reason': 'not supported', 'supportsSingleTimer': False},
|
@@ -121,6 +123,10 @@ class Vehicle:
|
|
121
123
|
data['supportsTargetStateOfCharge']=True
|
122
124
|
if capa['parameters'].get('supportsSingleTimer',False)==True or capa['parameters'].get('supportsSingleTimer',False)=='true':
|
123
125
|
data['supportsSingleTimer']=True
|
126
|
+
if capa['parameters'].get('supportsVehiclePositionedInProfileID',False)==True or capa['parameters'].get('supportsVehiclePositionedInProfileID',False)=='true':
|
127
|
+
data['supportsVehiclePositionedInProfileID']=True
|
128
|
+
if capa['parameters'].get('supportsTimerClimatisation',False)==True or capa['parameters'].get('supportsTimerClimatisation',False)=='true':
|
129
|
+
data['supportsTimerClimatisation']=True
|
124
130
|
self._relevantCapabilties[id].update(data)
|
125
131
|
|
126
132
|
|
@@ -179,7 +185,7 @@ class Vehicle:
|
|
179
185
|
if hasattr(self, '_last_full_update'):
|
180
186
|
_LOGGER.debug(f'last_full_update= {self._last_full_update}, fullUpdateExpired= {fullUpdateExpired}.')
|
181
187
|
if updateType!=1 and (hasattr(self, '_last_full_update') and self._last_full_update>fullUpdateExpired):
|
182
|
-
_LOGGER.debug(f'Just performed small update for vehicle with VIN {self.vin}.')
|
188
|
+
_LOGGER.debug(f'Just performed small update for vehicle with VIN {self._connection.anonymise(self.vin)}.')
|
183
189
|
return True
|
184
190
|
|
185
191
|
# Data to be updated less often
|
@@ -201,13 +207,13 @@ class Vehicle:
|
|
201
207
|
return_exceptions=True
|
202
208
|
)
|
203
209
|
self._last_full_update = datetime.now(tz=None)
|
204
|
-
_LOGGER.debug(f'Performed full update for vehicle with VIN {self.vin}.')
|
210
|
+
_LOGGER.debug(f'Performed full update for vehicle with VIN {self._connection.anonymise(self.vin)}.')
|
205
211
|
_LOGGER.debug(f'So far about {self._connection._sessionRequestCounter} API calls since {self._connection._sessionRequestTimestamp}.')
|
206
212
|
except:
|
207
213
|
raise SeatException("Update failed")
|
208
214
|
return True
|
209
215
|
else:
|
210
|
-
_LOGGER.info(f'Vehicle with VIN {self.vin} is deactivated.')
|
216
|
+
_LOGGER.info(f'Vehicle with VIN {self._connection.anonymise(self.vin)} is deactivated.')
|
211
217
|
return False
|
212
218
|
return True
|
213
219
|
|
@@ -245,7 +251,7 @@ class Vehicle:
|
|
245
251
|
async def get_climater(self):
|
246
252
|
"""Fetch climater data if function is enabled."""
|
247
253
|
if self._relevantCapabilties.get('climatisation', {}).get('active', False):
|
248
|
-
data = await self._connection.getClimater(self.vin, self._apibase)
|
254
|
+
data = await self._connection.getClimater(self.vin, self._apibase, deepcopy(self.attrs.get('climater',{})))
|
249
255
|
if data:
|
250
256
|
self._states.update(data)
|
251
257
|
self._last_get_climater = datetime.now(tz=None)
|
@@ -285,6 +291,14 @@ class Vehicle:
|
|
285
291
|
if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
|
286
292
|
data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
|
287
293
|
if data:
|
294
|
+
#warningsList = data.get('warninglights',{}).get('statuses',[])
|
295
|
+
#for i in range(len(warningsList)):
|
296
|
+
# _LOGGER.debug(f'Element {i} in warninglights: {warningsList[i]}')
|
297
|
+
# if isinstance(warningsList[i], dict):
|
298
|
+
# if warningsList[i].get('icon',''):
|
299
|
+
# #Value of icon is very long and can lead to problems
|
300
|
+
# _LOGGER.debug(f'Substituting value of icon by \'DELETED\'')
|
301
|
+
# data['warninglights']['statuses'][i]['icon']='DELETED'
|
288
302
|
self._states.update(data)
|
289
303
|
else:
|
290
304
|
_LOGGER.debug('Could not fetch vehicle health warnings')
|
@@ -311,7 +325,7 @@ class Vehicle:
|
|
311
325
|
async def get_charger(self):
|
312
326
|
"""Fetch charger data if function is enabled."""
|
313
327
|
if self._relevantCapabilties.get('charging', {}).get('active', False):
|
314
|
-
data = await self._connection.getCharger(self.vin, self._apibase)
|
328
|
+
data = await self._connection.getCharger(self.vin, self._apibase, deepcopy(self.attrs.get('charging',{})))
|
315
329
|
if data:
|
316
330
|
self._states.update(data)
|
317
331
|
self._last_get_charger = datetime.now(tz=None)
|
@@ -396,6 +410,33 @@ class Vehicle:
|
|
396
410
|
_LOGGER.error('No charger support.')
|
397
411
|
raise SeatInvalidRequestException('No charger support.')
|
398
412
|
|
413
|
+
async def set_charger_target_soc(self, value):
|
414
|
+
"""Set target state of charge"""
|
415
|
+
if self.is_charging_supported:
|
416
|
+
if isinstance(value, int):
|
417
|
+
if 1 <= int(value) <= 100:
|
418
|
+
# VW-Group API charger current request
|
419
|
+
if self._relevantCapabilties.get('charging', {}).get('active', False) and self._relevantCapabilties.get('charging', {}).get('supportsTargetStateOfCharge', False):
|
420
|
+
data= deepcopy(self.attrs.get('charging',{}).get('info',{}).get('settings',{}))
|
421
|
+
if data=={}:
|
422
|
+
_LOGGER.error(f'Can not set target soc, because currently no charging settings are present.')
|
423
|
+
raise SeatInvalidRequestException(f'Set target soc not possible. Charging settings not present.')
|
424
|
+
data['targetSoc'] = int(value)
|
425
|
+
else:
|
426
|
+
_LOGGER.warning(f'Can not set target soc, because vehicle does not support this feature.')
|
427
|
+
return False
|
428
|
+
else:
|
429
|
+
_LOGGER.error(f'Set target soc to {value} is not supported.')
|
430
|
+
raise SeatInvalidRequestException(f'Set target soc to {value} is not supported.')
|
431
|
+
# Mimick app and set charger max ampere to Maximum/Reduced
|
432
|
+
else:
|
433
|
+
_LOGGER.error(f'Data type passed is invalid.')
|
434
|
+
raise SeatInvalidRequestException(f'Invalid data type.')
|
435
|
+
return await self.set_charger('settings', data)
|
436
|
+
else:
|
437
|
+
_LOGGER.error('No charger support.')
|
438
|
+
raise SeatInvalidRequestException('No charger support.')
|
439
|
+
|
399
440
|
async def set_charger(self, action, data=None):
|
400
441
|
"""Charging actions."""
|
401
442
|
if not self._relevantCapabilties.get('charging', {}).get('active', False):
|
@@ -450,9 +491,11 @@ class Vehicle:
|
|
450
491
|
if not self.charging:
|
451
492
|
actionSuccessful = True
|
452
493
|
elif mode == 'settings':
|
494
|
+
if data.get('targetSoc',0) == self.target_soc: # In case targetSoc is changed
|
495
|
+
actionSuccessful = True
|
453
496
|
if data.get('maxChargeCurrentAc','') == self.charge_max_ampere: # In case 'maximum', 'reduced'
|
454
497
|
actionSuccessful = True
|
455
|
-
if data.get('maxChargeCurrentAcInAmperes',0) == self.charge_max_ampere: # In case of a numerical value
|
498
|
+
if data.get('maxChargeCurrentAcInAmperes',0) == self.charge_max_ampere: # In case of a numerical value for charge current
|
456
499
|
actionSuccessful = True
|
457
500
|
else:
|
458
501
|
_LOGGER.error(f'Missing code in vehicle._set_charger() for mode {mode}')
|
@@ -1360,6 +1403,17 @@ class Vehicle:
|
|
1360
1403
|
# if car.get('deactivated', False):
|
1361
1404
|
# return True
|
1362
1405
|
|
1406
|
+
@property
|
1407
|
+
def brand(self):
|
1408
|
+
"""Return brand"""
|
1409
|
+
return self._specification.get('factoryModel', False).get('vehicleBrand', 'Unknown')
|
1410
|
+
|
1411
|
+
@property
|
1412
|
+
def is_brand_supported(self):
|
1413
|
+
"""Return true if brand is supported."""
|
1414
|
+
if self._specification.get('factoryModel', False).get('vehicleBrand', False):
|
1415
|
+
return True
|
1416
|
+
|
1363
1417
|
@property
|
1364
1418
|
def model(self):
|
1365
1419
|
"""Return model"""
|
@@ -2183,7 +2237,12 @@ class Vehicle:
|
|
2183
2237
|
def warnings(self):
|
2184
2238
|
"""Return warnings."""
|
2185
2239
|
if self.attrs.get('warninglights', {}).get('statuses',[]):
|
2186
|
-
|
2240
|
+
warningTextList = []
|
2241
|
+
for elem in self.attrs['warninglights']['statuses']:
|
2242
|
+
if isinstance(elem, dict):
|
2243
|
+
if elem.get('text',''):
|
2244
|
+
warningTextList.append(elem.get('text',''))
|
2245
|
+
return warningTextList
|
2187
2246
|
return 'No warnings'
|
2188
2247
|
|
2189
2248
|
@property
|
@@ -3086,6 +3145,27 @@ class Vehicle:
|
|
3086
3145
|
)
|
3087
3146
|
|
3088
3147
|
|
3148
|
+
async def stopFirebase(self):
|
3149
|
+
# Check if firebase is activated
|
3150
|
+
if self.firebaseStatus!= FIREBASE_STATUS_ACTIVATED:
|
3151
|
+
_LOGGER.info(f'No need to stop firebase. Firebase status={self.firebaseStatus}')
|
3152
|
+
return self.firebaseStatus
|
3153
|
+
|
3154
|
+
if self.firebase == None:
|
3155
|
+
_LOGGER.error(f'Internal error: Firebase status={self.firebaseStatus} but firebase variable not set. Setting firebase status back to not initialised.')
|
3156
|
+
self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
|
3157
|
+
return self.firebaseStatus
|
3158
|
+
|
3159
|
+
success = await self.firebase.firebaseStop()
|
3160
|
+
if not success:
|
3161
|
+
_LOGGER.warning('Stopping of firebase messaging failed.')
|
3162
|
+
return self.firebaseStatus
|
3163
|
+
|
3164
|
+
#await asyncio.sleep(5) # Wait to ignore the first notifications
|
3165
|
+
self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
|
3166
|
+
_LOGGER.info('Stopping of firebase messaging was successful.')
|
3167
|
+
return self.firebaseStatus
|
3168
|
+
|
3089
3169
|
async def initialiseFirebase(self, firebaseCredentialsFileName=None, updateCallback=None):
|
3090
3170
|
# Check if firebase shall be used
|
3091
3171
|
if firebaseCredentialsFileName == None:
|
@@ -3107,12 +3187,13 @@ class Vehicle:
|
|
3107
3187
|
subscribedBrand = credentials.get('subscription',{}).get('brand','')
|
3108
3188
|
if subscribedVin != '' and subscribedUserId != '':
|
3109
3189
|
if subscribedVin != self.vin or subscribedUserId != self._connection._user_id or subscribedBrand != self._connection._session_auth_brand:
|
3110
|
-
_LOGGER.debug(f'Change of vin, userId or brand. Deleting subscription for vin={subscribedVin} and userId={subscribedUserId}.')
|
3190
|
+
_LOGGER.debug(self._connection.anonymise(f'Change of vin, userId or brand. Deleting subscription for vin={subscribedVin} and userId={subscribedUserId}.'))
|
3111
3191
|
result = await self._connection.deleteSubscription(credentials)
|
3112
3192
|
|
3113
3193
|
# Start firebase
|
3114
|
-
|
3115
|
-
|
3194
|
+
if self.firebase == None:
|
3195
|
+
self.firebase = Firebase()
|
3196
|
+
success = await self.firebase.firebaseStart(self.onNotification, firebaseCredentialsFileName, brand=self._connection._session_auth_brand)
|
3116
3197
|
if not success:
|
3117
3198
|
self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_FAILED
|
3118
3199
|
_LOGGER.warning('Activation of firebase messaging failed.')
|
@@ -3133,7 +3214,7 @@ class Vehicle:
|
|
3133
3214
|
loop = asyncio.get_running_loop()
|
3134
3215
|
await loop.run_in_executor(None, writeFCMCredsFile, credentials, firebaseCredentialsFileName)
|
3135
3216
|
|
3136
|
-
await asyncio.sleep(5) # Wait to
|
3217
|
+
await asyncio.sleep(5) # Wait to ignore the first notifications
|
3137
3218
|
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
3138
3219
|
_LOGGER.info('Activation of firebase messaging was successful.')
|
3139
3220
|
return self.firebaseStatus
|
@@ -3150,7 +3231,7 @@ class Vehicle:
|
|
3150
3231
|
self.storeFirebaseNotifications(obj, notification, data_message)
|
3151
3232
|
|
3152
3233
|
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
|
3153
|
-
_LOGGER.info(f'While firebase is not fully activated, received notifications are just
|
3234
|
+
_LOGGER.info(f'While firebase is not fully activated, received notifications are just acknowledged.')
|
3154
3235
|
# As long as the firebase status is not set to activated, ignore the notifications
|
3155
3236
|
return False
|
3156
3237
|
|
@@ -3200,7 +3281,7 @@ class Vehicle:
|
|
3200
3281
|
await self.get_departure_profiles()
|
3201
3282
|
if self.updateCallback:
|
3202
3283
|
await self.updateCallback(2)
|
3203
|
-
elif type in ('charging-status-changed', 'charging-started', 'charging-stopped'):
|
3284
|
+
elif type in ('charging-status-changed', 'charging-started', 'charging-stopped', 'charging-settings-updated'):
|
3204
3285
|
if self._requests.get('batterycharge', {}).get('id', None):
|
3205
3286
|
openRequest= self._requests.get('batterycharge', {}).get('id', None)
|
3206
3287
|
if openRequest == requestId:
|
@@ -3213,7 +3294,7 @@ class Vehicle:
|
|
3213
3294
|
await self.updateCallback(2)
|
3214
3295
|
else:
|
3215
3296
|
_LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_charger was at {self._last_get_charger}. So no need to update.')
|
3216
|
-
elif type in ('climatisation-status-changed','climatisation-started', 'climatisation-stopped'):
|
3297
|
+
elif type in ('climatisation-status-changed','climatisation-started', 'climatisation-stopped', 'climatisation-settings-updated'):
|
3217
3298
|
if self._requests.get('climatisation', {}).get('id', None):
|
3218
3299
|
openRequest= self._requests.get('climatisation', {}).get('id', None)
|
3219
3300
|
if openRequest == requestId:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pycupra
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.6
|
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
|
@@ -1,12 +1,12 @@
|
|
1
1
|
pycupra/__init__.py,sha256=p0880jPkLqOErX3u3qaLboBLOsEHFpe44axApdaGeqI,231
|
2
|
-
pycupra/__version__.py,sha256=
|
3
|
-
pycupra/connection.py,sha256=
|
2
|
+
pycupra/__version__.py,sha256=orABFX8h4AFdsffPt4uVXqXeC0Wz3ZJOeNAzyeNw7O8,207
|
3
|
+
pycupra/connection.py,sha256=cZIqsjEwA1rTkhVyjowrTdbWnviOXFSso-WR8FnSrmg,90610
|
4
4
|
pycupra/const.py,sha256=cJJ9xrof6HZ7ZE7nXnB6tU4qMbSadlN8mgs43Igy7Mo,10591
|
5
|
-
pycupra/dashboard.py,sha256=
|
5
|
+
pycupra/dashboard.py,sha256=RlJPdTvJV7Urog5gCz_4pYWNd5_ApQIVJUCwJIi_7L8,43822
|
6
6
|
pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
|
7
|
-
pycupra/firebase.py,sha256=
|
8
|
-
pycupra/utilities.py,sha256=
|
9
|
-
pycupra/vehicle.py,sha256=
|
7
|
+
pycupra/firebase.py,sha256=tuN_W3OX3h3-yfdprWzmCn6z_T-BMx-OpL7Z6hOA8Lc,3451
|
8
|
+
pycupra/utilities.py,sha256=6sDxWP13-XtxmqhuBJBGdVbkj48BQ9AxFMrBPxH0J7g,2679
|
9
|
+
pycupra/vehicle.py,sha256=stxeyn2vd2j2yQ3dWEmT9XX1aZNZ7FdYvxbIraF6M0o,157175
|
10
10
|
pycupra/firebase_messaging/__init__.py,sha256=oerLHWvEf4qRqu3GxSX6SLY_OYI430ydAiAhKtzyMEM,666
|
11
11
|
pycupra/firebase_messaging/android_checkin_pb2.py,sha256=-U1oGroFt3KRuGDieae3iTcux6mAfx1TFkE1Q35ul2E,2849
|
12
12
|
pycupra/firebase_messaging/android_checkin_pb2.pyi,sha256=7KL-zQIz2Zz7uftcLkv57Podzu-yk6trn50FN4X4A8E,9379
|
@@ -18,8 +18,8 @@ pycupra/firebase_messaging/fcmregister.py,sha256=yZngC-0ZfTygtjfdzg03OW_3xk2n_uS
|
|
18
18
|
pycupra/firebase_messaging/mcs_pb2.py,sha256=nwXY7IDgLYPxgpSGs6wyTSyYDdomQsyGqH8R8EgODLg,7733
|
19
19
|
pycupra/firebase_messaging/mcs_pb2.pyi,sha256=HfIhInC3wRg8_caKwUm-V3knE2jTdEQvBy6uXgQ5rHY,33959
|
20
20
|
pycupra/firebase_messaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
pycupra-0.1.
|
22
|
-
pycupra-0.1.
|
23
|
-
pycupra-0.1.
|
24
|
-
pycupra-0.1.
|
25
|
-
pycupra-0.1.
|
21
|
+
pycupra-0.1.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
22
|
+
pycupra-0.1.6.dist-info/METADATA,sha256=u9ZdIDY607S5-5APn3VwI1nJgvWHPngzptNHedmY_k4,3757
|
23
|
+
pycupra-0.1.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
24
|
+
pycupra-0.1.6.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
|
25
|
+
pycupra-0.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|