pycupra 0.1.7__py3-none-any.whl → 0.1.8__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 +17 -2
- pycupra/const.py +1 -1
- pycupra/dashboard.py +27 -0
- pycupra/firebase_messaging/fcmpushclient.py +7 -2
- pycupra/vehicle.py +162 -74
- {pycupra-0.1.7.dist-info → pycupra-0.1.8.dist-info}/METADATA +1 -1
- {pycupra-0.1.7.dist-info → pycupra-0.1.8.dist-info}/RECORD +11 -11
- {pycupra-0.1.7.dist-info → pycupra-0.1.8.dist-info}/WHEEL +0 -0
- {pycupra-0.1.7.dist-info → pycupra-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.1.7.dist-info → pycupra-0.1.8.dist-info}/top_level.txt +0 -0
pycupra/__version__.py
CHANGED
pycupra/connection.py
CHANGED
@@ -109,13 +109,14 @@ TIMEOUT = timedelta(seconds=90)
|
|
109
109
|
class Connection:
|
110
110
|
""" Connection to Connect services """
|
111
111
|
# Init connection class
|
112
|
-
def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, **optional):
|
112
|
+
def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, tripStatisticsStartYear=None, **optional):
|
113
113
|
""" Initialize """
|
114
114
|
self._session = session
|
115
115
|
self._lock = asyncio.Lock()
|
116
116
|
self._session_fulldebug = fulldebug
|
117
117
|
self._session_nightlyUpdateReduction = nightlyUpdateReduction
|
118
118
|
self._session_anonymise = anonymise
|
119
|
+
self._session_tripStatisticsStartYear = tripStatisticsStartYear
|
119
120
|
self._session_headers = HEADERS_SESSION.get(brand).copy()
|
120
121
|
self._session_base = BASE_SESSION
|
121
122
|
self._session_auth_headers = HEADERS_AUTH.copy()
|
@@ -636,7 +637,12 @@ class Connection:
|
|
636
637
|
async def _request(self, method, url, **kwargs):
|
637
638
|
"""Perform a HTTP query"""
|
638
639
|
if self._session_fulldebug:
|
639
|
-
|
640
|
+
argsString =''
|
641
|
+
if len(kwargs)>0:
|
642
|
+
argsString = 'with '
|
643
|
+
for k, val in kwargs.items():
|
644
|
+
argsString = argsString + f"{k}=\'{val}\' "
|
645
|
+
_LOGGER.debug(self.anonymise(f'HTTP {method} "{url}" {argsString}'))
|
640
646
|
try:
|
641
647
|
if datetime.now(tz=None).date() != self._sessionRequestTimestamp.date():
|
642
648
|
# A new day has begun. Store _sessionRequestCounter in history and reset timestamp and counter
|
@@ -701,6 +707,9 @@ class Connection:
|
|
701
707
|
if self._session_fulldebug:
|
702
708
|
if 'image/png' in response.headers.get('Content-Type', ''):
|
703
709
|
_LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}]. Not showing response for Content-Type image/png.'))
|
710
|
+
elif method==METH_PUT or method==METH_DELETE:
|
711
|
+
# deepcopy() of res can produce errors, if res is the API response on PUT or DELETE
|
712
|
+
_LOGGER.debug(f'Request for "{self.anonymise(url)}" returned with status code [{response.status}]. Not showing response for http {method}')
|
704
713
|
else:
|
705
714
|
_LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}], response: {self.anonymise(deepcopy(res))}'))
|
706
715
|
else:
|
@@ -1080,6 +1089,12 @@ class Connection:
|
|
1080
1089
|
async def getTripStatistics(self, vin, baseurl, supportsCyclicTrips):
|
1081
1090
|
"""Get short term and cyclic trip statistics."""
|
1082
1091
|
await self.set_token(self._session_auth_brand)
|
1092
|
+
if self._session_tripStatisticsStartYear==None:
|
1093
|
+
# If connection was not initialised with parameter tripStatisticsStartYear, then the value of the last year is used
|
1094
|
+
# (This keeps the statistics shorter in Home Assistant)
|
1095
|
+
startYear = datetime.now().year - 1
|
1096
|
+
else:
|
1097
|
+
startYear = self._session_tripStatisticsStartYear
|
1083
1098
|
try:
|
1084
1099
|
data={'tripstatistics': {}}
|
1085
1100
|
if supportsCyclicTrips:
|
pycupra/const.py
CHANGED
@@ -138,7 +138,7 @@ API_DEPARTURE_TIMERS = '{baseurl}/v1/vehicles/{vin}/departure-timers'
|
|
138
138
|
API_DEPARTURE_PROFILES = '{baseurl}/v1/vehicles/{vin}/departure/profiles' # Departure profiles
|
139
139
|
API_POSITION = '{baseurl}/v1/vehicles/{vin}/parkingposition' # Position data
|
140
140
|
API_POS_TO_ADDRESS= 'https://maps.googleapis.com/maps/api/directions/json?origin={lat},{lon}&destination={lat},{lon}&traffic_model=best_guess&departure_time=now&language=de&key={apiKeyForGoogle}&mode=driving'
|
141
|
-
API_TRIP = '{baseurl}/v1/vehicles/{vin}/driving-data/{dataType}?from=
|
141
|
+
API_TRIP = '{baseurl}/v1/vehicles/{vin}/driving-data/{dataType}?from={startYear}-07-01T00:00:00Z&to=2099-12-31T09:59:01Z' # Trip statistics (whole history) SHORT/LONG/CYCLIC (WEEK only with from)
|
142
142
|
API_MILEAGE = '{baseurl}/v1/vehicles/{vin}/mileage' # Total km etc
|
143
143
|
API_MAINTENANCE = '{baseurl}/v1/vehicles/{vin}/maintenance' # Inspection information
|
144
144
|
API_MEASUREMENTS = '{baseurl}/v1/vehicles/{vin}/measurements/engines' # ???
|
pycupra/dashboard.py
CHANGED
@@ -1002,6 +1002,32 @@ class ChargingState(BinarySensor):
|
|
1002
1002
|
attr['mode']=mode
|
1003
1003
|
return attr
|
1004
1004
|
|
1005
|
+
class AreaAlarm(BinarySensor):
|
1006
|
+
def __init__(self):
|
1007
|
+
super().__init__(attr="area_alarm", name="Area alarm", icon="mdi:alarm-light", device_class='safety')
|
1008
|
+
|
1009
|
+
@property
|
1010
|
+
def state(self):
|
1011
|
+
return self.vehicle.area_alarm
|
1012
|
+
|
1013
|
+
@property
|
1014
|
+
def assumed_state(self):
|
1015
|
+
return False
|
1016
|
+
|
1017
|
+
@property
|
1018
|
+
def attributes(self):
|
1019
|
+
attr = {}
|
1020
|
+
type = self.vehicle.attrs.get('areaAlarm', {}).get('type', '')
|
1021
|
+
zones = self.vehicle.attrs.get('areaAlarm', {}).get('zones', [])
|
1022
|
+
timestamp = self.vehicle.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
1023
|
+
if type != '':
|
1024
|
+
attr['type']=type
|
1025
|
+
if len(zones) > 0:
|
1026
|
+
attr['zone']=zones[0]
|
1027
|
+
if timestamp != 0:
|
1028
|
+
attr['timestamp']=timestamp
|
1029
|
+
return attr
|
1030
|
+
|
1005
1031
|
def create_instruments():
|
1006
1032
|
return [
|
1007
1033
|
Position(),
|
@@ -1030,6 +1056,7 @@ def create_instruments():
|
|
1030
1056
|
DepartureProfile2(),
|
1031
1057
|
DepartureProfile3(),
|
1032
1058
|
ChargingState(),
|
1059
|
+
AreaAlarm(),
|
1033
1060
|
Sensor(
|
1034
1061
|
attr="distance",
|
1035
1062
|
name="Odometer",
|
@@ -92,10 +92,10 @@ class FcmPushClientConfig: # pylint:disable=too-many-instance-attributes
|
|
92
92
|
"""Class to provide configuration to
|
93
93
|
:class:`firebase_messaging.FcmPushClientConfig`.FcmPushClient."""
|
94
94
|
|
95
|
-
server_heartbeat_interval: int | None =
|
95
|
+
server_heartbeat_interval: int | None = 30 # original value was 10
|
96
96
|
"""Time in seconds to request the server to send heartbeats"""
|
97
97
|
|
98
|
-
client_heartbeat_interval: int | None =
|
98
|
+
client_heartbeat_interval: int | None = 40 # original value was 20
|
99
99
|
"""Time in seconds to send heartbeats to the server"""
|
100
100
|
|
101
101
|
send_selective_acknowledgements: bool = True
|
@@ -218,6 +218,7 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
218
218
|
or (self.stopping_lock and self.stopping_lock.locked())
|
219
219
|
or not self.do_listen
|
220
220
|
):
|
221
|
+
_logger.debug(f"In _reset. reset_lock={self.reset_lock}, reset_lock.locked={self.reset_lock.locked()}, stopping_lock={self.stopping_lock}, stopping_lock.locked={self.stopping_lock.locked()}, do_listen={self.do_listen}")
|
221
222
|
return
|
222
223
|
|
223
224
|
async with self.reset_lock: # type: ignore[union-attr]
|
@@ -725,7 +726,10 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
725
726
|
else:
|
726
727
|
_logger.exception("Unexpected exception during read\n")
|
727
728
|
if self._try_increment_error_count(ErrorType.CONNECTION):
|
729
|
+
_logger.debug("Calling reset()\n")
|
728
730
|
await self._reset()
|
731
|
+
else:
|
732
|
+
_logger.debug("Not calling reset()\n")
|
729
733
|
except Exception as ex:
|
730
734
|
_logger.error(
|
731
735
|
"Unknown error: %s, shutting down FcmPushClient.\n%s",
|
@@ -805,3 +809,4 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
805
809
|
dms.persistent_id = persistent_id
|
806
810
|
|
807
811
|
# Not supported yet
|
812
|
+
|
pycupra/vehicle.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
import re
|
5
5
|
import logging
|
6
6
|
import asyncio
|
7
|
+
import json
|
7
8
|
|
8
9
|
from copy import deepcopy
|
9
10
|
from datetime import datetime, timedelta, timezone
|
@@ -72,7 +73,6 @@ class Vehicle:
|
|
72
73
|
self._relevantCapabilties = {
|
73
74
|
'measurements': {'active': False, 'reason': 'not supported', },
|
74
75
|
'climatisation': {'active': False, 'reason': 'not supported'},
|
75
|
-
#'parkingInformation': {'active': False, 'reason': 'not supported'},
|
76
76
|
'tripStatistics': {'active': False, 'reason': 'not supported', 'supportsCyclicTrips': False},
|
77
77
|
'vehicleHealthInspection': {'active': False, 'reason': 'not supported'},
|
78
78
|
'vehicleHealthWarnings': {'active': False, 'reason': 'not supported'},
|
@@ -95,6 +95,7 @@ class Vehicle:
|
|
95
95
|
self._last_get_charger = datetime.now(tz=None) - timedelta(seconds=600)
|
96
96
|
self._last_get_climater = datetime.now(tz=None) - timedelta(seconds=600)
|
97
97
|
self._last_get_mileage = datetime.now(tz=None) - timedelta(seconds=600)
|
98
|
+
self._last_get_position = datetime.now(tz=None) - timedelta(seconds=600)
|
98
99
|
|
99
100
|
|
100
101
|
#### API get and set functions ####
|
@@ -131,8 +132,7 @@ class Vehicle:
|
|
131
132
|
self._relevantCapabilties[id].update(data)
|
132
133
|
|
133
134
|
|
134
|
-
|
135
|
-
|
135
|
+
|
136
136
|
# Get URLs for model image
|
137
137
|
self._modelimages = await self.get_modelimageurl()
|
138
138
|
|
@@ -148,11 +148,16 @@ class Vehicle:
|
|
148
148
|
hourago = datetime.now() - timedelta(hours = 2)
|
149
149
|
if self._discovered < hourago:
|
150
150
|
await self.discover()
|
151
|
-
#_LOGGER.debug('Achtung! self.discover() auskommentiert')
|
152
151
|
|
153
152
|
# Fetch all data if car is not deactivated
|
154
153
|
if not self.deactivated:
|
155
154
|
try:
|
155
|
+
if self.attrs.get('areaAlarm', {}) !={}:
|
156
|
+
# Delete an area alarm if it is older than 900 seconds
|
157
|
+
alarmTimestamp = self.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
158
|
+
if alarmTimestamp < datetime.now(tz=None) - timedelta(seconds= 900):
|
159
|
+
self.attrs.pop("areaAlarm")
|
160
|
+
|
156
161
|
if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
|
157
162
|
# Check, if fcmpushclient still started
|
158
163
|
if not self.firebase._pushClient.is_started():
|
@@ -170,6 +175,26 @@ class Vehicle:
|
|
170
175
|
else:
|
171
176
|
fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1100)
|
172
177
|
|
178
|
+
if self.firebaseStatus == FIREBASE_STATUS_ACTIVATION_STOPPED:
|
179
|
+
# Trying to activate firebase connection again
|
180
|
+
"""_LOGGER.debug(f'As firebase status={self.firebaseStatus}, fcmpushclient.start() is called.')
|
181
|
+
await self.firebase._pushClient.start()
|
182
|
+
#await asyncio.sleep(5)
|
183
|
+
if self.firebase._pushClient.is_started():
|
184
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
185
|
+
_LOGGER.debug(f'Successfully restarted push client. New firebase status={self.firebaseStatus}')
|
186
|
+
else:
|
187
|
+
_LOGGER.warning(f'Restart of push client failed. Firebase status={self.firebaseStatus}')"""
|
188
|
+
newStatus = await self.stopFirebase()
|
189
|
+
if newStatus != FIREBASE_STATUS_NOT_INITIALISED:
|
190
|
+
_LOGGER.debug(f'stopFirebase() not successful.')
|
191
|
+
newStatus = await self.initialiseFirebase(self._firebaseCredentialsFileName, self.updateCallback)
|
192
|
+
if newStatus == FIREBASE_STATUS_ACTIVATED:
|
193
|
+
_LOGGER.debug(f'Reinitialisation of firebase successful.New firebase status={self.firebaseStatus}.')
|
194
|
+
else:
|
195
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_STOPPED
|
196
|
+
_LOGGER.warning(f'Reinitialisation of firebase failed. New firebase status={self.firebaseStatus}.')
|
197
|
+
|
173
198
|
if self._connection._session_nightlyUpdateReduction:
|
174
199
|
# nightlyUpdateReduction is activated
|
175
200
|
if datetime.now(tz=None).hour<5 or datetime.now(tz=None).hour>=22:
|
@@ -198,16 +223,6 @@ class Vehicle:
|
|
198
223
|
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
|
199
224
|
await self.get_mileage()
|
200
225
|
|
201
|
-
if self.firebaseStatus == FIREBASE_STATUS_ACTIVATION_STOPPED:
|
202
|
-
# Trying to activate firebase connection again
|
203
|
-
await self.firebase._pushClient.start()
|
204
|
-
#await asyncio.sleep(5)
|
205
|
-
if self.firebase._pushClient.is_started():
|
206
|
-
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
207
|
-
_LOGGER.debug(f'Successfully restarted push client. New firebase status ={self.firebaseStatus}')
|
208
|
-
else:
|
209
|
-
_LOGGER.debug(f'Restart of push client failed. Firebase status ={self.firebaseStatus}')
|
210
|
-
|
211
226
|
|
212
227
|
await asyncio.gather(
|
213
228
|
#self.get_statusreport(),
|
@@ -244,6 +259,10 @@ class Vehicle:
|
|
244
259
|
data = await self._connection.getBasicCarData(self.vin, self._apibase)
|
245
260
|
if data:
|
246
261
|
self._states.update(data)
|
262
|
+
return True
|
263
|
+
else:
|
264
|
+
_LOGGER.debug('Could not fetch basic car data')
|
265
|
+
return False
|
247
266
|
|
248
267
|
async def get_mileage(self):
|
249
268
|
"""Fetch basic car data."""
|
@@ -251,6 +270,10 @@ class Vehicle:
|
|
251
270
|
if data:
|
252
271
|
self._states.update(data)
|
253
272
|
self._last_get_mileage = datetime.now(tz=None)
|
273
|
+
return True
|
274
|
+
else:
|
275
|
+
_LOGGER.debug('Could not fetch mileage data')
|
276
|
+
return False
|
254
277
|
|
255
278
|
async def get_preheater(self):
|
256
279
|
"""Fetch pre-heater data if function is enabled."""
|
@@ -272,8 +295,10 @@ class Vehicle:
|
|
272
295
|
if data:
|
273
296
|
self._states.update(data)
|
274
297
|
self._last_get_climater = datetime.now(tz=None)
|
298
|
+
return True
|
275
299
|
else:
|
276
300
|
_LOGGER.debug('Could not fetch climater data')
|
301
|
+
return False
|
277
302
|
#else:
|
278
303
|
# self._requests.pop('climatisation', None)
|
279
304
|
|
@@ -283,8 +308,10 @@ class Vehicle:
|
|
283
308
|
data = await self._connection.getTripStatistics(self.vin, self._apibase, self._relevantCapabilties['tripStatistics'].get('supportsCyclicTrips', False))
|
284
309
|
if data:
|
285
310
|
self._states.update(data)
|
311
|
+
return True
|
286
312
|
else:
|
287
313
|
_LOGGER.debug('Could not fetch trip statistics')
|
314
|
+
return False
|
288
315
|
|
289
316
|
async def get_position(self):
|
290
317
|
"""Fetch position data if function is enabled."""
|
@@ -301,24 +328,21 @@ class Vehicle:
|
|
301
328
|
except:
|
302
329
|
pass
|
303
330
|
self._states.update(data)
|
331
|
+
self._last_get_position = datetime.now(tz=None)
|
332
|
+
return True
|
304
333
|
else:
|
305
334
|
_LOGGER.debug('Could not fetch any positional data')
|
335
|
+
return False
|
306
336
|
|
307
337
|
async def get_vehicleHealthWarnings(self):
|
308
338
|
if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
|
309
339
|
data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
|
310
340
|
if data:
|
311
|
-
#warningsList = data.get('warninglights',{}).get('statuses',[])
|
312
|
-
#for i in range(len(warningsList)):
|
313
|
-
# _LOGGER.debug(f'Element {i} in warninglights: {warningsList[i]}')
|
314
|
-
# if isinstance(warningsList[i], dict):
|
315
|
-
# if warningsList[i].get('icon',''):
|
316
|
-
# #Value of icon is very long and can lead to problems
|
317
|
-
# _LOGGER.debug(f'Substituting value of icon by \'DELETED\'')
|
318
|
-
# data['warninglights']['statuses'][i]['icon']='DELETED'
|
319
341
|
self._states.update(data)
|
342
|
+
return True
|
320
343
|
else:
|
321
344
|
_LOGGER.debug('Could not fetch vehicle health warnings')
|
345
|
+
return False
|
322
346
|
|
323
347
|
async def get_statusreport(self):
|
324
348
|
"""Fetch status data if function is enabled."""
|
@@ -327,8 +351,10 @@ class Vehicle:
|
|
327
351
|
if data:
|
328
352
|
self._states.update(data)
|
329
353
|
self._last_get_statusreport = datetime.now(tz=None)
|
354
|
+
return True
|
330
355
|
else:
|
331
356
|
_LOGGER.debug('Could not fetch status report')
|
357
|
+
return False
|
332
358
|
|
333
359
|
async def get_maintenance(self):
|
334
360
|
"""Fetch maintenance data if function is enabled."""
|
@@ -336,8 +362,10 @@ class Vehicle:
|
|
336
362
|
data = await self._connection.getMaintenance(self.vin, self._apibase)
|
337
363
|
if data:
|
338
364
|
self._states.update(data)
|
365
|
+
return True
|
339
366
|
else:
|
340
367
|
_LOGGER.debug('Could not fetch status report')
|
368
|
+
return False
|
341
369
|
|
342
370
|
async def get_charger(self):
|
343
371
|
"""Fetch charger data if function is enabled."""
|
@@ -346,8 +374,10 @@ class Vehicle:
|
|
346
374
|
if data:
|
347
375
|
self._states.update(data)
|
348
376
|
self._last_get_charger = datetime.now(tz=None)
|
377
|
+
return True
|
349
378
|
else:
|
350
379
|
_LOGGER.debug('Could not fetch charger data')
|
380
|
+
return False
|
351
381
|
|
352
382
|
async def get_departure_timers(self):
|
353
383
|
"""Fetch timer data if function is enabled."""
|
@@ -356,8 +386,10 @@ class Vehicle:
|
|
356
386
|
if data:
|
357
387
|
self._states.update(data)
|
358
388
|
self._last_get_departure_timers = datetime.now(tz=None)
|
389
|
+
return True
|
359
390
|
else:
|
360
391
|
_LOGGER.debug('Could not fetch timers')
|
392
|
+
return False
|
361
393
|
|
362
394
|
async def get_departure_profiles(self):
|
363
395
|
"""Fetch timer data if function is enabled."""
|
@@ -366,8 +398,10 @@ class Vehicle:
|
|
366
398
|
if data:
|
367
399
|
self._states.update(data)
|
368
400
|
self._last_get_departure_profiles = datetime.now(tz=None)
|
401
|
+
return True
|
369
402
|
else:
|
370
403
|
_LOGGER.debug('Could not fetch timers')
|
404
|
+
return False
|
371
405
|
|
372
406
|
#async def wait_for_request(self, section, request, retryCount=36):
|
373
407
|
"""Update status of outstanding requests."""
|
@@ -1000,7 +1034,7 @@ class Vehicle:
|
|
1000
1034
|
async def set_climatisation_temp(self, temperature=20):
|
1001
1035
|
"""Set climatisation target temp."""
|
1002
1036
|
if self.is_electric_climatisation_supported or self.is_auxiliary_climatisation_supported:
|
1003
|
-
if 16 <=
|
1037
|
+
if 16 <= float(temperature) <= 30:
|
1004
1038
|
data = {
|
1005
1039
|
'climatisationWithoutExternalPower': self.climatisation_without_external_power,
|
1006
1040
|
'targetTemperature': temperature,
|
@@ -1051,28 +1085,30 @@ class Vehicle:
|
|
1051
1085
|
data = {}
|
1052
1086
|
# Validate user input
|
1053
1087
|
if mode.lower() not in ['electric', 'auxiliary', 'start', 'stop', 'on', 'off']:
|
1088
|
+
_LOGGER.error(f"Invalid mode for 'set_climatisation': {mode}")
|
1054
1089
|
raise SeatInvalidRequestException(f"Invalid mode for set_climatisation: {mode}")
|
1055
1090
|
elif mode == 'auxiliary' and spin is None:
|
1056
1091
|
raise SeatInvalidRequestException("Starting auxiliary heater requires provided S-PIN")
|
1057
1092
|
if temp is not None:
|
1058
|
-
if not isinstance(temp, float):
|
1093
|
+
if not isinstance(temp, float) and not isinstance(temp, int):
|
1094
|
+
_LOGGER.error(f"Invalid type for temp. type={type(temp)}")
|
1059
1095
|
raise SeatInvalidRequestException(f"Invalid type for temp")
|
1060
1096
|
elif not 16 <= float(temp) <=30:
|
1061
1097
|
raise SeatInvalidRequestException(f"Invalid value for temp")
|
1062
1098
|
else:
|
1063
1099
|
temp = self.climatisation_target_temperature
|
1064
|
-
if hvpower is not None:
|
1065
|
-
|
1066
|
-
|
1100
|
+
#if hvpower is not None:
|
1101
|
+
# if not isinstance(hvpower, bool):
|
1102
|
+
# raise SeatInvalidRequestException(f"Invalid type for hvpower")
|
1067
1103
|
if self.is_electric_climatisation_supported:
|
1068
1104
|
if self._relevantCapabilties.get('climatisation', {}).get('active', False):
|
1069
1105
|
if mode in ['Start', 'start', 'Electric', 'electric', 'On', 'on']:
|
1070
1106
|
mode = 'start'
|
1071
1107
|
if mode in ['start', 'auxiliary']:
|
1072
|
-
if hvpower is not None:
|
1073
|
-
|
1074
|
-
else:
|
1075
|
-
|
1108
|
+
#if hvpower is not None:
|
1109
|
+
# withoutHVPower = hvpower
|
1110
|
+
#else:
|
1111
|
+
# withoutHVPower = self.climatisation_without_external_power
|
1076
1112
|
data = {
|
1077
1113
|
'targetTemperature': temp,
|
1078
1114
|
'targetTemperatureUnit': 'celsius',
|
@@ -1159,7 +1195,7 @@ class Vehicle:
|
|
1159
1195
|
self._requests['climatisation'] = {'status': 'Exception'}
|
1160
1196
|
raise SeatException('Climatisation action failed')
|
1161
1197
|
|
1162
|
-
|
1198
|
+
# Parking heater heating/ventilation (RS)
|
1163
1199
|
async def set_pheater(self, mode, spin):
|
1164
1200
|
"""Set the mode for the parking heater."""
|
1165
1201
|
if not self.is_pheater_heating_supported:
|
@@ -1202,7 +1238,7 @@ class Vehicle:
|
|
1202
1238
|
self._requests['preheater'] = {'status': 'Exception'}
|
1203
1239
|
raise SeatException('Pre-heater action failed')
|
1204
1240
|
|
1205
|
-
|
1241
|
+
# Lock
|
1206
1242
|
async def set_lock(self, action, spin):
|
1207
1243
|
"""Remote lock and unlock actions."""
|
1208
1244
|
#if not self._services.get('rlu_v1', False):
|
@@ -1264,7 +1300,7 @@ class Vehicle:
|
|
1264
1300
|
self._requests['lock'] = {'status': 'Exception'}
|
1265
1301
|
raise SeatException('Lock action failed')
|
1266
1302
|
|
1267
|
-
|
1303
|
+
# Honk and flash (RHF)
|
1268
1304
|
async def set_honkandflash(self, action, lat=None, lng=None):
|
1269
1305
|
"""Turn on/off honk and flash."""
|
1270
1306
|
if not self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
|
@@ -1321,7 +1357,7 @@ class Vehicle:
|
|
1321
1357
|
self._requests['honkandflash'] = {'status': 'Exception'}
|
1322
1358
|
raise SeatException('Honk and flash action failed')
|
1323
1359
|
|
1324
|
-
|
1360
|
+
# Refresh vehicle data (VSR)
|
1325
1361
|
async def set_refresh(self):
|
1326
1362
|
"""Wake up vehicle and update status data."""
|
1327
1363
|
if not self._relevantCapabilties.get('state', {}).get('active', False):
|
@@ -1357,7 +1393,7 @@ class Vehicle:
|
|
1357
1393
|
raise SeatException('Data refresh failed')
|
1358
1394
|
|
1359
1395
|
#### Vehicle class helpers ####
|
1360
|
-
|
1396
|
+
# Vehicle info
|
1361
1397
|
@property
|
1362
1398
|
def attrs(self):
|
1363
1399
|
return self._states
|
@@ -1390,7 +1426,7 @@ class Vehicle:
|
|
1390
1426
|
|
1391
1427
|
|
1392
1428
|
#### Information from vehicle states ####
|
1393
|
-
|
1429
|
+
# Car information
|
1394
1430
|
@property
|
1395
1431
|
def nickname(self):
|
1396
1432
|
return self._properties.get('vehicleNickname', '')
|
@@ -1479,7 +1515,7 @@ class Vehicle:
|
|
1479
1515
|
if self._modelimages is not None:
|
1480
1516
|
return True
|
1481
1517
|
|
1482
|
-
|
1518
|
+
# Lights
|
1483
1519
|
@property
|
1484
1520
|
def parking_light(self):
|
1485
1521
|
"""Return true if parking light is on"""
|
@@ -1498,7 +1534,7 @@ class Vehicle:
|
|
1498
1534
|
else:
|
1499
1535
|
return False
|
1500
1536
|
|
1501
|
-
|
1537
|
+
# Connection status
|
1502
1538
|
@property
|
1503
1539
|
def last_connected(self):
|
1504
1540
|
"""Return when vehicle was last connected to connect servers."""
|
@@ -1515,7 +1551,7 @@ class Vehicle:
|
|
1515
1551
|
if 'updatedAt' in self.attrs.get('status', {}):
|
1516
1552
|
return True
|
1517
1553
|
|
1518
|
-
|
1554
|
+
# Update status
|
1519
1555
|
@property
|
1520
1556
|
def last_full_update(self):
|
1521
1557
|
"""Return when the last full update for the vehicle took place."""
|
@@ -1527,7 +1563,7 @@ class Vehicle:
|
|
1527
1563
|
if hasattr(self,'_last_full_update'):
|
1528
1564
|
return True
|
1529
1565
|
|
1530
|
-
|
1566
|
+
# Service information
|
1531
1567
|
@property
|
1532
1568
|
def distance(self):
|
1533
1569
|
"""Return vehicle odometer."""
|
@@ -1615,7 +1651,7 @@ class Vehicle:
|
|
1615
1651
|
return True
|
1616
1652
|
return False
|
1617
1653
|
|
1618
|
-
|
1654
|
+
# Charger related states for EV and PHEV
|
1619
1655
|
@property
|
1620
1656
|
def charging(self):
|
1621
1657
|
"""Return battery level"""
|
@@ -1874,7 +1910,7 @@ class Vehicle:
|
|
1874
1910
|
if self.attrs.get('charging', {}).get('info', {}).get('settings', {}).get('targetSoc', False):
|
1875
1911
|
return True
|
1876
1912
|
|
1877
|
-
|
1913
|
+
# Vehicle location states
|
1878
1914
|
@property
|
1879
1915
|
def position(self):
|
1880
1916
|
"""Return position."""
|
@@ -1941,7 +1977,7 @@ class Vehicle:
|
|
1941
1977
|
if 'parkingTimeUTC' in self.attrs.get('findCarResponse', {}):
|
1942
1978
|
return True
|
1943
1979
|
|
1944
|
-
|
1980
|
+
# Vehicle fuel level and range
|
1945
1981
|
@property
|
1946
1982
|
def primary_range(self):
|
1947
1983
|
value = -1
|
@@ -2080,7 +2116,7 @@ class Vehicle:
|
|
2080
2116
|
return self.is_secondary_range_supported
|
2081
2117
|
return False
|
2082
2118
|
|
2083
|
-
|
2119
|
+
# Climatisation settings
|
2084
2120
|
@property
|
2085
2121
|
def climatisation_target_temperature(self):
|
2086
2122
|
"""Return the target temperature from climater."""
|
@@ -2154,7 +2190,7 @@ class Vehicle:
|
|
2154
2190
|
else:
|
2155
2191
|
return False
|
2156
2192
|
|
2157
|
-
|
2193
|
+
# Climatisation, electric
|
2158
2194
|
@property
|
2159
2195
|
def electric_climatisation_attributes(self):
|
2160
2196
|
"""Return climatisation attributes."""
|
@@ -2262,7 +2298,7 @@ class Vehicle:
|
|
2262
2298
|
return True
|
2263
2299
|
return False
|
2264
2300
|
|
2265
|
-
|
2301
|
+
# Parking heater, "legacy" auxiliary climatisation
|
2266
2302
|
@property
|
2267
2303
|
def pheater_duration(self):
|
2268
2304
|
return self._climate_duration
|
@@ -2310,7 +2346,7 @@ class Vehicle:
|
|
2310
2346
|
if self.attrs.get('heating', {}).get('climatisationStateReport', {}).get('climatisationState', False):
|
2311
2347
|
return True
|
2312
2348
|
|
2313
|
-
|
2349
|
+
# Windows
|
2314
2350
|
@property
|
2315
2351
|
def windows_closed(self):
|
2316
2352
|
return (self.window_closed_left_front and self.window_closed_left_back and self.window_closed_right_front and self.window_closed_right_back)
|
@@ -2417,7 +2453,7 @@ class Vehicle:
|
|
2417
2453
|
# response = self.attrs.get('status')['windows'].get('sunRoof', '')
|
2418
2454
|
return True if response != '' else False
|
2419
2455
|
|
2420
|
-
|
2456
|
+
# Locks
|
2421
2457
|
@property
|
2422
2458
|
def door_locked(self):
|
2423
2459
|
# LEFT FRONT
|
@@ -2460,7 +2496,7 @@ class Vehicle:
|
|
2460
2496
|
return True
|
2461
2497
|
return False
|
2462
2498
|
|
2463
|
-
|
2499
|
+
# Doors, hood and trunk
|
2464
2500
|
@property
|
2465
2501
|
def hood_closed(self):
|
2466
2502
|
"""Return true if hood is closed"""
|
@@ -2545,7 +2581,7 @@ class Vehicle:
|
|
2545
2581
|
response = self.attrs.get('status')['trunk'].get('open', 0)
|
2546
2582
|
return True if response != 0 else False
|
2547
2583
|
|
2548
|
-
|
2584
|
+
# Departure timers
|
2549
2585
|
@property
|
2550
2586
|
def departure1(self):
|
2551
2587
|
"""Return timer status and attributes."""
|
@@ -2657,7 +2693,7 @@ class Vehicle:
|
|
2657
2693
|
return True
|
2658
2694
|
return False
|
2659
2695
|
|
2660
|
-
|
2696
|
+
# Departure profiles
|
2661
2697
|
@property
|
2662
2698
|
def departure_profile1(self):
|
2663
2699
|
"""Return profile status and attributes."""
|
@@ -2730,7 +2766,7 @@ class Vehicle:
|
|
2730
2766
|
return True
|
2731
2767
|
return False
|
2732
2768
|
|
2733
|
-
|
2769
|
+
# Trip data
|
2734
2770
|
@property
|
2735
2771
|
def trip_last_entry(self):
|
2736
2772
|
return self.attrs.get('tripstatistics', {}).get('short', [{},{}])[-1]
|
@@ -2955,7 +2991,26 @@ class Vehicle:
|
|
2955
2991
|
if response and type(response.get('totalElectricConsumption', None)) in (float, int):
|
2956
2992
|
return True
|
2957
2993
|
|
2958
|
-
#
|
2994
|
+
# Area alarm
|
2995
|
+
@property
|
2996
|
+
def area_alarm(self):
|
2997
|
+
"""Return True, if attribute areaAlarm is not {}"""
|
2998
|
+
alarmPresent = self.attrs.get('areaAlarm', {})
|
2999
|
+
if alarmPresent !={}:
|
3000
|
+
# Delete an area alarm if it is older than 900 seconds
|
3001
|
+
alarmTimestamp = self.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
3002
|
+
if alarmTimestamp < datetime.now(tz=None) - timedelta(seconds= 900):
|
3003
|
+
self.attrs.pop("areaAlarm")
|
3004
|
+
alarmPresent = {}
|
3005
|
+
return False if alarmPresent == {} else True
|
3006
|
+
|
3007
|
+
@property
|
3008
|
+
def is_area_alarm_supported(self):
|
3009
|
+
"""Return True, if vehicle supports area alarm (always True at the moment)"""
|
3010
|
+
# Always True at the moment. Have to check, if the geofence capability is a necessary condition
|
3011
|
+
return True
|
3012
|
+
|
3013
|
+
# Status of set data requests
|
2959
3014
|
@property
|
2960
3015
|
def refresh_action_status(self):
|
2961
3016
|
"""Return latest status of data refresh request."""
|
@@ -3060,7 +3115,7 @@ class Vehicle:
|
|
3060
3115
|
"""Data update is supported."""
|
3061
3116
|
return True
|
3062
3117
|
|
3063
|
-
|
3118
|
+
# Honk and flash
|
3064
3119
|
@property
|
3065
3120
|
def request_honkandflash(self):
|
3066
3121
|
"""State is always False"""
|
@@ -3083,7 +3138,7 @@ class Vehicle:
|
|
3083
3138
|
if self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
|
3084
3139
|
return True
|
3085
3140
|
|
3086
|
-
|
3141
|
+
# Requests data
|
3087
3142
|
@property
|
3088
3143
|
def request_in_progress(self):
|
3089
3144
|
"""Request in progress is always supported."""
|
@@ -3138,7 +3193,7 @@ class Vehicle:
|
|
3138
3193
|
#if self.is_request_in_progress_supported:
|
3139
3194
|
# return True if self._requests.get('remaining', False) else False
|
3140
3195
|
|
3141
|
-
|
3196
|
+
#### Helper functions ####
|
3142
3197
|
def __str__(self):
|
3143
3198
|
return self.vin
|
3144
3199
|
|
@@ -3157,7 +3212,7 @@ class Vehicle:
|
|
3157
3212
|
|
3158
3213
|
async def stopFirebase(self):
|
3159
3214
|
# Check if firebase is activated
|
3160
|
-
if self.firebaseStatus
|
3215
|
+
if self.firebaseStatus not in (FIREBASE_STATUS_ACTIVATED, FIREBASE_STATUS_ACTIVATION_STOPPED):
|
3161
3216
|
_LOGGER.info(f'No need to stop firebase. Firebase status={self.firebaseStatus}')
|
3162
3217
|
return self.firebaseStatus
|
3163
3218
|
|
@@ -3171,7 +3226,7 @@ class Vehicle:
|
|
3171
3226
|
_LOGGER.warning('Stopping of firebase messaging failed.')
|
3172
3227
|
return self.firebaseStatus
|
3173
3228
|
|
3174
|
-
#await asyncio.sleep(5)
|
3229
|
+
#await asyncio.sleep(5)
|
3175
3230
|
self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
|
3176
3231
|
_LOGGER.info('Stopping of firebase messaging was successful.')
|
3177
3232
|
return self.firebaseStatus
|
@@ -3236,17 +3291,25 @@ class Vehicle:
|
|
3236
3291
|
_LOGGER.debug(f'Received push notification: notification id={notification}, type={obj.get('data',{}).get('type','')}, requestId={obj.get('data',{}).get('requestId','[None]')}')
|
3237
3292
|
_LOGGER.debug(f' data_message={data_message}, payload={obj.get('data',{}).get('payload','[None]')}')
|
3238
3293
|
|
3239
|
-
#temporary output of notifications in a file
|
3240
|
-
if self.updateCallback == self.update:
|
3241
|
-
|
3294
|
+
#temporary output of notifications in a file, will be removed in the next release
|
3295
|
+
#if self.updateCallback == self.update:
|
3296
|
+
# self.storeFirebaseNotifications(obj, notification, data_message)
|
3242
3297
|
|
3243
3298
|
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
|
3244
|
-
|
3245
|
-
|
3246
|
-
|
3299
|
+
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATION_STOPPED:
|
3300
|
+
_LOGGER.info(f'While firebase is not fully activated, received notifications are just acknowledged.')
|
3301
|
+
# As long as the firebase status is not set to activated, ignore the notifications
|
3302
|
+
return False
|
3303
|
+
else:
|
3304
|
+
# It seems that the firebase connection still works although fcmpushclient.is_started() returned False some time ago
|
3305
|
+
_LOGGER.info(f'Firebase status={self.firebaseStatus}, but PyCupra still receives push notifications.')
|
3306
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
3307
|
+
_LOGGER.info(f'Set firebase status back to {self.firebaseStatus}.')
|
3308
|
+
|
3247
3309
|
|
3248
3310
|
type = obj.get('data',{}).get('type','')
|
3249
3311
|
requestId = obj.get('data',{}).get('requestId','')
|
3312
|
+
payload = obj.get('data',{}).get('payload','')
|
3250
3313
|
openRequest = -1
|
3251
3314
|
if requestId != '':
|
3252
3315
|
_LOGGER.info(f'Received notification of type \'{type}\', request id={requestId} ')
|
@@ -3317,6 +3380,28 @@ class Vehicle:
|
|
3317
3380
|
await self.updateCallback(2)
|
3318
3381
|
else:
|
3319
3382
|
_LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_climater was at {self._last_get_climater}. So no need to update.')
|
3383
|
+
elif type in ('vehicle-area-alarm-vehicle-exits-zone-triggered', 'vehicle-area-alarm-vehicle-enters-zone-triggered'):
|
3384
|
+
#if self._last_get_position < datetime.now(tz=None) - timedelta(seconds= 30):
|
3385
|
+
# # Update position data only if the last one is older than timedelta
|
3386
|
+
# await self.get_position()
|
3387
|
+
#else:
|
3388
|
+
# _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_position was at {self._last_get_position}. So no need to update.')
|
3389
|
+
if payload != '':
|
3390
|
+
payloadDict = json.loads(payload) # Convert json string to dict
|
3391
|
+
#_LOGGER.debug(f'payloadDict is dict: {isinstance(payloadDict, dict)}')
|
3392
|
+
zones = payloadDict.get('description',{}).get('values',[])
|
3393
|
+
else:
|
3394
|
+
_LOGGER.warning(f'Missing information about areas. Payload ={payload}')
|
3395
|
+
zones = []
|
3396
|
+
areaAlarm = {'areaAlarm' : {
|
3397
|
+
'type': 'vehicle-exits-zone' if type=='vehicle-area-alarm-vehicle-exits-zone-triggered' else 'vehicle-enters-zone',
|
3398
|
+
'timestamp': datetime.now(tz=None),
|
3399
|
+
'zones': zones
|
3400
|
+
}
|
3401
|
+
}
|
3402
|
+
self._states.update(areaAlarm)
|
3403
|
+
if self.updateCallback:
|
3404
|
+
await self.updateCallback(2)
|
3320
3405
|
elif type == 'vehicle-wakeup-succeeded':
|
3321
3406
|
if self._requests.get('refresh', {}).get('id', None):
|
3322
3407
|
openRequest= self._requests.get('refresh', {}).get('id', None)
|
@@ -3327,20 +3412,23 @@ class Vehicle:
|
|
3327
3412
|
# Do full update only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
|
3328
3413
|
if self.updateCallback:
|
3329
3414
|
await self.updateCallback(1)
|
3415
|
+
elif type in ('vehicle-area-alert-added', 'vehicle-area-alert-updated'):
|
3416
|
+
_LOGGER.info(f' Intentionally ignoring a notification of type \'{type}\')')
|
3330
3417
|
else:
|
3331
3418
|
_LOGGER.warning(f' Don\'t know what to do with a notification of type \'{type}\')')
|
3332
3419
|
|
3333
3420
|
|
3334
|
-
|
3335
|
-
|
3336
|
-
|
3337
|
-
|
3421
|
+
#temporary output of notifications in a file, will be removed in the next release
|
3422
|
+
#def storeFirebaseNotifications(self, obj, notification, data_message):
|
3423
|
+
# _LOGGER.debug(f'In storeFirebaseNotifications. notification={notification}')
|
3424
|
+
# fName = self._firebaseCredentialsFileName
|
3425
|
+
# fName = fName.replace('pycupra_firebase_credentials.json', 'pycupra_firebasenotifications.txt')
|
3338
3426
|
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3343
|
-
|
3427
|
+
# with open(fName, "a") as ofile:
|
3428
|
+
# ofile.write(f'{datetime.now()}\n')
|
3429
|
+
# ofile.write(f' notification id={notification}, data_message={data_message}\n')
|
3430
|
+
# ofile.write(f' obj={obj}\n')
|
3431
|
+
# ofile.write("----------------------------------------------------------------\n")
|
3344
3432
|
|
3345
3433
|
|
3346
3434
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pycupra
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
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,25 +1,25 @@
|
|
1
1
|
pycupra/__init__.py,sha256=p0880jPkLqOErX3u3qaLboBLOsEHFpe44axApdaGeqI,231
|
2
|
-
pycupra/__version__.py,sha256=
|
3
|
-
pycupra/connection.py,sha256=
|
4
|
-
pycupra/const.py,sha256=
|
5
|
-
pycupra/dashboard.py,sha256=
|
2
|
+
pycupra/__version__.py,sha256=ahbRianoeRb9v2bqw7g3dKIyoW7jt_rvwY0nas5Bn0c,207
|
3
|
+
pycupra/connection.py,sha256=5bUxSUC03O-4kceZn8fPWKyOaxOq13JVMjY-_UqrOwo,92187
|
4
|
+
pycupra/const.py,sha256=hCtDfI0gRewmVjXOQRrC4pUphOxGwCA3_Plfp2NwFXU,10637
|
5
|
+
pycupra/dashboard.py,sha256=bEmIRvxX08WNa0rftpQ6JrD0GW5FeVTDu5yQr0IUjMM,45178
|
6
6
|
pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
|
7
7
|
pycupra/firebase.py,sha256=tuN_W3OX3h3-yfdprWzmCn6z_T-BMx-OpL7Z6hOA8Lc,3451
|
8
8
|
pycupra/utilities.py,sha256=6sDxWP13-XtxmqhuBJBGdVbkj48BQ9AxFMrBPxH0J7g,2679
|
9
|
-
pycupra/vehicle.py,sha256=
|
9
|
+
pycupra/vehicle.py,sha256=1sKJJ4xuBIrx6ZWnH50-b7bzjmqyBgRiETOSzDiNNN8,162609
|
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
|
13
13
|
pycupra/firebase_messaging/checkin_pb2.py,sha256=lFzCIAkYz9NFUpRbVuW-2kM_EaYKVWHeifHS1PV2eHQ,2795
|
14
14
|
pycupra/firebase_messaging/checkin_pb2.pyi,sha256=mHOqbedt5jZDI20jcyFrTMSnQ0f_tq4zkIlHiaSC3xI,14626
|
15
15
|
pycupra/firebase_messaging/const.py,sha256=XMy8kJ37uBSkTpVpdLeSjxk5UIPuvDuo-rxYdgmo2G8,1191
|
16
|
-
pycupra/firebase_messaging/fcmpushclient.py,sha256=
|
16
|
+
pycupra/firebase_messaging/fcmpushclient.py,sha256=5kAp6rnl9EJqVPn9OfHFKUfFDRWA9LAVbfiL-pLRSqo,30550
|
17
17
|
pycupra/firebase_messaging/fcmregister.py,sha256=yZngC-0ZfTygtjfdzg03OW_3xk2n_uSQQ3Lrash5Y_E,18091
|
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.8.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
22
|
+
pycupra-0.1.8.dist-info/METADATA,sha256=GC1Av2ilKrcM3hNfIoFPsBRKsSBjzlkIQrH8nmUzijY,3757
|
23
|
+
pycupra-0.1.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
24
|
+
pycupra-0.1.8.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
|
25
|
+
pycupra-0.1.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|