pycupra 0.1.7__py3-none-any.whl → 0.1.9__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 CHANGED
@@ -3,4 +3,4 @@ pycupra - A Python 3 library for interacting with the My Cupra/My Seat portal.
3
3
 
4
4
  For more details and documentation, visit the github page at https://github.com/WulfgarW/pycupra
5
5
  """
6
- __version__ = "0.1.7"
6
+ __version__ = "0.1.9"
pycupra/connection.py CHANGED
@@ -98,6 +98,7 @@ from .const import (
98
98
  API_DESTINATION,
99
99
 
100
100
  PUBLIC_MODEL_IMAGES_SERVER,
101
+ FIREBASE_STATUS_NOT_INITIALISED,
101
102
  )
102
103
 
103
104
  version_info >= (3, 0) or exit('Python 3 required')
@@ -109,13 +110,14 @@ TIMEOUT = timedelta(seconds=90)
109
110
  class Connection:
110
111
  """ Connection to Connect services """
111
112
  # Init connection class
112
- def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, **optional):
113
+ def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, tripStatisticsStartDate=None, **optional):
113
114
  """ Initialize """
114
115
  self._session = session
115
116
  self._lock = asyncio.Lock()
116
117
  self._session_fulldebug = fulldebug
117
118
  self._session_nightlyUpdateReduction = nightlyUpdateReduction
118
119
  self._session_anonymise = anonymise
120
+ self._session_tripStatisticsStartDate = tripStatisticsStartDate
119
121
  self._session_headers = HEADERS_SESSION.get(brand).copy()
120
122
  self._session_base = BASE_SESSION
121
123
  self._session_auth_headers = HEADERS_AUTH.copy()
@@ -560,8 +562,10 @@ class Connection:
560
562
  for v in self.vehicles:
561
563
  _LOGGER.debug(self.anonymise(f'Calling stopFirebase() for vehicle {v.vin}'))
562
564
  newStatus = await v.stopFirebase()
563
- if newStatus != 0:
565
+ if newStatus != FIREBASE_STATUS_NOT_INITIALISED:
564
566
  _LOGGER.debug(self.anonymise(f'stopFirebase() not successful for vehicle {v.vin}'))
567
+ # Although stopFirebase() was not successful, the firebase status is reset to FIREBASE_STATUS_NOT_INITIALISED to allow a new initialisation
568
+ v.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
565
569
  await self.logout()
566
570
 
567
571
  async def logout(self):
@@ -636,7 +640,12 @@ class Connection:
636
640
  async def _request(self, method, url, **kwargs):
637
641
  """Perform a HTTP query"""
638
642
  if self._session_fulldebug:
639
- _LOGGER.debug(self.anonymise(f'HTTP {method} "{url}"'))
643
+ argsString =''
644
+ if len(kwargs)>0:
645
+ argsString = 'with '
646
+ for k, val in kwargs.items():
647
+ argsString = argsString + f"{k}=\'{val}\' "
648
+ _LOGGER.debug(self.anonymise(f'HTTP {method} "{url}" {argsString}'))
640
649
  try:
641
650
  if datetime.now(tz=None).date() != self._sessionRequestTimestamp.date():
642
651
  # A new day has begun. Store _sessionRequestCounter in history and reset timestamp and counter
@@ -701,6 +710,9 @@ class Connection:
701
710
  if self._session_fulldebug:
702
711
  if 'image/png' in response.headers.get('Content-Type', ''):
703
712
  _LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}]. Not showing response for Content-Type image/png.'))
713
+ elif method==METH_PUT or method==METH_DELETE:
714
+ # deepcopy() of res can produce errors, if res is the API response on PUT or DELETE
715
+ _LOGGER.debug(f'Request for "{self.anonymise(url)}" returned with status code [{response.status}]. Not showing response for http {method}')
704
716
  else:
705
717
  _LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}], response: {self.anonymise(deepcopy(res))}'))
706
718
  else:
@@ -1080,6 +1092,12 @@ class Connection:
1080
1092
  async def getTripStatistics(self, vin, baseurl, supportsCyclicTrips):
1081
1093
  """Get short term and cyclic trip statistics."""
1082
1094
  await self.set_token(self._session_auth_brand)
1095
+ if self._session_tripStatisticsStartDate==None:
1096
+ # If connection was not initialised with parameter tripStatisticsStartDate, then 360 day is used for the CYCLIC trips and 90 days for the SHORT trips
1097
+ # (This keeps the statistics shorter in Home Assistant)
1098
+ startDate = (datetime.now() - timedelta(days= 360)).strftime('%Y-%m-%d')
1099
+ else:
1100
+ startDate = self._session_tripStatisticsStartDate
1083
1101
  try:
1084
1102
  data={'tripstatistics': {}}
1085
1103
  if supportsCyclicTrips:
@@ -1094,6 +1112,10 @@ class Connection:
1094
1112
  else:
1095
1113
  _LOGGER.info(f'Vehicle does not support cyclic trips.')
1096
1114
  dataType='SHORT'
1115
+ if self._session_tripStatisticsStartDate==None:
1116
+ # If connection was not initialised with parameter tripStatisticsStartDate, then 360 day is used for the CYCLIC trips and 90 days for the SHORT trips
1117
+ # (This keeps the statistics shorter in Home Assistant)
1118
+ startDate = (datetime.now() - timedelta(days= 90)).strftime('%Y-%m-%d')
1097
1119
  response = await self.get(eval(f"f'{API_TRIP}'"))
1098
1120
  if response.get('data', []):
1099
1121
  data['tripstatistics']['short']= response.get('data', [])
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=1970-01-01T00:00:00Z&to=2099-12-31T09:59:01Z' # Trip statistics (whole history) SHORT/LONG/CYCLIC (WEEK only with from)
141
+ API_TRIP = '{baseurl}/v1/vehicles/{vin}/driving-data/{dataType}?from={startDate}T00: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
@@ -455,7 +455,7 @@ class RequestRefresh(Switch):
455
455
  async def turn_on(self):
456
456
  _LOGGER.debug('User has called RequestRefresh().')
457
457
  await self.vehicle.set_refresh()
458
- await self.vehicle.update(updateType=1) #full update after set_refresh
458
+ #await self.vehicle.update(updateType=1) #full update after set_refresh
459
459
  if self.callback is not None:
460
460
  self.callback()
461
461
 
@@ -776,11 +776,12 @@ class DepartureTimer1(Switch):
776
776
 
777
777
  @property
778
778
  def state(self):
779
- status = self.vehicle.departure1.get("enabled", "")
780
- if status:
781
- return True
782
- else:
783
- return False
779
+ if self.vehicle.departure1 != None:
780
+ status = self.vehicle.departure1.get("enabled", "")
781
+ if status:
782
+ return True
783
+ #else:
784
+ return False
784
785
 
785
786
  async def turn_on(self):
786
787
  await self.vehicle.set_timer_active(id=1, action="on")
@@ -808,11 +809,12 @@ class DepartureTimer2(Switch):
808
809
 
809
810
  @property
810
811
  def state(self):
811
- status = self.vehicle.departure2.get("enabled", "")
812
- if status:
813
- return True
814
- else:
815
- return False
812
+ if self.vehicle.departure2 != None:
813
+ status = self.vehicle.departure2.get("enabled", "")
814
+ if status:
815
+ return True
816
+ #else:
817
+ return False
816
818
 
817
819
  async def turn_on(self):
818
820
  await self.vehicle.set_timer_active(id=2, action="on")
@@ -839,11 +841,12 @@ class DepartureTimer3(Switch):
839
841
 
840
842
  @property
841
843
  def state(self):
842
- status = self.vehicle.departure3.get("enabled", "")
843
- if status:
844
- return True
845
- else:
846
- return False
844
+ if self.vehicle.departure3 != None:
845
+ status = self.vehicle.departure3.get("enabled", "")
846
+ if status:
847
+ return True
848
+ #else:
849
+ return False
847
850
 
848
851
  async def turn_on(self):
849
852
  await self.vehicle.set_timer_active(id=3, action="on")
@@ -1002,6 +1005,32 @@ class ChargingState(BinarySensor):
1002
1005
  attr['mode']=mode
1003
1006
  return attr
1004
1007
 
1008
+ class AreaAlarm(BinarySensor):
1009
+ def __init__(self):
1010
+ super().__init__(attr="area_alarm", name="Area alarm", icon="mdi:alarm-light", device_class=None)
1011
+
1012
+ @property
1013
+ def state(self):
1014
+ return self.vehicle.area_alarm
1015
+
1016
+ @property
1017
+ def assumed_state(self):
1018
+ return False
1019
+
1020
+ @property
1021
+ def attributes(self):
1022
+ attr = {}
1023
+ type = self.vehicle.attrs.get('areaAlarm', {}).get('type', '')
1024
+ zones = self.vehicle.attrs.get('areaAlarm', {}).get('zones', [])
1025
+ timestamp = self.vehicle.attrs.get('areaAlarm', {}).get('timestamp', 0)
1026
+ if type != '':
1027
+ attr['type']=type
1028
+ if len(zones) > 0:
1029
+ attr['zone']=zones[0]
1030
+ if timestamp != 0:
1031
+ attr['timestamp']=timestamp
1032
+ return attr
1033
+
1005
1034
  def create_instruments():
1006
1035
  return [
1007
1036
  Position(),
@@ -1030,6 +1059,7 @@ def create_instruments():
1030
1059
  DepartureProfile2(),
1031
1060
  DepartureProfile3(),
1032
1061
  ChargingState(),
1062
+ AreaAlarm(),
1033
1063
  Sensor(
1034
1064
  attr="distance",
1035
1065
  name="Odometer",
pycupra/firebase.py CHANGED
@@ -43,7 +43,7 @@ class Firebase():
43
43
  await asyncio.sleep(5)
44
44
  return self._pushClient.is_started()
45
45
  except Exception as e:
46
- _LOGGER.error('Error in firebaseStart. Error: {e}')
46
+ _LOGGER.error(f'Error in firebaseStart. Error: {e}')
47
47
  return False
48
48
 
49
49
  async def firebaseStop(self):
@@ -54,7 +54,7 @@ class Firebase():
54
54
  self._pushClient = None
55
55
  return True
56
56
  except Exception as e:
57
- _LOGGER.error('Error in firebaseStop. Error: {e}')
57
+ _LOGGER.error(f'Error in firebaseStop. Error: {e}')
58
58
  return False
59
59
 
60
60
  def readFCMCredsFile(credsFile):
@@ -70,7 +70,7 @@ def readFCMCredsFile(credsFile):
70
70
  _LOGGER.debug(f'{credsFile} not found.')
71
71
  return {}
72
72
  except Exception as e:
73
- _LOGGER.warning('readFCMCredsFile() not successful. Error: {e}')
73
+ _LOGGER.warning(f'readFCMCredsFile() not successful. Error: {e}')
74
74
  return ''
75
75
 
76
76
  def writeFCMCredsFile(creds, firebaseCredentialsFileName):
@@ -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 = 20 # original value was 10
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 = 30 # original value was 20
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
- #await self.get_trip_statistic() # in full update
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,28 @@ 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
+ # Although stopFirebase() was not successful, the firebase status is reset to FIREBASE_STATUS_NOT_INITIALISED to allow a new initialisation
192
+ self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
193
+ newStatus = await self.initialiseFirebase(self._firebaseCredentialsFileName, self.updateCallback)
194
+ if newStatus == FIREBASE_STATUS_ACTIVATED:
195
+ _LOGGER.debug(f'Reinitialisation of firebase successful.New firebase status={self.firebaseStatus}.')
196
+ else:
197
+ self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_STOPPED
198
+ _LOGGER.warning(f'Reinitialisation of firebase failed. New firebase status={self.firebaseStatus}.')
199
+
173
200
  if self._connection._session_nightlyUpdateReduction:
174
201
  # nightlyUpdateReduction is activated
175
202
  if datetime.now(tz=None).hour<5 or datetime.now(tz=None).hour>=22:
@@ -198,16 +225,6 @@ class Vehicle:
198
225
  if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
199
226
  await self.get_mileage()
200
227
 
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
228
 
212
229
  await asyncio.gather(
213
230
  #self.get_statusreport(),
@@ -244,6 +261,10 @@ class Vehicle:
244
261
  data = await self._connection.getBasicCarData(self.vin, self._apibase)
245
262
  if data:
246
263
  self._states.update(data)
264
+ return True
265
+ else:
266
+ _LOGGER.debug('Could not fetch basic car data')
267
+ return False
247
268
 
248
269
  async def get_mileage(self):
249
270
  """Fetch basic car data."""
@@ -251,6 +272,10 @@ class Vehicle:
251
272
  if data:
252
273
  self._states.update(data)
253
274
  self._last_get_mileage = datetime.now(tz=None)
275
+ return True
276
+ else:
277
+ _LOGGER.debug('Could not fetch mileage data')
278
+ return False
254
279
 
255
280
  async def get_preheater(self):
256
281
  """Fetch pre-heater data if function is enabled."""
@@ -272,8 +297,10 @@ class Vehicle:
272
297
  if data:
273
298
  self._states.update(data)
274
299
  self._last_get_climater = datetime.now(tz=None)
300
+ return True
275
301
  else:
276
302
  _LOGGER.debug('Could not fetch climater data')
303
+ return False
277
304
  #else:
278
305
  # self._requests.pop('climatisation', None)
279
306
 
@@ -283,8 +310,10 @@ class Vehicle:
283
310
  data = await self._connection.getTripStatistics(self.vin, self._apibase, self._relevantCapabilties['tripStatistics'].get('supportsCyclicTrips', False))
284
311
  if data:
285
312
  self._states.update(data)
313
+ return True
286
314
  else:
287
315
  _LOGGER.debug('Could not fetch trip statistics')
316
+ return False
288
317
 
289
318
  async def get_position(self):
290
319
  """Fetch position data if function is enabled."""
@@ -301,24 +330,21 @@ class Vehicle:
301
330
  except:
302
331
  pass
303
332
  self._states.update(data)
333
+ self._last_get_position = datetime.now(tz=None)
334
+ return True
304
335
  else:
305
336
  _LOGGER.debug('Could not fetch any positional data')
337
+ return False
306
338
 
307
339
  async def get_vehicleHealthWarnings(self):
308
340
  if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
309
341
  data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
310
342
  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
343
  self._states.update(data)
344
+ return True
320
345
  else:
321
346
  _LOGGER.debug('Could not fetch vehicle health warnings')
347
+ return False
322
348
 
323
349
  async def get_statusreport(self):
324
350
  """Fetch status data if function is enabled."""
@@ -327,8 +353,10 @@ class Vehicle:
327
353
  if data:
328
354
  self._states.update(data)
329
355
  self._last_get_statusreport = datetime.now(tz=None)
356
+ return True
330
357
  else:
331
358
  _LOGGER.debug('Could not fetch status report')
359
+ return False
332
360
 
333
361
  async def get_maintenance(self):
334
362
  """Fetch maintenance data if function is enabled."""
@@ -336,8 +364,10 @@ class Vehicle:
336
364
  data = await self._connection.getMaintenance(self.vin, self._apibase)
337
365
  if data:
338
366
  self._states.update(data)
367
+ return True
339
368
  else:
340
369
  _LOGGER.debug('Could not fetch status report')
370
+ return False
341
371
 
342
372
  async def get_charger(self):
343
373
  """Fetch charger data if function is enabled."""
@@ -346,8 +376,10 @@ class Vehicle:
346
376
  if data:
347
377
  self._states.update(data)
348
378
  self._last_get_charger = datetime.now(tz=None)
379
+ return True
349
380
  else:
350
381
  _LOGGER.debug('Could not fetch charger data')
382
+ return False
351
383
 
352
384
  async def get_departure_timers(self):
353
385
  """Fetch timer data if function is enabled."""
@@ -356,8 +388,10 @@ class Vehicle:
356
388
  if data:
357
389
  self._states.update(data)
358
390
  self._last_get_departure_timers = datetime.now(tz=None)
391
+ return True
359
392
  else:
360
393
  _LOGGER.debug('Could not fetch timers')
394
+ return False
361
395
 
362
396
  async def get_departure_profiles(self):
363
397
  """Fetch timer data if function is enabled."""
@@ -366,8 +400,10 @@ class Vehicle:
366
400
  if data:
367
401
  self._states.update(data)
368
402
  self._last_get_departure_profiles = datetime.now(tz=None)
403
+ return True
369
404
  else:
370
405
  _LOGGER.debug('Could not fetch timers')
406
+ return False
371
407
 
372
408
  #async def wait_for_request(self, section, request, retryCount=36):
373
409
  """Update status of outstanding requests."""
@@ -1000,7 +1036,7 @@ class Vehicle:
1000
1036
  async def set_climatisation_temp(self, temperature=20):
1001
1037
  """Set climatisation target temp."""
1002
1038
  if self.is_electric_climatisation_supported or self.is_auxiliary_climatisation_supported:
1003
- if 16 <= int(temperature) <= 30:
1039
+ if 16 <= float(temperature) <= 30:
1004
1040
  data = {
1005
1041
  'climatisationWithoutExternalPower': self.climatisation_without_external_power,
1006
1042
  'targetTemperature': temperature,
@@ -1051,28 +1087,30 @@ class Vehicle:
1051
1087
  data = {}
1052
1088
  # Validate user input
1053
1089
  if mode.lower() not in ['electric', 'auxiliary', 'start', 'stop', 'on', 'off']:
1090
+ _LOGGER.error(f"Invalid mode for 'set_climatisation': {mode}")
1054
1091
  raise SeatInvalidRequestException(f"Invalid mode for set_climatisation: {mode}")
1055
1092
  elif mode == 'auxiliary' and spin is None:
1056
1093
  raise SeatInvalidRequestException("Starting auxiliary heater requires provided S-PIN")
1057
1094
  if temp is not None:
1058
- if not isinstance(temp, float):
1095
+ if not isinstance(temp, float) and not isinstance(temp, int):
1096
+ _LOGGER.error(f"Invalid type for temp. type={type(temp)}")
1059
1097
  raise SeatInvalidRequestException(f"Invalid type for temp")
1060
1098
  elif not 16 <= float(temp) <=30:
1061
1099
  raise SeatInvalidRequestException(f"Invalid value for temp")
1062
1100
  else:
1063
1101
  temp = self.climatisation_target_temperature
1064
- if hvpower is not None:
1065
- if not isinstance(hvpower, bool):
1066
- raise SeatInvalidRequestException(f"Invalid type for hvpower")
1102
+ #if hvpower is not None:
1103
+ # if not isinstance(hvpower, bool):
1104
+ # raise SeatInvalidRequestException(f"Invalid type for hvpower")
1067
1105
  if self.is_electric_climatisation_supported:
1068
1106
  if self._relevantCapabilties.get('climatisation', {}).get('active', False):
1069
1107
  if mode in ['Start', 'start', 'Electric', 'electric', 'On', 'on']:
1070
1108
  mode = 'start'
1071
1109
  if mode in ['start', 'auxiliary']:
1072
- if hvpower is not None:
1073
- withoutHVPower = hvpower
1074
- else:
1075
- withoutHVPower = self.climatisation_without_external_power
1110
+ #if hvpower is not None:
1111
+ # withoutHVPower = hvpower
1112
+ #else:
1113
+ # withoutHVPower = self.climatisation_without_external_power
1076
1114
  data = {
1077
1115
  'targetTemperature': temp,
1078
1116
  'targetTemperatureUnit': 'celsius',
@@ -1159,7 +1197,7 @@ class Vehicle:
1159
1197
  self._requests['climatisation'] = {'status': 'Exception'}
1160
1198
  raise SeatException('Climatisation action failed')
1161
1199
 
1162
- # Parking heater heating/ventilation (RS)
1200
+ # Parking heater heating/ventilation (RS)
1163
1201
  async def set_pheater(self, mode, spin):
1164
1202
  """Set the mode for the parking heater."""
1165
1203
  if not self.is_pheater_heating_supported:
@@ -1202,7 +1240,7 @@ class Vehicle:
1202
1240
  self._requests['preheater'] = {'status': 'Exception'}
1203
1241
  raise SeatException('Pre-heater action failed')
1204
1242
 
1205
- # Lock
1243
+ # Lock
1206
1244
  async def set_lock(self, action, spin):
1207
1245
  """Remote lock and unlock actions."""
1208
1246
  #if not self._services.get('rlu_v1', False):
@@ -1264,7 +1302,7 @@ class Vehicle:
1264
1302
  self._requests['lock'] = {'status': 'Exception'}
1265
1303
  raise SeatException('Lock action failed')
1266
1304
 
1267
- # Honk and flash (RHF)
1305
+ # Honk and flash (RHF)
1268
1306
  async def set_honkandflash(self, action, lat=None, lng=None):
1269
1307
  """Turn on/off honk and flash."""
1270
1308
  if not self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
@@ -1321,7 +1359,7 @@ class Vehicle:
1321
1359
  self._requests['honkandflash'] = {'status': 'Exception'}
1322
1360
  raise SeatException('Honk and flash action failed')
1323
1361
 
1324
- # Refresh vehicle data (VSR)
1362
+ # Refresh vehicle data (VSR)
1325
1363
  async def set_refresh(self):
1326
1364
  """Wake up vehicle and update status data."""
1327
1365
  if not self._relevantCapabilties.get('state', {}).get('active', False):
@@ -1348,6 +1386,11 @@ class Vehicle:
1348
1386
  'status': response.get('status', 'Unknown'),
1349
1387
  'id': response.get('id', 0)
1350
1388
  }
1389
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
1390
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
1391
+ _LOGGER.debug('POST request for wakeup vehicle assumed successful. Waiting for push notification')
1392
+ return True
1393
+ await self.update(updateType=1) #full update after set_refresh
1351
1394
  return True
1352
1395
  except(SeatInvalidRequestException, SeatException):
1353
1396
  raise
@@ -1357,7 +1400,7 @@ class Vehicle:
1357
1400
  raise SeatException('Data refresh failed')
1358
1401
 
1359
1402
  #### Vehicle class helpers ####
1360
- # Vehicle info
1403
+ # Vehicle info
1361
1404
  @property
1362
1405
  def attrs(self):
1363
1406
  return self._states
@@ -1390,7 +1433,7 @@ class Vehicle:
1390
1433
 
1391
1434
 
1392
1435
  #### Information from vehicle states ####
1393
- # Car information
1436
+ # Car information
1394
1437
  @property
1395
1438
  def nickname(self):
1396
1439
  return self._properties.get('vehicleNickname', '')
@@ -1479,7 +1522,7 @@ class Vehicle:
1479
1522
  if self._modelimages is not None:
1480
1523
  return True
1481
1524
 
1482
- # Lights
1525
+ # Lights
1483
1526
  @property
1484
1527
  def parking_light(self):
1485
1528
  """Return true if parking light is on"""
@@ -1498,7 +1541,7 @@ class Vehicle:
1498
1541
  else:
1499
1542
  return False
1500
1543
 
1501
- # Connection status
1544
+ # Connection status
1502
1545
  @property
1503
1546
  def last_connected(self):
1504
1547
  """Return when vehicle was last connected to connect servers."""
@@ -1515,7 +1558,7 @@ class Vehicle:
1515
1558
  if 'updatedAt' in self.attrs.get('status', {}):
1516
1559
  return True
1517
1560
 
1518
- # Update status
1561
+ # Update status
1519
1562
  @property
1520
1563
  def last_full_update(self):
1521
1564
  """Return when the last full update for the vehicle took place."""
@@ -1527,7 +1570,7 @@ class Vehicle:
1527
1570
  if hasattr(self,'_last_full_update'):
1528
1571
  return True
1529
1572
 
1530
- # Service information
1573
+ # Service information
1531
1574
  @property
1532
1575
  def distance(self):
1533
1576
  """Return vehicle odometer."""
@@ -1615,7 +1658,7 @@ class Vehicle:
1615
1658
  return True
1616
1659
  return False
1617
1660
 
1618
- # Charger related states for EV and PHEV
1661
+ # Charger related states for EV and PHEV
1619
1662
  @property
1620
1663
  def charging(self):
1621
1664
  """Return battery level"""
@@ -1874,7 +1917,7 @@ class Vehicle:
1874
1917
  if self.attrs.get('charging', {}).get('info', {}).get('settings', {}).get('targetSoc', False):
1875
1918
  return True
1876
1919
 
1877
- # Vehicle location states
1920
+ # Vehicle location states
1878
1921
  @property
1879
1922
  def position(self):
1880
1923
  """Return position."""
@@ -1941,7 +1984,7 @@ class Vehicle:
1941
1984
  if 'parkingTimeUTC' in self.attrs.get('findCarResponse', {}):
1942
1985
  return True
1943
1986
 
1944
- # Vehicle fuel level and range
1987
+ # Vehicle fuel level and range
1945
1988
  @property
1946
1989
  def primary_range(self):
1947
1990
  value = -1
@@ -2080,7 +2123,7 @@ class Vehicle:
2080
2123
  return self.is_secondary_range_supported
2081
2124
  return False
2082
2125
 
2083
- # Climatisation settings
2126
+ # Climatisation settings
2084
2127
  @property
2085
2128
  def climatisation_target_temperature(self):
2086
2129
  """Return the target temperature from climater."""
@@ -2154,7 +2197,7 @@ class Vehicle:
2154
2197
  else:
2155
2198
  return False
2156
2199
 
2157
- # Climatisation, electric
2200
+ # Climatisation, electric
2158
2201
  @property
2159
2202
  def electric_climatisation_attributes(self):
2160
2203
  """Return climatisation attributes."""
@@ -2262,7 +2305,7 @@ class Vehicle:
2262
2305
  return True
2263
2306
  return False
2264
2307
 
2265
- # Parking heater, "legacy" auxiliary climatisation
2308
+ # Parking heater, "legacy" auxiliary climatisation
2266
2309
  @property
2267
2310
  def pheater_duration(self):
2268
2311
  return self._climate_duration
@@ -2310,7 +2353,7 @@ class Vehicle:
2310
2353
  if self.attrs.get('heating', {}).get('climatisationStateReport', {}).get('climatisationState', False):
2311
2354
  return True
2312
2355
 
2313
- # Windows
2356
+ # Windows
2314
2357
  @property
2315
2358
  def windows_closed(self):
2316
2359
  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 +2460,7 @@ class Vehicle:
2417
2460
  # response = self.attrs.get('status')['windows'].get('sunRoof', '')
2418
2461
  return True if response != '' else False
2419
2462
 
2420
- # Locks
2463
+ # Locks
2421
2464
  @property
2422
2465
  def door_locked(self):
2423
2466
  # LEFT FRONT
@@ -2460,7 +2503,7 @@ class Vehicle:
2460
2503
  return True
2461
2504
  return False
2462
2505
 
2463
- # Doors, hood and trunk
2506
+ # Doors, hood and trunk
2464
2507
  @property
2465
2508
  def hood_closed(self):
2466
2509
  """Return true if hood is closed"""
@@ -2545,7 +2588,7 @@ class Vehicle:
2545
2588
  response = self.attrs.get('status')['trunk'].get('open', 0)
2546
2589
  return True if response != 0 else False
2547
2590
 
2548
- # Departure timers
2591
+ # Departure timers
2549
2592
  @property
2550
2593
  def departure1(self):
2551
2594
  """Return timer status and attributes."""
@@ -2657,7 +2700,7 @@ class Vehicle:
2657
2700
  return True
2658
2701
  return False
2659
2702
 
2660
- # Departure profiles
2703
+ # Departure profiles
2661
2704
  @property
2662
2705
  def departure_profile1(self):
2663
2706
  """Return profile status and attributes."""
@@ -2730,7 +2773,7 @@ class Vehicle:
2730
2773
  return True
2731
2774
  return False
2732
2775
 
2733
- # Trip data
2776
+ # Trip data
2734
2777
  @property
2735
2778
  def trip_last_entry(self):
2736
2779
  return self.attrs.get('tripstatistics', {}).get('short', [{},{}])[-1]
@@ -2955,7 +2998,26 @@ class Vehicle:
2955
2998
  if response and type(response.get('totalElectricConsumption', None)) in (float, int):
2956
2999
  return True
2957
3000
 
2958
- # Status of set data requests
3001
+ # Area alarm
3002
+ @property
3003
+ def area_alarm(self):
3004
+ """Return True, if attribute areaAlarm is not {}"""
3005
+ alarmPresent = self.attrs.get('areaAlarm', {})
3006
+ if alarmPresent !={}:
3007
+ # Delete an area alarm if it is older than 900 seconds
3008
+ alarmTimestamp = self.attrs.get('areaAlarm', {}).get('timestamp', 0)
3009
+ if alarmTimestamp < datetime.now(tz=None) - timedelta(seconds= 900):
3010
+ self.attrs.pop("areaAlarm")
3011
+ alarmPresent = {}
3012
+ return False if alarmPresent == {} else True
3013
+
3014
+ @property
3015
+ def is_area_alarm_supported(self):
3016
+ """Return True, if vehicle supports area alarm (always True at the moment)"""
3017
+ # Always True at the moment. Have to check, if the geofence capability is a necessary condition
3018
+ return True
3019
+
3020
+ # Status of set data requests
2959
3021
  @property
2960
3022
  def refresh_action_status(self):
2961
3023
  """Return latest status of data refresh request."""
@@ -3060,7 +3122,7 @@ class Vehicle:
3060
3122
  """Data update is supported."""
3061
3123
  return True
3062
3124
 
3063
- # Honk and flash
3125
+ # Honk and flash
3064
3126
  @property
3065
3127
  def request_honkandflash(self):
3066
3128
  """State is always False"""
@@ -3083,7 +3145,7 @@ class Vehicle:
3083
3145
  if self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
3084
3146
  return True
3085
3147
 
3086
- # Requests data
3148
+ # Requests data
3087
3149
  @property
3088
3150
  def request_in_progress(self):
3089
3151
  """Request in progress is always supported."""
@@ -3138,7 +3200,7 @@ class Vehicle:
3138
3200
  #if self.is_request_in_progress_supported:
3139
3201
  # return True if self._requests.get('remaining', False) else False
3140
3202
 
3141
- #### Helper functions ####
3203
+ #### Helper functions ####
3142
3204
  def __str__(self):
3143
3205
  return self.vin
3144
3206
 
@@ -3157,7 +3219,7 @@ class Vehicle:
3157
3219
 
3158
3220
  async def stopFirebase(self):
3159
3221
  # Check if firebase is activated
3160
- if self.firebaseStatus!= FIREBASE_STATUS_ACTIVATED:
3222
+ if self.firebaseStatus not in (FIREBASE_STATUS_ACTIVATED, FIREBASE_STATUS_ACTIVATION_STOPPED):
3161
3223
  _LOGGER.info(f'No need to stop firebase. Firebase status={self.firebaseStatus}')
3162
3224
  return self.firebaseStatus
3163
3225
 
@@ -3171,7 +3233,7 @@ class Vehicle:
3171
3233
  _LOGGER.warning('Stopping of firebase messaging failed.')
3172
3234
  return self.firebaseStatus
3173
3235
 
3174
- #await asyncio.sleep(5) # Wait to ignore the first notifications
3236
+ #await asyncio.sleep(5)
3175
3237
  self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
3176
3238
  _LOGGER.info('Stopping of firebase messaging was successful.')
3177
3239
  return self.firebaseStatus
@@ -3236,17 +3298,21 @@ class Vehicle:
3236
3298
  _LOGGER.debug(f'Received push notification: notification id={notification}, type={obj.get('data',{}).get('type','')}, requestId={obj.get('data',{}).get('requestId','[None]')}')
3237
3299
  _LOGGER.debug(f' data_message={data_message}, payload={obj.get('data',{}).get('payload','[None]')}')
3238
3300
 
3239
- #temporary output of notifications in a file
3240
- if self.updateCallback == self.update:
3241
- self.storeFirebaseNotifications(obj, notification, data_message)
3242
-
3243
3301
  if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
3244
- _LOGGER.info(f'While firebase is not fully activated, received notifications are just acknowledged.')
3245
- # As long as the firebase status is not set to activated, ignore the notifications
3246
- return False
3302
+ if self.firebaseStatus != FIREBASE_STATUS_ACTIVATION_STOPPED:
3303
+ _LOGGER.info(f'While firebase is not fully activated, received notifications are just acknowledged.')
3304
+ # As long as the firebase status is not set to activated, ignore the notifications
3305
+ return False
3306
+ else:
3307
+ # It seems that the firebase connection still works although fcmpushclient.is_started() returned False some time ago
3308
+ _LOGGER.info(f'Firebase status={self.firebaseStatus}, but PyCupra still receives push notifications.')
3309
+ self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
3310
+ _LOGGER.info(f'Set firebase status back to {self.firebaseStatus}.')
3311
+
3247
3312
 
3248
3313
  type = obj.get('data',{}).get('type','')
3249
3314
  requestId = obj.get('data',{}).get('requestId','')
3315
+ payload = obj.get('data',{}).get('payload','')
3250
3316
  openRequest = -1
3251
3317
  if requestId != '':
3252
3318
  _LOGGER.info(f'Received notification of type \'{type}\', request id={requestId} ')
@@ -3317,7 +3383,29 @@ class Vehicle:
3317
3383
  await self.updateCallback(2)
3318
3384
  else:
3319
3385
  _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_climater was at {self._last_get_climater}. So no need to update.')
3320
- elif type == 'vehicle-wakeup-succeeded':
3386
+ elif type in ('vehicle-area-alarm-vehicle-exits-zone-triggered', 'vehicle-area-alarm-vehicle-enters-zone-triggered'):
3387
+ #if self._last_get_position < datetime.now(tz=None) - timedelta(seconds= 30):
3388
+ # # Update position data only if the last one is older than timedelta
3389
+ # await self.get_position()
3390
+ #else:
3391
+ # _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_position was at {self._last_get_position}. So no need to update.')
3392
+ if payload != '':
3393
+ payloadDict = json.loads(payload) # Convert json string to dict
3394
+ #_LOGGER.debug(f'payloadDict is dict: {isinstance(payloadDict, dict)}')
3395
+ zones = payloadDict.get('description',{}).get('values',[])
3396
+ else:
3397
+ _LOGGER.warning(f'Missing information about areas. Payload ={payload}')
3398
+ zones = []
3399
+ areaAlarm = {'areaAlarm' : {
3400
+ 'type': 'vehicle-exits-zone' if type=='vehicle-area-alarm-vehicle-exits-zone-triggered' else 'vehicle-enters-zone',
3401
+ 'timestamp': datetime.now(tz=None),
3402
+ 'zones': zones
3403
+ }
3404
+ }
3405
+ self._states.update(areaAlarm)
3406
+ if self.updateCallback:
3407
+ await self.updateCallback(2)
3408
+ elif type in ('vehicle-wake-up-succeeded', 'vehicle-wakeup-succeeded'):
3321
3409
  if self._requests.get('refresh', {}).get('id', None):
3322
3410
  openRequest= self._requests.get('refresh', {}).get('id', None)
3323
3411
  if openRequest == requestId:
@@ -3327,20 +3415,8 @@ class Vehicle:
3327
3415
  # 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
3416
  if self.updateCallback:
3329
3417
  await self.updateCallback(1)
3418
+ elif type in ('vehicle-area-alert-added', 'vehicle-area-alert-updated'):
3419
+ _LOGGER.info(f' Intentionally ignoring a notification of type \'{type}\')')
3330
3420
  else:
3331
3421
  _LOGGER.warning(f' Don\'t know what to do with a notification of type \'{type}\')')
3332
3422
 
3333
-
3334
- def storeFirebaseNotifications(self, obj, notification, data_message):
3335
- _LOGGER.debug(f'In storeFirebaseNotifications. notification={notification}')
3336
- fName = self._firebaseCredentialsFileName
3337
- fName = fName.replace('pycupra_firebase_credentials.json', 'pycupra_firebasenotifications.txt')
3338
-
3339
- with open(fName, "a") as ofile:
3340
- ofile.write(f'{datetime.now()}\n')
3341
- ofile.write(f' notification id={notification}, data_message={data_message}\n')
3342
- ofile.write(f' obj={obj}\n')
3343
- ofile.write("----------------------------------------------------------------\n")
3344
-
3345
-
3346
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.1.7
3
+ Version: 0.1.9
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=DKM773a_0KC1VMvD1yaWYuzJUpp3XsxR_HcWB1g_-Ys,207
3
- pycupra/connection.py,sha256=GWup_DcxnHNydb0DRJV79Hd108HJxMrGk89vi5R7meE,91150
4
- pycupra/const.py,sha256=F6y3qt_wS1QxhdSR-NHxB7lpUcf5uKLeoeTAJcAXgsE,10630
5
- pycupra/dashboard.py,sha256=xWggxTyKTBm3ByjRtymVzLJWtlIa52IEJsNx0z7bA9Q,44324
2
+ pycupra/__version__.py,sha256=dcxJ-u8Zz6qTGBXri5kGr0y8fa_8l43365hGXeOK-zU,207
3
+ pycupra/connection.py,sha256=Knu3vzokMRrsg9psjeD_r-2cl04ySJ1j7Mrn2g9mzdg,92941
4
+ pycupra/const.py,sha256=5b4uuNUE1AGZHmqQfIZv-76Ad20mJTXXQPGI8TU6FfY,10631
5
+ pycupra/dashboard.py,sha256=AlffEx8WmhLaSwX-eVcrOc54rUmwpzZUuR1fhruYkzI,45337
6
6
  pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
- pycupra/firebase.py,sha256=tuN_W3OX3h3-yfdprWzmCn6z_T-BMx-OpL7Z6hOA8Lc,3451
7
+ pycupra/firebase.py,sha256=lmI4a8f5VAlmHAqP2OiJWZhn7dmhyHkIBxL252qdtkA,3454
8
8
  pycupra/utilities.py,sha256=6sDxWP13-XtxmqhuBJBGdVbkj48BQ9AxFMrBPxH0J7g,2679
9
- pycupra/vehicle.py,sha256=jOtpn8WUxKBYPIuCuPMfmH19AVfY0Mn1GXtRJM_gosQ,157941
9
+ pycupra/vehicle.py,sha256=12pFVqgIRkPMJ9IoBt0TAuSS4RRQOdTCq4-ltNtbN9c,162376
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=7ADEYA6Aw4c42bwbIJr_I76wGiLOu99oV7PSNC4UcVs,30157
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.7.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
22
- pycupra-0.1.7.dist-info/METADATA,sha256=hQAjfqNAkgF72DsnoCrLL6NtkF5_iIWY8mgc4ssgsCo,3757
23
- pycupra-0.1.7.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
- pycupra-0.1.7.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
25
- pycupra-0.1.7.dist-info/RECORD,,
21
+ pycupra-0.1.9.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
22
+ pycupra-0.1.9.dist-info/METADATA,sha256=5LDPYAqO74WlAZanWVES__uzRngZlq2t_JS8cbABI9w,3757
23
+ pycupra-0.1.9.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
24
+ pycupra-0.1.9.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
25
+ pycupra-0.1.9.dist-info/RECORD,,