pycupra 0.0.15__py3-none-any.whl → 0.1.1__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/vehicle.py CHANGED
@@ -20,9 +20,15 @@ from .exceptions import (
20
20
  SeatRequestInProgressException
21
21
  )
22
22
  from .const import (
23
- APP_URI
23
+ APP_URI,
24
+ FIREBASE_STATUS_NOT_INITIALISED,
25
+ FIREBASE_STATUS_ACTIVATED,
26
+ FIREBASE_STATUS_ACTIVATION_FAILED,
27
+ FIREBASE_STATUS_NOT_WANTED,
24
28
  )
25
29
 
30
+ from .firebase import Firebase, readFCMCredsFile, writeFCMCredsFile
31
+
26
32
  _LOGGER = logging.getLogger(__name__)
27
33
 
28
34
  DATEZERO = datetime(1970,1,1)
@@ -41,6 +47,10 @@ class Vehicle:
41
47
  self._discovered = False
42
48
  self._dashboard = None
43
49
  self._states = {}
50
+ self._firebaseCredentialsFileName = None
51
+ self._firebaseLastMessageId = ''
52
+ self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
53
+ self.updateCallback = None
44
54
 
45
55
  self._requests = {
46
56
  'departuretimer': {'status': '', 'timestamp': DATEZERO},
@@ -75,6 +85,13 @@ class Vehicle:
75
85
  }
76
86
 
77
87
  self._last_full_update = datetime.now(tz=None) - timedelta(seconds=1200)
88
+ # Timestamps for the last API calls
89
+ self._last_get_statusreport = datetime.now(tz=None) - timedelta(seconds=600)
90
+ self._last_get_departure_timers = datetime.now(tz=None) - timedelta(seconds=600)
91
+ self._last_get_departure_profiles = datetime.now(tz=None) - timedelta(seconds=600)
92
+ self._last_get_charger = datetime.now(tz=None) - timedelta(seconds=600)
93
+ self._last_get_climater = datetime.now(tz=None) - timedelta(seconds=600)
94
+ self._last_get_mileage = datetime.now(tz=None) - timedelta(seconds=600)
78
95
 
79
96
 
80
97
  #### API get and set functions ####
@@ -114,7 +131,7 @@ class Vehicle:
114
131
 
115
132
  self._discovered = datetime.now()
116
133
 
117
- async def update(self, updateType=0):
134
+ async def update(self, updateType=0) -> bool:
118
135
  """Try to fetch data for all known API endpoints."""
119
136
  # Update vehicle information if not discovered or stale information
120
137
  if not self._discovered:
@@ -129,14 +146,25 @@ class Vehicle:
129
146
  # Fetch all data if car is not deactivated
130
147
  if not self.deactivated:
131
148
  try:
149
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
150
+ fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1700)
151
+ oldMileage = self.distance
152
+ if self._last_get_mileage < datetime.now(tz=None) - timedelta(seconds= 300):
153
+ await self.get_mileage()
154
+ if self.distance > oldMileage:
155
+ # self.distance has changed. So it's time for a full update
156
+ _LOGGER.debug(f'Mileage has changed. Old value: {oldMileage}, new value {self.distance}. This calls for a full update.')
157
+ updateType = 1
158
+ else:
159
+ fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1100)
160
+
132
161
  if self._connection._session_nightlyUpdateReduction:
133
162
  # nightlyUpdateReduction is activated
134
163
  if datetime.now(tz=None).hour<5 or datetime.now(tz=None).hour>=22:
135
164
  # current time is within the night interval
136
- fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1100)
137
165
  if hasattr(self, '_last_full_update'):
138
166
  _LOGGER.debug(f'last_full_update= {self._last_full_update}, fullUpdateExpired= {fullUpdateExpired}.')
139
- if updateType!=1 and (hasattr(self, '_last_full_update') and self._last_full_update>fullUpdateExpired):
167
+ if updateType<1 and (hasattr(self, '_last_full_update') and self._last_full_update>fullUpdateExpired):
140
168
  _LOGGER.debug('Nightly update reduction is active and current time within 22:00 and 5:00. So we skip small update.')
141
169
  return True
142
170
 
@@ -148,7 +176,6 @@ class Vehicle:
148
176
  return_exceptions=True
149
177
  )
150
178
 
151
- fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1100)
152
179
  if hasattr(self, '_last_full_update'):
153
180
  _LOGGER.debug(f'last_full_update= {self._last_full_update}, fullUpdateExpired= {fullUpdateExpired}.')
154
181
  if updateType!=1 and (hasattr(self, '_last_full_update') and self._last_full_update>fullUpdateExpired):
@@ -156,6 +183,9 @@ class Vehicle:
156
183
  return True
157
184
 
158
185
  # Data to be updated less often
186
+ if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
187
+ await self.get_mileage()
188
+
159
189
  await asyncio.gather(
160
190
  #self.get_statusreport(),
161
191
  self.get_charger(),
@@ -167,7 +197,6 @@ class Vehicle:
167
197
  self.get_vehicleHealthWarnings(),
168
198
  self.get_departure_timers(),
169
199
  self.get_departure_profiles(),
170
- self.get_mileage(),
171
200
  #self.get_modelimageurl(), #commented out, because getting the images discover() should be sufficient
172
201
  return_exceptions=True
173
202
  )
@@ -198,6 +227,7 @@ class Vehicle:
198
227
  data = await self._connection.getMileage(self.vin, self._apibase)
199
228
  if data:
200
229
  self._states.update(data)
230
+ self._last_get_mileage = datetime.now(tz=None)
201
231
 
202
232
  async def get_preheater(self):
203
233
  """Fetch pre-heater data if function is enabled."""
@@ -218,10 +248,11 @@ class Vehicle:
218
248
  data = await self._connection.getClimater(self.vin, self._apibase)
219
249
  if data:
220
250
  self._states.update(data)
251
+ self._last_get_climater = datetime.now(tz=None)
221
252
  else:
222
253
  _LOGGER.debug('Could not fetch climater data')
223
- else:
224
- self._requests.pop('climatisation', None)
254
+ #else:
255
+ # self._requests.pop('climatisation', None)
225
256
 
226
257
  async def get_trip_statistic(self):
227
258
  """Fetch trip data if function is enabled."""
@@ -264,6 +295,7 @@ class Vehicle:
264
295
  data = await self._connection.getVehicleStatusReport(self.vin, self._apibase)
265
296
  if data:
266
297
  self._states.update(data)
298
+ self._last_get_statusreport = datetime.now(tz=None)
267
299
  else:
268
300
  _LOGGER.debug('Could not fetch status report')
269
301
 
@@ -282,6 +314,7 @@ class Vehicle:
282
314
  data = await self._connection.getCharger(self.vin, self._apibase)
283
315
  if data:
284
316
  self._states.update(data)
317
+ self._last_get_charger = datetime.now(tz=None)
285
318
  else:
286
319
  _LOGGER.debug('Could not fetch charger data')
287
320
 
@@ -291,6 +324,7 @@ class Vehicle:
291
324
  data = await self._connection.getDeparturetimer(self.vin, self._apibase)
292
325
  if data:
293
326
  self._states.update(data)
327
+ self._last_get_departure_timers = datetime.now(tz=None)
294
328
  else:
295
329
  _LOGGER.debug('Could not fetch timers')
296
330
 
@@ -300,6 +334,7 @@ class Vehicle:
300
334
  data = await self._connection.getDepartureprofiles(self.vin, self._apibase)
301
335
  if data:
302
336
  self._states.update(data)
337
+ self._last_get_departure_profiles = datetime.now(tz=None)
303
338
  else:
304
339
  _LOGGER.debug('Could not fetch timers')
305
340
 
@@ -336,8 +371,10 @@ class Vehicle:
336
371
  data = {'maxChargeCurrentAc': int(value)}
337
372
  if int(value)==252:
338
373
  data = {'maxChargeCurrentAc': 'reduced'}
339
- if int(value)==254:
374
+ elif int(value)==254:
340
375
  data = {'maxChargeCurrentAc': 'maximum'}
376
+ else:
377
+ data = {'maxChargeCurrentAcInAmperes': int(value)}
341
378
  else:
342
379
  _LOGGER.error(f'Set charger maximum current to {value} is not supported.')
343
380
  raise SeatInvalidRequestException(f'Set charger maximum current to {value} is not supported.')
@@ -395,6 +432,10 @@ class Vehicle:
395
432
  'status': response.get('state', 'Unknown'),
396
433
  'id': response.get('id', 0)
397
434
  }
435
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
436
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
437
+ _LOGGER.debug('POST request for charger assumed successful. Waiting for push notification')
438
+ return True
398
439
  # Update the charger data and check, if they have changed as expected
399
440
  retry = 0
400
441
  actionSuccessful = False
@@ -409,7 +450,9 @@ class Vehicle:
409
450
  if not self.charging:
410
451
  actionSuccessful = True
411
452
  elif mode == 'settings':
412
- if data.get('maxChargeCurrentAc','') == self.charge_max_ampere:
453
+ if data.get('maxChargeCurrentAc','') == self.charge_max_ampere: # In case 'maximum', 'reduced'
454
+ actionSuccessful = True
455
+ if data.get('maxChargeCurrentAcInAmperes',0) == self.charge_max_ampere: # In case of a numerical value
413
456
  actionSuccessful = True
414
457
  else:
415
458
  _LOGGER.error(f'Missing code in vehicle._set_charger() for mode {mode}')
@@ -641,6 +684,10 @@ class Vehicle:
641
684
  'status': response.get('state', 'Unknown'),
642
685
  'id': response.get('id', 0),
643
686
  }
687
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
688
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
689
+ _LOGGER.debug('POST request for change of departure timers assumed successful. Waiting for push notification')
690
+ return True
644
691
  # Update the departure timers data and check, if they have changed as expected
645
692
  retry = 0
646
693
  actionSuccessful = False
@@ -828,6 +875,10 @@ class Vehicle:
828
875
  'status': response.get('state', 'Unknown'),
829
876
  'id': response.get('id', 0),
830
877
  }
878
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
879
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
880
+ _LOGGER.debug('POST request for change of departure profiles assumed successful. Waiting for push notification')
881
+ return True
831
882
  # Update the departure profile data and check, if they have changed as expected
832
883
  retry = 0
833
884
  actionSuccessful = False
@@ -1006,6 +1057,10 @@ class Vehicle:
1006
1057
  'status': response.get('state', 'Unknown'),
1007
1058
  'id': response.get('id', 0),
1008
1059
  }
1060
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
1061
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
1062
+ _LOGGER.debug('POST request for climater assumed successful. Waiting for push notification')
1063
+ return True
1009
1064
  # Update the climater data and check, if they have changed as expected
1010
1065
  retry = 0
1011
1066
  actionSuccessful = False
@@ -1119,6 +1174,10 @@ class Vehicle:
1119
1174
  'status': response.get('state', 'Unknown'),
1120
1175
  'id': response.get('id', 0),
1121
1176
  }
1177
+ # if firebaseStatus is FIREBASE_STATUS_ACTIVATED, the request is assumed successful. Waiting for push notification before rereading status
1178
+ if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
1179
+ _LOGGER.debug('POST request for lock/unlock assumed successful. Waiting for push notification')
1180
+ return True
1122
1181
  # Update the lock data and check, if they have changed as expected
1123
1182
  retry = 0
1124
1183
  actionSuccessful = False
@@ -1552,7 +1611,10 @@ class Vehicle:
1552
1611
  def charge_max_ampere(self):
1553
1612
  """Return charger max ampere setting."""
1554
1613
  if self.attrs.get('charging', False):
1555
- return self.attrs.get('charging').get('info').get('settings').get('maxChargeCurrentAc')
1614
+ if self.attrs.get('charging',{}).get('info',{}).get('settings',{}).get('maxChargeCurrentAcInAmperes', None):
1615
+ return self.attrs.get('charging',{}).get('info',{}).get('settings',{}).get('maxChargeCurrentAcInAmperes', 0)
1616
+ else:
1617
+ return self.attrs.get('charging').get('info').get('settings').get('maxChargeCurrentAc')
1556
1618
  return 0
1557
1619
 
1558
1620
  @property
@@ -1730,6 +1792,17 @@ class Vehicle:
1730
1792
  if self.is_charging_state_supported:
1731
1793
  return True
1732
1794
 
1795
+ @property
1796
+ def target_soc(self):
1797
+ """Return the target soc."""
1798
+ return self.attrs.get('charging', {}).get('info', {}).get('settings', {}).get('targetSoc', 0)
1799
+
1800
+ @property
1801
+ def is_target_soc_supported(self):
1802
+ """Target state of charge supported."""
1803
+ if self.attrs.get('charging', {}).get('info', {}).get('settings', {}).get('targetSoc', False):
1804
+ return True
1805
+
1733
1806
  # Vehicle location states
1734
1807
  @property
1735
1808
  def position(self):
@@ -2956,7 +3029,7 @@ class Vehicle:
2956
3029
  @property
2957
3030
  def is_request_in_progress_supported(self):
2958
3031
  """Request in progress is always supported."""
2959
- return True
3032
+ return False
2960
3033
 
2961
3034
  @property
2962
3035
  def request_results(self):
@@ -2966,7 +3039,7 @@ class Vehicle:
2966
3039
  'state': self._requests.get('state', 'N/A'),
2967
3040
  }
2968
3041
  for section in self._requests:
2969
- if section in ['departuretimer', 'batterycharge', 'climatisation', 'refresh', 'lock', 'preheater']:
3042
+ if section in ['departuretimer', 'departureprofiles', 'batterycharge', 'climatisation', 'refresh', 'lock', 'preheater']:
2970
3043
  timestamp = self._requests.get(section, {}).get('timestamp', DATEZERO)
2971
3044
  data[section] = self._requests[section].get('status', 'N/A')
2972
3045
  data[section+'_timestamp'] = timestamp.strftime("%Y-%m-%d %H:%M:%S")
@@ -3011,3 +3084,174 @@ class Vehicle:
3011
3084
  indent=4,
3012
3085
  default=serialize
3013
3086
  )
3087
+
3088
+
3089
+ async def initialiseFirebase(self, firebaseCredentialsFileName=None, updateCallback=None):
3090
+ # Check if firebase shall be used
3091
+ if firebaseCredentialsFileName == None:
3092
+ _LOGGER.debug('No use of firebase wanted.')
3093
+ self.firebaseStatus = FIREBASE_STATUS_NOT_WANTED
3094
+ return self.firebaseStatus
3095
+ self._firebaseCredentialsFileName = firebaseCredentialsFileName
3096
+
3097
+ # Check if firebase not already initialised
3098
+ if self.firebaseStatus!= FIREBASE_STATUS_NOT_INITIALISED:
3099
+ _LOGGER.debug(f'No need to initialise firebase anymore. Firebase status={self.firebaseStatus}')
3100
+ return self.firebaseStatus
3101
+
3102
+ # Read the firebase credentials file and check if an existing subscription has to be deleted
3103
+ loop = asyncio.get_running_loop()
3104
+ credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
3105
+ subscribedVin = credentials.get('subscription',{}).get('vin','')
3106
+ subscribedUserId = credentials.get('subscription',{}).get('userId','')
3107
+ subscribedBrand = credentials.get('subscription',{}).get('brand','')
3108
+ if subscribedVin != '' and subscribedUserId != '':
3109
+ 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}.')
3111
+ result = await self._connection.deleteSubscription(credentials)
3112
+
3113
+ # Start firebase
3114
+ fb = Firebase()
3115
+ success = await fb.firebaseStart(self.onNotification, firebaseCredentialsFileName, brand=self._connection._session_auth_brand)
3116
+ if not success:
3117
+ self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_FAILED
3118
+ _LOGGER.warning('Activation of firebase messaging failed.')
3119
+ return self.firebaseStatus
3120
+
3121
+ self.updateCallback = updateCallback
3122
+ # Read possibly new credentials and subscribe vin and userId for push notifications
3123
+ loop = asyncio.get_running_loop()
3124
+ credentials = await loop.run_in_executor(None, readFCMCredsFile, firebaseCredentialsFileName)
3125
+ result = await self._connection.subscribe(self.vin, credentials)
3126
+ _LOGGER.debug(f'Result of subscription={result}.')
3127
+ credentials['subscription']= {
3128
+ 'vin' : self.vin,
3129
+ 'userId' : self._connection._user_id,
3130
+ 'brand' : self._connection._session_auth_brand,
3131
+ 'id' : result.get('id', ''),
3132
+ }
3133
+ loop = asyncio.get_running_loop()
3134
+ await loop.run_in_executor(None, writeFCMCredsFile, credentials, firebaseCredentialsFileName)
3135
+
3136
+ await asyncio.sleep(5) # Wait to let the first notifications
3137
+ self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
3138
+ _LOGGER.info('Activation of firebase messaging was successful.')
3139
+ return self.firebaseStatus
3140
+
3141
+
3142
+
3143
+ async def onNotification(self, obj, notification, data_message):
3144
+ # Do something with the notification
3145
+ _LOGGER.debug(f'Received push notification: notification id={notification}, type={obj.get('data',{}).get('type','')}, requestId={obj.get('data',{}).get('requestId','[None]')}')
3146
+ _LOGGER.debug(f' data_message={data_message}, payload={obj.get('data',{}).get('payload','[None]')}')
3147
+
3148
+ #temporary output of notifications in a file
3149
+ if self.updateCallback == self.update:
3150
+ self.storeFirebaseNotifications(obj, notification, data_message)
3151
+
3152
+ if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
3153
+ _LOGGER.info(f'While firebase is not fully activated, received notifications are just acknoledged.')
3154
+ # As long as the firebase status is not set to activated, ignore the notifications
3155
+ return False
3156
+
3157
+ type = obj.get('data',{}).get('type','')
3158
+ requestId = obj.get('data',{}).get('requestId','')
3159
+ openRequest = -1
3160
+ if requestId != '':
3161
+ _LOGGER.info(f'Received notification of type \'{type}\', request id={requestId} ')
3162
+ else:
3163
+ _LOGGER.info(f'Received notification of type \'{type}\' ')
3164
+
3165
+ if notification == self._firebaseLastMessageId:
3166
+ _LOGGER.info(f'Received notification {notification} again. Just acknoledging it, nothing to do.')
3167
+ return False
3168
+
3169
+ self._firebaseLastMessageId = notification # save the id of the last notification
3170
+ if type in ('vehicle-access-locked-successful', 'vehicle-access-unlocked-successful'): # vehicle was locked/unlocked
3171
+ if self._requests.get('lock', {}).get('id', None):
3172
+ openRequest= self._requests.get('lock', {}).get('id', None)
3173
+ if openRequest == requestId:
3174
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3175
+ self._requests.get('lock', {}).pop('id')
3176
+ if (self._last_get_statusreport < datetime.now(tz=None) - timedelta(seconds= 10)) or openRequest == requestId:
3177
+ # Update the status report only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3178
+ #await self.get_statusreport() # Call not needed because it's part of updateCallback(2)
3179
+ if self.updateCallback:
3180
+ await self.updateCallback(2)
3181
+ elif type == 'departure-times-updated':
3182
+ if self._requests.get('departuretimer', {}).get('id', None):
3183
+ openRequest= self._requests.get('departuretimer', {}).get('id', None)
3184
+ if openRequest == requestId:
3185
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3186
+ self._requests.get('departuretimer', {}).pop('id')
3187
+ if (self._last_get_departure_timers < datetime.now(tz=None) - timedelta(seconds= 30)) or openRequest == requestId:
3188
+ # Update the departure timers only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3189
+ await self.get_departure_timers()
3190
+ if self.updateCallback:
3191
+ await self.updateCallback(2)
3192
+ elif type == 'departure-profiles-updated': # !!! Is this the right type?
3193
+ if self._requests.get('departureprofile', {}).get('id', None):
3194
+ openRequest= self._requests.get('departureprofile', {}).get('id', None)
3195
+ if openRequest == requestId:
3196
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3197
+ self._requests.get('departureprofile', {}).pop('id')
3198
+ if (self._last_get_departure_profiles < datetime.now(tz=None) - timedelta(seconds= 30)) or openRequest == requestId:
3199
+ # Update the departure profiles only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3200
+ await self.get_departure_profiles()
3201
+ if self.updateCallback:
3202
+ await self.updateCallback(2)
3203
+ elif type in ('charging-status-changed', 'charging-started', 'charging-stopped'):
3204
+ if self._requests.get('batterycharge', {}).get('id', None):
3205
+ openRequest= self._requests.get('batterycharge', {}).get('id', None)
3206
+ if openRequest == requestId:
3207
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3208
+ self._requests.get('batterycharge', {}).pop('id')
3209
+ if (self._last_get_charger < datetime.now(tz=None) - timedelta(seconds= 10)) or openRequest == requestId:
3210
+ # Update the charging data only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3211
+ await self.get_charger()
3212
+ if self.updateCallback:
3213
+ await self.updateCallback(2)
3214
+ else:
3215
+ _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'):
3217
+ if self._requests.get('climatisation', {}).get('id', None):
3218
+ openRequest= self._requests.get('climatisation', {}).get('id', None)
3219
+ if openRequest == requestId:
3220
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3221
+ self._requests.get('climatisation', {}).pop('id')
3222
+ if (self._last_get_climater < datetime.now(tz=None) - timedelta(seconds= 10)) or openRequest == requestId:
3223
+ # Update the climatisation data only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3224
+ await self.get_climater()
3225
+ if self.updateCallback:
3226
+ await self.updateCallback(2)
3227
+ else:
3228
+ _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_climater was at {self._last_get_climater}. So no need to update.')
3229
+ elif type == 'vehicle-wakeup-succeeded':
3230
+ if self._requests.get('refresh', {}).get('id', None):
3231
+ openRequest= self._requests.get('refresh', {}).get('id', None)
3232
+ if openRequest == requestId:
3233
+ _LOGGER.debug(f'The notification closes an open request initiated by PyCupra.')
3234
+ self._requests.get('refresh', {}).pop('id')
3235
+ if (self._last_full_update < datetime.now(tz=None) - timedelta(seconds= 30)) or openRequest == requestId:
3236
+ # Do full update only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
3237
+ if self.updateCallback:
3238
+ await self.updateCallback(1)
3239
+ else:
3240
+ _LOGGER.warning(f' Don\'t know what to do with a notification of type \'{type}\')')
3241
+
3242
+
3243
+
3244
+
3245
+ def storeFirebaseNotifications(self, obj, notification, data_message):
3246
+ _LOGGER.debug(f'In storeFirebaseNotifications. notification={notification}')
3247
+ fName = self._firebaseCredentialsFileName
3248
+ fName = fName.replace('pycupra_firebase_credentials.json', 'pycupra_firebasenotifications.txt')
3249
+
3250
+ with open(fName, "a") as ofile:
3251
+ ofile.write(f'{datetime.now()}\n')
3252
+ ofile.write(f' notification id={notification}, data_message={data_message}\n')
3253
+ ofile.write(f' obj={obj}\n')
3254
+ ofile.write("----------------------------------------------------------------\n")
3255
+
3256
+
3257
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.0.15
3
+ Version: 0.1.1
4
4
  Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
5
5
  Home-page: https://github.com/WulfgarW/pycupra
6
6
  Author: WulfgarW
@@ -39,11 +39,21 @@ No licence, public domain, no guarantees, feel free to use for anything. Please
39
39
 
40
40
  ## Breaking changes
41
41
 
42
+ - The method vehicle.update(updateType) supports 3 different update types:
43
+ - updateType=0: Small update (=only get_basiccardata() and get_statusreport are called). If the last full update is more than 1100 seconds ago, then a full update is performed.
44
+ - updateType=1: Full update (nearly all get-methods are called. The model images and the capabilitites are refreshed only every 2 hours.)
45
+ - updateType=2: Like updateType=0, but ignoring the nightly update reduction
46
+
47
+ - Nightly update reduction: If nightly reduction is activated and the current time is within the time frame between 22:00 and 05:00, then vehicle.update(0) performs a full update, if the last full update is more than 1700 seconds ago. If that's not the case, vehicle.update(0) does nothing.
48
+
49
+ - PyCupra can ask the Seat/Cupra portal to send push notifications to PyCupra if the charging status or the climatisation status has changed or when the API has finished a request like lock or unlock vehicle, start or stop charging or change departure timers or ....
50
+
42
51
  ## Thanks to
43
52
 
44
53
  - [RobinostLund](https://github.com/robinostlund/volkswagencarnet) for initial project for Volkswagen Carnet I was able to fork
45
54
  - [Farfar](https://github.com/Farfar) for modifications related to electric engines
46
55
  - [tanelvakker](https://github.com/tanelvakker) for modifications related to correct SPIN handling for various actions and using correct URLs also for MY2021
56
+ - [sdb9696](https://github.com/sdb9696) for the firebase-messaging package that is used in PyCupra with only minor modifications
47
57
 
48
58
  ### Example
49
59
 
@@ -52,6 +62,6 @@ When logged in the library will automatically create a vehicle object for every
52
62
  Method get_vehicles will fetch vehicle basic information and create Vehicle class objects for all associated vehicles in account.
53
63
  To update all available data use the update_all method of the Connect class. This will call the update function for all registered vehicles, which in turn will fetch data from all available API endpoints.
54
64
 
55
- The file *cupra_credentials.json.demo* explains the data structure of the credentials file.
65
+ The file *pycupra_credentials.json.demo* explains the data structure of the credentials file.
56
66
 
57
67
 
@@ -0,0 +1,19 @@
1
+ pycupra/__init__.py,sha256=p0880jPkLqOErX3u3qaLboBLOsEHFpe44axApdaGeqI,231
2
+ pycupra/__version__.py,sha256=zlzRw8I9vnBzTZvrypDu4BTmAAVACxH_TDevfUOUGxE,207
3
+ pycupra/connection.py,sha256=r02CHAhRrZwu90AIsfTnzAhRn9dZjK8WuZvhUa6_DcA,85314
4
+ pycupra/const.py,sha256=cJJ9xrof6HZ7ZE7nXnB6tU4qMbSadlN8mgs43Igy7Mo,10591
5
+ pycupra/dashboard.py,sha256=hFHhCDrCNufQR1I85AlR4kPgSlFSinCegZ0w8F4FvUk,43703
6
+ pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
+ pycupra/firebase.py,sha256=V3Ico6FZzEn0-5-CnqaDP9Mg9LpVU-_qLyZQwiRBbD0,2725
8
+ pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
9
+ pycupra/vehicle.py,sha256=armeMQHxOZyyQJ9Iu7G26pq_BNc8oahPFxOe7daEip8,151994
10
+ pycupra/firebase_messaging/__init__.py,sha256=Gwe6NPn4_DTJdZ8rZMNJjg3pDkz2ZURjOYJDnGoOYnk,476
11
+ pycupra/firebase_messaging/const.py,sha256=XMy8kJ37uBSkTpVpdLeSjxk5UIPuvDuo-rxYdgmo2G8,1191
12
+ pycupra/firebase_messaging/fcmpushclient.py,sha256=FGttEKCnOlnBWkFiVSo-xkl7RAc7Dgi25lY3lEvMAa8,29549
13
+ pycupra/firebase_messaging/fcmregister.py,sha256=lDgk0TZjW2dvF6ogEKc45rHT30iv8DxHzB9MlekZtO4,18103
14
+ pycupra/firebase_messaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ pycupra-0.1.1.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
16
+ pycupra-0.1.1.dist-info/METADATA,sha256=i6n3a43MUJwm1qGMmeF2tMTVNqQJtj4FJ3KDnghDDqA,3757
17
+ pycupra-0.1.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
18
+ pycupra-0.1.1.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
19
+ pycupra-0.1.1.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- pycupra/__init__.py,sha256=VPzUfKd5mBFD1UERNV61FbGHih5dQPupLgIfYtmIUi4,230
2
- pycupra/__version__.py,sha256=TaiHHyrm9YZkkLYWZtVPPNSF68UvwKMIg0DY3rZCtfQ,208
3
- pycupra/connection.py,sha256=0SA-2UcL_uJhKMfluzyw_N4ZCqmsNEg3jU18XacMm5w,82641
4
- pycupra/const.py,sha256=mgl29DcZz_J5hSzxknteu0ocDOXmQAgP0x17kvVSSi0,10234
5
- pycupra/dashboard.py,sha256=GNMBVJx-iF2vtmOpCJQBcyVBsKBxa049KfZDuQaimJs,43497
6
- pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
- pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
8
- pycupra/vehicle.py,sha256=dTAFaANVzXp05d3kMd8bgDmzzD-kD8Rdywdi96aG63c,135973
9
- pycupra-0.0.15.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
10
- pycupra-0.0.15.dist-info/METADATA,sha256=-gVU6f2phktNe1sqg4nD-h-x-iXY3z-ZdjsSz6X5oBU,2579
11
- pycupra-0.0.15.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
12
- pycupra-0.0.15.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
13
- pycupra-0.0.15.dist-info/RECORD,,