pycupra 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pycupra/__version__.py 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.0.5"
6
+ __version__ = "0.0.6"
pycupra/connection.py CHANGED
@@ -79,6 +79,7 @@ from .const import (
79
79
  API_CLIMATER_STATUS,
80
80
  API_CLIMATER,
81
81
  API_DEPARTURE_TIMERS,
82
+ API_DEPARTURE_PROFILES,
82
83
  API_MILEAGE,
83
84
  API_CAPABILITIES,
84
85
  #API_CAPABILITIES_MANAGEMENT,
@@ -623,6 +624,8 @@ class Connection:
623
624
  try:
624
625
  if response.status == 204:
625
626
  res = {'status_code': response.status}
627
+ elif response.status == 202 and method==METH_PUT:
628
+ res = response
626
629
  elif response.status >= 200 or response.status <= 300:
627
630
  # If this is a revoke token url, expect Content-Length 0 and return
628
631
  if int(response.headers.get('Content-Length', 0)) == 0 and 'revoke' in url:
@@ -881,16 +884,6 @@ class Connection:
881
884
  _LOGGER.info('Unhandled error while trying to fetch mycar data')
882
885
  except Exception as error:
883
886
  _LOGGER.warning(f'Could not fetch mycar report, error: {error}')
884
- try:
885
- response = await self.get(eval(f"f'{API_WARNINGLIGHTS}'"))
886
- if 'statuses' in response:
887
- data['warninglights'] = response
888
- elif response.get('status_code', {}):
889
- _LOGGER.warning(f'Could not fetch warnlights, HTTP status code: {response.get("status_code")}')
890
- else:
891
- _LOGGER.info('Unhandled error while trying to fetch warnlights')
892
- except Exception as error:
893
- _LOGGER.warning(f'Could not fetch warnlights, error: {error}')
894
887
  try:
895
888
  response = await self.get(eval(f"f'{API_MILEAGE}'"))
896
889
  if response.get('mileageKm', {}):
@@ -905,6 +898,24 @@ class Connection:
905
898
  return False
906
899
  return data
907
900
 
901
+ async def getVehicleHealthWarnings(self, vin, baseurl):
902
+ """Get car information from customer profile, VIN, nickname, etc."""
903
+ await self.set_token(self._session_auth_brand)
904
+ data={}
905
+ try:
906
+ response = await self.get(eval(f"f'{API_WARNINGLIGHTS}'"))
907
+ if 'statuses' in response:
908
+ data['warninglights'] = response
909
+ elif response.get('status_code', {}):
910
+ _LOGGER.warning(f'Could not fetch warnlights, HTTP status code: {response.get("status_code")}')
911
+ else:
912
+ _LOGGER.info('Unhandled error while trying to fetch warnlights')
913
+ except Exception as error:
914
+ _LOGGER.warning(f'Could not fetch warnlights, error: {error}')
915
+ if data=={}:
916
+ return False
917
+ return data
918
+
908
919
  #async def getOperationList(self, vin, baseurl):
909
920
  """Collect operationlist for VIN, supported/licensed functions."""
910
921
  """try:
@@ -1009,19 +1020,22 @@ class Connection:
1009
1020
  return False
1010
1021
  return data
1011
1022
 
1012
- async def getTripStatistics(self, vin, baseurl):
1023
+ async def getTripStatistics(self, vin, baseurl, supportsCyclicTrips):
1013
1024
  """Get short term and cyclic trip statistics."""
1014
1025
  await self.set_token(self._session_auth_brand)
1015
1026
  try:
1016
1027
  data={'tripstatistics': {}}
1017
- dataType='CYCLIC'
1018
- response = await self.get(eval(f"f'{API_TRIP}'"))
1019
- if response.get('data', []):
1020
- data['tripstatistics']['cyclic']= response.get('data', [])
1021
- elif response.get('status_code', {}):
1022
- _LOGGER.warning(f'Could not fetch trip statistics, HTTP status code: {response.get("status_code")}')
1028
+ if supportsCyclicTrips:
1029
+ dataType='CYCLIC'
1030
+ response = await self.get(eval(f"f'{API_TRIP}'"))
1031
+ if response.get('data', []):
1032
+ data['tripstatistics']['cyclic']= response.get('data', [])
1033
+ elif response.get('status_code', {}):
1034
+ _LOGGER.warning(f'Could not fetch trip statistics, HTTP status code: {response.get("status_code")}')
1035
+ else:
1036
+ _LOGGER.info(f'Unhandled error while trying to fetch trip statistics')
1023
1037
  else:
1024
- _LOGGER.info(f'Unhandled error while trying to fetch trip statistics')
1038
+ _LOGGER.info(f'Vehicle does not support cyclic trips.')
1025
1039
  dataType='SHORT'
1026
1040
  response = await self.get(eval(f"f'{API_TRIP}'"))
1027
1041
  if response.get('data', []):
@@ -1081,11 +1095,33 @@ class Connection:
1081
1095
  data['departureTimers'] = response
1082
1096
  return data
1083
1097
  elif response.get('status_code', {}):
1084
- _LOGGER.warning(f'Could not fetch timers, HTTP status code: {response.get("status_code")}')
1098
+ _LOGGER.warning(f'Could not fetch departure timers, HTTP status code: {response.get("status_code")}')
1085
1099
  else:
1086
1100
  _LOGGER.info('Unknown error while trying to fetch data for departure timers')
1087
1101
  except Exception as error:
1088
- _LOGGER.warning(f'Could not fetch timers, error: {error}')
1102
+ _LOGGER.warning(f'Could not fetch departure timers, error: {error}')
1103
+ return False
1104
+
1105
+ async def getDepartureprofiles(self, vin, baseurl):
1106
+ """Get departure timers."""
1107
+ await self.set_token(self._session_auth_brand)
1108
+ try:
1109
+ response = await self.get(eval(f"f'{API_DEPARTURE_PROFILES}'"))
1110
+ if response.get('timers', {}):
1111
+ for e in range(len(response.get('timers', []))):
1112
+ if response['timers'][e].get('singleTimer','')==None:
1113
+ response['timers'][e].pop('singleTimer')
1114
+ if response['timers'][e].get('recurringTimer','')==None:
1115
+ response['timers'][e].pop('recurringTimer')
1116
+ data={}
1117
+ data['departureProfiles'] = response
1118
+ return data
1119
+ elif response.get('status_code', {}):
1120
+ _LOGGER.warning(f'Could not fetch departure profiles, HTTP status code: {response.get("status_code")}')
1121
+ else:
1122
+ _LOGGER.info('Unknown error while trying to fetch data for departure profiles')
1123
+ except Exception as error:
1124
+ _LOGGER.warning(f'Could not fetch departure profiles, error: {error}')
1089
1125
  return False
1090
1126
 
1091
1127
  async def getClimater(self, vin, baseurl):
@@ -1271,6 +1307,39 @@ class Connection:
1271
1307
  raise
1272
1308
  return False
1273
1309
 
1310
+ async def _setViaPUTtoAPI(self, endpoint, **data):
1311
+ """PUT call to API to set a value or to start an action."""
1312
+ await self.set_token(self._session_auth_brand)
1313
+ try:
1314
+ url = endpoint
1315
+ response = await self._request(METH_PUT,url, **data)
1316
+ if not response:
1317
+ raise SeatException(f'Invalid or no response for endpoint {endpoint}')
1318
+ elif response == 429:
1319
+ raise SeatThrottledException('Action rate limit reached. Start the car to reset the action limit')
1320
+ else:
1321
+ data = {'id': '', 'state' : ''}
1322
+ if 'requestId' in response:
1323
+ data['state'] = 'Request accepted'
1324
+ for key in response:
1325
+ if isinstance(response.get(key), dict):
1326
+ for k in response.get(key):
1327
+ if 'id' in k.lower():
1328
+ data['id'] = str(response.get(key).get(k))
1329
+ if 'state' in k.lower():
1330
+ data['state'] = response.get(key).get(k)
1331
+ else:
1332
+ if 'Id' in key:
1333
+ data['id'] = str(response.get(key))
1334
+ if 'State' in key:
1335
+ data['state'] = response.get(key)
1336
+ if response.get('rate_limit_remaining', False):
1337
+ data['rate_limit_remaining'] = response.get('rate_limit_remaining', None)
1338
+ return data
1339
+ except:
1340
+ raise
1341
+ return False
1342
+
1274
1343
  async def setCharger(self, vin, baseurl, mode, data):
1275
1344
  """Start/Stop charger."""
1276
1345
  if mode in {'start', 'stop'}:
@@ -1321,21 +1390,25 @@ class Connection:
1321
1390
  raise
1322
1391
  return False
1323
1392
 
1393
+ async def setDepartureprofile(self, vin, baseurl, data, spin):
1394
+ """Set departure profiles."""
1395
+ try:
1396
+ url= eval(f"f'{API_DEPARTURE_PROFILES}'")
1397
+ #if data:
1398
+ #if data.get('minSocPercentage',False):
1399
+ # url=url+'/settings'
1400
+ return await self._setViaPUTtoAPI(url, json = data)
1401
+ except:
1402
+ raise
1403
+ return False
1404
+
1324
1405
  async def sendDestination(self, vin, baseurl, data, spin):
1325
1406
  """Send destination to vehicle."""
1326
1407
 
1327
1408
  await self.set_token(self._session_auth_brand)
1328
1409
  try:
1329
1410
  url= eval(f"f'{API_DESTINATION}'")
1330
- response = await self._session.request(
1331
- METH_PUT,
1332
- url,
1333
- headers=self._session_headers,
1334
- timeout=ClientTimeout(total=TIMEOUT.seconds),
1335
- cookies=self._session_cookies,
1336
- raise_for_status=False,
1337
- json=data
1338
- )
1411
+ response = await self._request(METH_PUT, url, json=data)
1339
1412
  if response.status==202: #[202 Accepted]
1340
1413
  _LOGGER.debug(f'Destination {data[0]} successfully sent to API.')
1341
1414
  return response
pycupra/const.py CHANGED
@@ -133,6 +133,7 @@ API_CHARGING = '{baseurl}/v1/vehicles/{vin}/charging'
133
133
  API_CLIMATER_STATUS = '{baseurl}/v1/vehicles/{vin}/climatisation/status' # Climatisation data
134
134
  API_CLIMATER = '{baseurl}/v2/vehicles/{vin}/climatisation' # Climatisation data
135
135
  API_DEPARTURE_TIMERS = '{baseurl}/v1/vehicles/{vin}/departure-timers' # Departure timers
136
+ API_DEPARTURE_PROFILES = '{baseurl}/v1/vehicles/{vin}/departure/profiles' # Departure profiles
136
137
  API_POSITION = '{baseurl}/v1/vehicles/{vin}/parkingposition' # Position data
137
138
  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'
138
139
  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)
pycupra/dashboard.py CHANGED
@@ -402,7 +402,7 @@ class RequestHonkAndFlash(Switch):
402
402
 
403
403
  async def turn_on(self):
404
404
  await self.vehicle.set_honkandflash('honkandflash')
405
- await self.vehicle.update()
405
+ #await self.vehicle.update()
406
406
  if self.callback is not None:
407
407
  self.callback()
408
408
 
@@ -428,7 +428,7 @@ class RequestFlash(Switch):
428
428
 
429
429
  async def turn_on(self):
430
430
  await self.vehicle.set_honkandflash('flash')
431
- await self.vehicle.update()
431
+ #await self.vehicle.update()
432
432
  if self.callback is not None:
433
433
  self.callback()
434
434
 
@@ -480,11 +480,11 @@ class ElectricClimatisation(Switch):
480
480
 
481
481
  async def turn_on(self):
482
482
  await self.vehicle.set_climatisation(mode = 'electric')
483
- await self.vehicle.update()
483
+ #await self.vehicle.update()
484
484
 
485
485
  async def turn_off(self):
486
486
  await self.vehicle.set_climatisation(mode = 'off')
487
- await self.vehicle.update()
487
+ #await self.vehicle.update()
488
488
 
489
489
  @property
490
490
  def assumed_state(self):
@@ -514,11 +514,11 @@ class AuxiliaryClimatisation(Switch):
514
514
 
515
515
  async def turn_on(self):
516
516
  await self.vehicle.set_climatisation(mode = 'auxiliary', spin = self.spin)
517
- await self.vehicle.update()
517
+ #await self.vehicle.update()
518
518
 
519
519
  async def turn_off(self):
520
520
  await self.vehicle.set_climatisation(mode = 'off')
521
- await self.vehicle.update()
521
+ #await self.vehicle.update()
522
522
 
523
523
  @property
524
524
  def assumed_state(self):
@@ -539,11 +539,11 @@ class Charging(Switch):
539
539
 
540
540
  async def turn_on(self):
541
541
  await self.vehicle.set_charger('start')
542
- await self.vehicle.update()
542
+ #await self.vehicle.update()
543
543
 
544
544
  async def turn_off(self):
545
545
  await self.vehicle.set_charger('stop')
546
- await self.vehicle.update()
546
+ #await self.vehicle.update()
547
547
 
548
548
  @property
549
549
  def assumed_state(self):
@@ -564,11 +564,11 @@ class WindowHeater(Switch):
564
564
 
565
565
  async def turn_on(self):
566
566
  await self.vehicle.set_window_heating('start')
567
- await self.vehicle.update()
567
+ #await self.vehicle.update()
568
568
 
569
569
  async def turn_off(self):
570
570
  await self.vehicle.set_window_heating('stop')
571
- await self.vehicle.update()
571
+ #await self.vehicle.update()
572
572
 
573
573
  @property
574
574
  def assumed_state(self):
@@ -617,11 +617,11 @@ class BatteryClimatisation(Switch):
617
617
 
618
618
  async def turn_on(self):
619
619
  await self.vehicle.set_battery_climatisation(True)
620
- await self.vehicle.update()
620
+ #await self.vehicle.update()
621
621
 
622
622
  async def turn_off(self):
623
623
  await self.vehicle.set_battery_climatisation(False)
624
- await self.vehicle.update()
624
+ #await self.vehicle.update()
625
625
 
626
626
  @property
627
627
  def assumed_state(self):
@@ -646,11 +646,11 @@ class PHeaterHeating(Switch):
646
646
 
647
647
  async def turn_on(self):
648
648
  await self.vehicle.set_pheater(mode='heating', spin=self.spin)
649
- await self.vehicle.update()
649
+ #await self.vehicle.update()
650
650
 
651
651
  async def turn_off(self):
652
652
  await self.vehicle.set_pheater(mode='off', spin=self.spin)
653
- await self.vehicle.update()
653
+ #await self.vehicle.update()
654
654
 
655
655
  @property
656
656
  def assumed_state(self):
@@ -675,11 +675,11 @@ class PHeaterVentilation(Switch):
675
675
 
676
676
  async def turn_on(self):
677
677
  await self.vehicle.set_pheater(mode='ventilation', spin=self.spin)
678
- await self.vehicle.update()
678
+ #await self.vehicle.update()
679
679
 
680
680
  async def turn_off(self):
681
681
  await self.vehicle.set_pheater(mode='off', spin=self.spin)
682
- await self.vehicle.update()
682
+ #await self.vehicle.update()
683
683
 
684
684
  @property
685
685
  def assumed_state(self):
@@ -719,11 +719,11 @@ class DepartureTimer1(Switch):
719
719
 
720
720
  async def turn_on(self):
721
721
  await self.vehicle.set_timer_active(id=1, action="on")
722
- await self.vehicle.update()
722
+ #await self.vehicle.update()
723
723
 
724
724
  async def turn_off(self):
725
725
  await self.vehicle.set_timer_active(id=1, action="off")
726
- await self.vehicle.update()
726
+ #await self.vehicle.update()
727
727
 
728
728
  @property
729
729
  def assumed_state(self):
@@ -751,11 +751,11 @@ class DepartureTimer2(Switch):
751
751
 
752
752
  async def turn_on(self):
753
753
  await self.vehicle.set_timer_active(id=2, action="on")
754
- await self.vehicle.update()
754
+ #await self.vehicle.update()
755
755
 
756
756
  async def turn_off(self):
757
757
  await self.vehicle.set_timer_active(id=2, action="off")
758
- await self.vehicle.update()
758
+ #await self.vehicle.update()
759
759
 
760
760
  @property
761
761
  def assumed_state(self):
@@ -782,11 +782,11 @@ class DepartureTimer3(Switch):
782
782
 
783
783
  async def turn_on(self):
784
784
  await self.vehicle.set_timer_active(id=3, action="on")
785
- await self.vehicle.update()
785
+ #await self.vehicle.update()
786
786
 
787
787
  async def turn_off(self):
788
788
  await self.vehicle.set_timer_active(id=3, action="off")
789
- await self.vehicle.update()
789
+ #await self.vehicle.update()
790
790
 
791
791
  @property
792
792
  def assumed_state(self):
@@ -796,6 +796,99 @@ class DepartureTimer3(Switch):
796
796
  def attributes(self):
797
797
  return dict(self.vehicle.departure3)
798
798
 
799
+ class DepartureProfile1(Switch):
800
+ def __init__(self):
801
+ super().__init__(attr="departure_profile1", name="Departure profile 1", icon="mdi:radiator")
802
+
803
+ def configurate(self, **config):
804
+ self.spin = config.get('spin', '')
805
+
806
+ @property
807
+ def state(self):
808
+ status = self.vehicle.departure_profile1.get("enabled", "")
809
+ if status:
810
+ return True
811
+ else:
812
+ return False
813
+
814
+ async def turn_on(self):
815
+ await self.vehicle.set_departure_profile_active(id=1, action="on")
816
+ #await self.vehicle.update()
817
+
818
+ async def turn_off(self):
819
+ await self.vehicle.set_departure_profile_active(id=1, action="off")
820
+ #await self.vehicle.update()
821
+
822
+ @property
823
+ def assumed_state(self):
824
+ return False
825
+
826
+ @property
827
+ def attributes(self):
828
+ return dict(self.vehicle.departure_profile1)
829
+
830
+ class DepartureProfile2(Switch):
831
+ def __init__(self):
832
+ super().__init__(attr="departure_profile2", name="Departure profile 2", icon="mdi:radiator")
833
+
834
+ def configurate(self, **config):
835
+ self.spin = config.get('spin', '')
836
+
837
+ @property
838
+ def state(self):
839
+ status = self.vehicle.departure_profile2.get("enabled", "")
840
+ if status:
841
+ return True
842
+ else:
843
+ return False
844
+
845
+ async def turn_on(self):
846
+ await self.vehicle.set_departure_profile_active(id=2, action="on")
847
+ #await self.vehicle.update()
848
+
849
+ async def turn_off(self):
850
+ await self.vehicle.set_departure_profile_active(id=2, action="off")
851
+ #await self.vehicle.update()
852
+
853
+ @property
854
+ def assumed_state(self):
855
+ return False
856
+
857
+ @property
858
+ def attributes(self):
859
+ return dict(self.vehicle.departure_profile2)
860
+
861
+ class DepartureProfile3(Switch):
862
+ def __init__(self):
863
+ super().__init__(attr="departure_profile3", name="Departure profile 3", icon="mdi:radiator")
864
+
865
+ def configurate(self, **config):
866
+ self.spin = config.get('spin', '')
867
+
868
+ @property
869
+ def state(self):
870
+ status = self.vehicle.departure_profile3.get("enabled", "")
871
+ if status:
872
+ return True
873
+ else:
874
+ return False
875
+
876
+ async def turn_on(self):
877
+ await self.vehicle.set_departure_profile_active(id=3, action="on")
878
+ #await self.vehicle.update()
879
+
880
+ async def turn_off(self):
881
+ await self.vehicle.set_departure_profile_active(id=3, action="off")
882
+ #await self.vehicle.update()
883
+
884
+ @property
885
+ def assumed_state(self):
886
+ return False
887
+
888
+ @property
889
+ def attributes(self):
890
+ return dict(self.vehicle.departure_profile3)
891
+
799
892
 
800
893
  class RequestResults(Sensor):
801
894
  def __init__(self):
@@ -838,6 +931,9 @@ def create_instruments():
838
931
  DepartureTimer1(),
839
932
  DepartureTimer2(),
840
933
  DepartureTimer3(),
934
+ DepartureProfile1(),
935
+ DepartureProfile2(),
936
+ DepartureProfile3(),
841
937
  Sensor(
842
938
  attr="distance",
843
939
  name="Odometer",
pycupra/vehicle.py CHANGED
@@ -5,6 +5,7 @@ import re
5
5
  import logging
6
6
  import asyncio
7
7
 
8
+ from copy import deepcopy
8
9
  from datetime import datetime, timedelta, timezone
9
10
  from json import dumps as to_json
10
11
  from collections import OrderedDict
@@ -43,6 +44,7 @@ class Vehicle:
43
44
 
44
45
  self._requests = {
45
46
  'departuretimer': {'status': '', 'timestamp': DATEZERO},
47
+ 'departureprofile': {'status': '', 'timestamp': DATEZERO},
46
48
  'batterycharge': {'status': '', 'timestamp': DATEZERO},
47
49
  'climatisation': {'status': '', 'timestamp': DATEZERO},
48
50
  'refresh': {'status': '', 'timestamp': DATEZERO},
@@ -56,16 +58,18 @@ class Vehicle:
56
58
  self._climate_duration = 30
57
59
 
58
60
  self._relevantCapabilties = {
59
- 'measurements': {'active': False, 'reason': 'not supported'},
61
+ 'measurements': {'active': False, 'reason': 'not supported', },
60
62
  'climatisation': {'active': False, 'reason': 'not supported'},
61
63
  #'parkingInformation': {'active': False, 'reason': 'not supported'},
62
- 'tripStatistics': {'active': False, 'reason': 'not supported'},
64
+ 'tripStatistics': {'active': False, 'reason': 'not supported', 'supportsCyclicTrips': False},
65
+ 'vehicleHealthInspection': {'active': False, 'reason': 'not supported'},
63
66
  'vehicleHealthWarnings': {'active': False, 'reason': 'not supported'},
64
67
  'state': {'active': False, 'reason': 'not supported'},
65
- 'charging': {'active': False, 'reason': 'not supported'},
68
+ 'charging': {'active': False, 'reason': 'not supported', 'supportsTargetStateOfCharge': False},
66
69
  'honkAndFlash': {'active': False, 'reason': 'not supported'},
67
70
  'parkingPosition': {'active': False, 'reason': 'not supported'},
68
- 'departureTimers': {'active': False, 'reason': 'not supported'},
71
+ 'departureTimers': {'active': False, 'reason': 'not supported', 'supportsSingleTimer': False},
72
+ 'departureProfiles': {'active': False, 'reason': 'not supported', 'supportsSingleTimer': False},
69
73
  'transactionHistoryLockUnlock': {'active': False, 'reason': 'not supported'},
70
74
  'transactionHistoryHonkFlash': {'active': False, 'reason': 'not supported'},
71
75
  }
@@ -90,11 +94,19 @@ class Vehicle:
90
94
  data['reason']=capa.get('user-enabled', False)
91
95
  if capa.get('status', False):
92
96
  data['reason']=capa.get('status', '')
97
+ if capa.get('parameters', False):
98
+ if capa['parameters'].get('supportsCyclicTrips',False)==True or capa['parameters'].get('supportsCyclicTrips',False)=='true':
99
+ data['supportsCyclicTrips']=True
100
+ if capa['parameters'].get('supportsTargetStateOfCharge',False)==True or capa['parameters'].get('supportsTargetStateOfCharge',False)=='true':
101
+ data['supportsTargetStateOfCharge']=True
102
+ if capa['parameters'].get('supportsSingleTimer',False)==True or capa['parameters'].get('supportsSingleTimer',False)=='true':
103
+ data['supportsSingleTimer']=True
93
104
  self._relevantCapabilties[id].update(data)
94
105
 
95
106
 
96
- # Get URLs for model image
97
- self._modelimages = await self.get_modelimageurl()
107
+ await self.get_trip_statistic(),
108
+ # Get URLs for model image
109
+ self._modelimages = await self.get_modelimageurl(),
98
110
 
99
111
  self._discovered = datetime.now()
100
112
 
@@ -104,11 +116,11 @@ class Vehicle:
104
116
  if not self._discovered:
105
117
  await self.discover()
106
118
  else:
107
- # Rediscover if data is older than 1 hour
108
- hourago = datetime.now() - timedelta(hours = 1)
119
+ # Rediscover if data is older than 2 hours
120
+ hourago = datetime.now() - timedelta(hours = 2)
109
121
  if self._discovered < hourago:
110
- #await self.discover()
111
- _LOGGER.debug('Achtung! self.discover() auskommentiert')
122
+ await self.discover()
123
+ #_LOGGER.debug('Achtung! self.discover() auskommentiert')
112
124
 
113
125
  # Fetch all data if car is not deactivated
114
126
  if not self.deactivated:
@@ -116,13 +128,15 @@ class Vehicle:
116
128
  await asyncio.gather(
117
129
  self.get_preheater(),
118
130
  self.get_climater(),
119
- self.get_trip_statistic(),
131
+ #self.get_trip_statistic(), # commented out, because getting the trip statistic in discover() should be sufficient
120
132
  self.get_position(),
121
133
  self.get_statusreport(),
134
+ self.get_vehicleHealthWarnings(),
122
135
  self.get_charger(),
123
- self.get_timerprogramming(),
136
+ self.get_departure_timers(),
137
+ self.get_departure_profiles(),
124
138
  self.get_basiccardata(),
125
- #self.get_modelimageurl(), #commented out because getting the images once in discover() should be sufficient
139
+ #self.get_modelimageurl(), #commented out, because getting the images discover() should be sufficient
126
140
  return_exceptions=True
127
141
  )
128
142
  except:
@@ -171,7 +185,7 @@ class Vehicle:
171
185
  async def get_trip_statistic(self):
172
186
  """Fetch trip data if function is enabled."""
173
187
  if self._relevantCapabilties.get('tripStatistics', {}).get('active', False):
174
- data = await self._connection.getTripStatistics(self.vin, self._apibase)
188
+ data = await self._connection.getTripStatistics(self.vin, self._apibase, self._relevantCapabilties['tripStatistics'].get('supportsCyclicTrips', False))
175
189
  if data:
176
190
  self._states.update(data)
177
191
  else:
@@ -195,6 +209,14 @@ class Vehicle:
195
209
  else:
196
210
  _LOGGER.debug('Could not fetch any positional data')
197
211
 
212
+ async def get_vehicleHealthWarnings(self):
213
+ if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
214
+ data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
215
+ if data:
216
+ self._states.update(data)
217
+ else:
218
+ _LOGGER.debug('Could not fetch vehicle health warnings')
219
+
198
220
  async def get_statusreport(self):
199
221
  """Fetch status data if function is enabled."""
200
222
  if self._relevantCapabilties.get('state', {}).get('active', False):
@@ -203,7 +225,7 @@ class Vehicle:
203
225
  self._states.update(data)
204
226
  else:
205
227
  _LOGGER.debug('Could not fetch status report')
206
- if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
228
+ if self._relevantCapabilties.get('vehicleHealthInspection', {}).get('active', False):
207
229
  data = await self._connection.getMaintenance(self.vin, self._apibase)
208
230
  if data:
209
231
  self._states.update(data)
@@ -219,7 +241,7 @@ class Vehicle:
219
241
  else:
220
242
  _LOGGER.debug('Could not fetch charger data')
221
243
 
222
- async def get_timerprogramming(self):
244
+ async def get_departure_timers(self):
223
245
  """Fetch timer data if function is enabled."""
224
246
  if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
225
247
  data = await self._connection.getDeparturetimer(self.vin, self._apibase)
@@ -228,6 +250,15 @@ class Vehicle:
228
250
  else:
229
251
  _LOGGER.debug('Could not fetch timers')
230
252
 
253
+ async def get_departure_profiles(self):
254
+ """Fetch timer data if function is enabled."""
255
+ if self._relevantCapabilties.get('departureProfiles', {}).get('active', False):
256
+ data = await self._connection.getDepartureprofiles(self.vin, self._apibase)
257
+ if data:
258
+ self._states.update(data)
259
+ else:
260
+ _LOGGER.debug('Could not fetch timers')
261
+
231
262
  #async def wait_for_request(self, section, request, retryCount=36):
232
263
  """Update status of outstanding requests."""
233
264
  """retryCount -= 1
@@ -321,8 +352,8 @@ class Vehicle:
321
352
  # Update the charger data and check, if they have changed as expected
322
353
  retry = 0
323
354
  actionSuccessful = False
324
- while not actionSuccessful and retry < 3:
325
- await asyncio.sleep(5)
355
+ while not actionSuccessful and retry < 2:
356
+ await asyncio.sleep(15)
326
357
  await self.get_charger()
327
358
  if mode == 'start':
328
359
  if self.charging:
@@ -338,6 +369,7 @@ class Vehicle:
338
369
  raise
339
370
  retry = retry +1
340
371
  if actionSuccessful:
372
+ _LOGGER.debug('POST request for charger successful. New status as expected.')
341
373
  self._requests.get('batterycharge', {}).pop('id')
342
374
  return True
343
375
  _LOGGER.error('Response to POST request seemed successful but the charging status did not change as expected.')
@@ -351,12 +383,15 @@ class Vehicle:
351
383
 
352
384
  # API endpoint departuretimer
353
385
  async def set_charge_limit(self, limit=50):
354
- """ Set charging limit. """
355
- if not self._relevantCapabilties.get('departureTimers', {}).get('active', False) and not self._relevantCapabilties.get('charging', {}).get('active', False):
386
+ """ Set minimum state of charge limit for departure timers or departure profiles. """
387
+ if (not self._relevantCapabilties.get('departureTimers', {}).get('active', False) and
388
+ not self._relevantCapabilties.get('departureProfiles', {}).get('active', False) and
389
+ not self._relevantCapabilties.get('charging', {}).get('active', False)):
356
390
  _LOGGER.info('Set charging limit is not supported.')
357
391
  raise SeatInvalidRequestException('Set charging limit is not supported.')
358
- data = {}
359
- if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
392
+ if self._relevantCapabilties.get('departureTimers', {}).get('active', False) :
393
+ # Vehicle has departure timers
394
+ data = {}
360
395
  if isinstance(limit, int):
361
396
  if limit in [0, 10, 20, 30, 40, 50]:
362
397
  data['minSocPercentage'] = limit
@@ -365,13 +400,24 @@ class Vehicle:
365
400
  else:
366
401
  raise SeatInvalidRequestException(f'Charge limit "{limit}" is not supported.')
367
402
  return await self._set_timers(data)
403
+ elif self._relevantCapabilties.get('departureProfiles', {}).get('active', False):
404
+ # Vehicle has departure profiles
405
+ data= deepcopy(self.attrs.get('departureProfiles'))
406
+ if isinstance(limit, int):
407
+ if limit in [0, 10, 20, 30, 40, 50]:
408
+ data['minSocPercentage'] = limit
409
+ else:
410
+ raise SeatInvalidRequestException(f'Charge limit must be one of 0, 10, 20, 30, 40 or 50.')
411
+ else:
412
+ raise SeatInvalidRequestException(f'Charge limit "{limit}" is not supported.')
413
+ return await self._set_departure_profiles(data, action='minSocPercentage')
368
414
 
369
415
  async def set_timer_active(self, id=1, action='off'):
370
416
  """ Activate/deactivate departure timers. """
371
417
  data = {}
372
- supported = 'is_departure' + str(id) + "_supported"
418
+ supported = "is_departure" + str(id) + "_supported"
373
419
  if getattr(self, supported) is not True:
374
- raise SeatConfigException(f'This vehicle does not support timer id "{id}".')
420
+ raise SeatConfigException(f'This vehicle does not support timer id {id}.')
375
421
  if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
376
422
  allTimers= self.attrs.get('departureTimers').get('timers', [])
377
423
  for singleTimer in allTimers:
@@ -389,7 +435,7 @@ class Vehicle:
389
435
  else:
390
436
  raise SeatInvalidRequestException(f'Timer action "{action}" is not supported.')
391
437
  return await self._set_timers(data)
392
- raise SeatInvalidRequestException(f'Departure timers id {id} not found.')
438
+ raise SeatInvalidRequestException(f'Departure timer id {id} not found.')
393
439
  else:
394
440
  raise SeatInvalidRequestException('Departure timers are not supported.')
395
441
 
@@ -397,9 +443,9 @@ class Vehicle:
397
443
  """ Set departure schedules. """
398
444
  data = {}
399
445
  # Validate required user inputs
400
- supported = 'is_departure' + str(id) + "_supported"
446
+ supported = "is_departure" + str(id) + "_supported"
401
447
  if getattr(self, supported) is not True:
402
- raise SeatConfigException(f'Timer id "{id}" is not supported for this vehicle.')
448
+ raise SeatConfigException(f'Timer id {id} is not supported for this vehicle.')
403
449
  else:
404
450
  _LOGGER.debug(f'Timer id {id} is supported')
405
451
  if not schedule:
@@ -461,6 +507,8 @@ class Vehicle:
461
507
  else:
462
508
  raise SeatInvalidRequestException('Invalid type for charge max current variable')
463
509
 
510
+ # Prepare data and execute
511
+ data['id'] = id
464
512
  # Converting schedule to data map
465
513
  if schedule.get("enabled",False):
466
514
  data['enabled']=True
@@ -484,7 +532,7 @@ class Vehicle:
484
532
  else:
485
533
  preferedChargingTimes= [{
486
534
  "id" : 1,
487
- "enabled" : True,
535
+ "enabled" : False,
488
536
  "startTimeLocal" : "00:00",
489
537
  "endTimeLocal" : "00:00"
490
538
  }]
@@ -499,7 +547,7 @@ class Vehicle:
499
547
  "fridays":(schedule.get('days',"nnnnnnn")[4]=='y'),
500
548
  "saturdays":(schedule.get('days',"nnnnnnn")[5]=='y'),
501
549
  "sundays":(schedule.get('days',"nnnnnnn")[6]=='y'),
502
- "preferredChargingTimes": preferedChargingTimes
550
+ #"preferredChargingTimes": preferedChargingTimes
503
551
  }
504
552
  }
505
553
  else:
@@ -507,11 +555,10 @@ class Vehicle:
507
555
  _LOGGER.info(f'startDateTime={startDateTime.isoformat()}')
508
556
  data['singleTimer']= {
509
557
  "startDateTimeLocal": startDateTime.isoformat(),
510
- "preferredChargingTimes": preferedChargingTimes
558
+ #"preferredChargingTimes": preferedChargingTimes
511
559
  }
560
+ data["preferredChargingTimes"]= preferedChargingTimes
512
561
 
513
- # Prepare data and execute
514
- data['id'] = id
515
562
  # Now we have to embed the data for the timer 'id' in timers[]
516
563
  data={
517
564
  'timers' : [data]
@@ -550,24 +597,33 @@ class Vehicle:
550
597
  # Update the departure timers data and check, if they have changed as expected
551
598
  retry = 0
552
599
  actionSuccessful = False
553
- while not actionSuccessful and retry < 3:
554
- await asyncio.sleep(5)
555
- await self.get_timerprogramming()
600
+ while not actionSuccessful and retry < 2:
601
+ await asyncio.sleep(15)
602
+ await self.get_departure_timers()
556
603
  if data.get('minSocPercentage',False):
557
604
  if data.get('minSocPercentage',-2)==self.attrs.get('departureTimers',{}).get('minSocPercentage',-1):
558
605
  actionSuccessful=True
559
606
  else:
607
+ _LOGGER.debug('Checking if new departure timer is as expected:')
560
608
  timerData = data.get('timers',[])[0]
561
609
  timerDataId = timerData.get('id',False)
610
+ timerDataCopy = deepcopy(timerData)
611
+ timerDataCopy['enabled']=True
562
612
  if timerDataId:
563
613
  newTimers = self.attrs.get('departureTimers',{}).get('timers',[])
564
614
  for newTimer in newTimers:
565
615
  if newTimer.get('id',-1)==timerDataId:
566
- if timerData==newTimer:
616
+ _LOGGER.debug(f'Value of timer sent:{timerData}')
617
+ _LOGGER.debug(f'Value of timer read:{newTimer}')
618
+ if timerData==newTimer:
619
+ actionSuccessful=True
620
+ elif timerDataCopy==newTimer:
621
+ _LOGGER.debug('Data written and data read are the same, but the timer is activated.')
567
622
  actionSuccessful=True
568
623
  break
569
624
  retry = retry +1
570
- if actionSuccessful:
625
+ if True: #actionSuccessful:
626
+ #_LOGGER.debug('POST request for departure timers successful. New status as expected.')
571
627
  self._requests.get('departuretimer', {}).pop('id')
572
628
  return True
573
629
  _LOGGER.error('Response to POST request seemed successful but the departure timers status did not change as expected.')
@@ -579,6 +635,94 @@ class Vehicle:
579
635
  self._requests['departuretimer'] = {'status': 'Exception'}
580
636
  raise SeatException('Failed to set departure timer schedule')
581
637
 
638
+ async def set_departure_profile_active(self, id=1, action='off'):
639
+ """ Activate/deactivate departure profiles. """
640
+ data = {}
641
+ supported = "is_departure_profile" + str(id) + "_supported"
642
+ if getattr(self, supported) is not True:
643
+ raise SeatConfigException(f'This vehicle does not support departure profile id "{id}".')
644
+ if self._relevantCapabilties.get('departureProfiles', {}).get('active', False):
645
+ data= deepcopy(self.attrs.get('departureProfiles'))
646
+ if len(data.get('timers', []))<1:
647
+ raise SeatInvalidRequestException(f'No timers found in departure profile: {data}.')
648
+ idFound=False
649
+ for e in range(len(data.get('timers', []))):
650
+ if data['timers'][e].get('id',-1)==id:
651
+ if action in ['on', 'off']:
652
+ if action=='on':
653
+ enabled=True
654
+ else:
655
+ enabled=False
656
+ data['timers'][e]['enabled'] = enabled
657
+ idFound=True
658
+ _LOGGER.debug(f'Changing departure profile {id} to {action}.')
659
+ else:
660
+ raise SeatInvalidRequestException(f'Profile action "{action}" is not supported.')
661
+ if idFound:
662
+ return await self._set_departure_profiles(data, action=action)
663
+ raise SeatInvalidRequestException(f'Departure profile id {id} not found in {data.get('timers',[])}.')
664
+ else:
665
+ raise SeatInvalidRequestException('Departure profiles are not supported.')
666
+
667
+ async def _set_departure_profiles(self, data=None, action=None):
668
+ """ Set departure profiles. """
669
+ if not self._relevantCapabilties.get('departureProfiles', {}).get('active', False):
670
+ raise SeatInvalidRequestException('Departure profiles are not supported.')
671
+ if self._requests['departureprofile'].get('id', False):
672
+ timestamp = self._requests.get('departureprofile', {}).get('timestamp', datetime.now())
673
+ expired = datetime.now() - timedelta(minutes=1)
674
+ if expired > timestamp:
675
+ self._requests.get('departureprofile', {}).pop('id')
676
+ else:
677
+ raise SeatRequestInProgressException('Scheduling of departure profile is already in progress')
678
+ try:
679
+ self._requests['latest'] = 'Departureprofile'
680
+ response = await self._connection.setDepartureprofile(self.vin, self._apibase, data, spin=False)
681
+ if not response:
682
+ self._requests['departureprofile'] = {'status': 'Failed'}
683
+ _LOGGER.error('Failed to execute departure profile request')
684
+ raise SeatException('Failed to execute departure profile request')
685
+ else:
686
+ self._requests['remaining'] = response.get('rate_limit_remaining', -1)
687
+ self._requests['departureprofile'] = {
688
+ 'timestamp': datetime.now(),
689
+ 'status': response.get('state', 'Unknown'),
690
+ 'id': response.get('id', 0),
691
+ }
692
+ # Update the departure profile data and check, if they have changed as expected
693
+ retry = 0
694
+ actionSuccessful = False
695
+ while not actionSuccessful and retry < 2:
696
+ await asyncio.sleep(15)
697
+ await self.get_departure_profiles()
698
+ if action=='minSocPercentage':
699
+ _LOGGER.debug('Checking if new minSocPercentage is as expected:')
700
+ _LOGGER.debug(f'Value of minSocPercentage sent:{data.get('minSocPercentage',-2)}')
701
+ _LOGGER.debug(f'Value of minSocPercentage read:{self.attrs.get('departureTimers',{}).get('minSocPercentage',-1)}')
702
+ if data.get('minSocPercentage',-2)==self.attrs.get('departureTimers',{}).get('minSocPercentage',-1):
703
+ actionSuccessful=True
704
+ else:
705
+ sendData = data.get('timers',[])
706
+ newData = self.attrs.get('departureProfiles',{}).get('timers',[])
707
+ _LOGGER.debug('Checking if new departure profiles are as expected:')
708
+ _LOGGER.debug(f'Value of data sent:{sendData}')
709
+ _LOGGER.debug(f'Value of data read:{newData}')
710
+ if sendData==newData:
711
+ actionSuccessful=True
712
+ retry = retry +1
713
+ if actionSuccessful:
714
+ self._requests.get('departureprofile', {}).pop('id')
715
+ return True
716
+ _LOGGER.error('Response to PUT request seemed successful but the departure profiles status did not change as expected.')
717
+ return False
718
+ except (SeatInvalidRequestException, SeatException):
719
+ raise
720
+ except Exception as error:
721
+ _LOGGER.warning(f'Failed to execute departure profile request - {error}')
722
+ self._requests['departureprofile'] = {'status': 'Exception'}
723
+ raise SeatException('Failed to set departure profile schedule')
724
+
725
+
582
726
  # Send a destination to vehicle
583
727
  async def send_destination(self, destination=None):
584
728
  """ Send destination to vehicle. """
@@ -726,8 +870,8 @@ class Vehicle:
726
870
  # Update the climater data and check, if they have changed as expected
727
871
  retry = 0
728
872
  actionSuccessful = False
729
- while not actionSuccessful and retry < 3:
730
- await asyncio.sleep(5)
873
+ while not actionSuccessful and retry < 2:
874
+ await asyncio.sleep(15)
731
875
  await self.get_climater()
732
876
  if mode == 'start':
733
877
  if self.electric_climatisation:
@@ -749,6 +893,7 @@ class Vehicle:
749
893
  raise
750
894
  retry = retry +1
751
895
  if actionSuccessful:
896
+ _LOGGER.debug('POST request for climater successful. New status as expected.')
752
897
  self._requests.get('climatisation', {}).pop('id')
753
898
  return True
754
899
  _LOGGER.error('Response to POST request seemed successful but the climater status did not change as expected.')
@@ -1052,7 +1197,7 @@ class Vehicle:
1052
1197
  def parking_light(self):
1053
1198
  """Return true if parking light is on"""
1054
1199
  response = self.attrs.get('status').get('lights', 0)
1055
- if response == 'On':
1200
+ if response == 'on':
1056
1201
  return True
1057
1202
  else:
1058
1203
  return False
@@ -2034,7 +2179,6 @@ class Vehicle:
2034
2179
  return True if response != 0 else False
2035
2180
 
2036
2181
  # Departure timers
2037
- # Under development
2038
2182
  @property
2039
2183
  def departure1(self):
2040
2184
  """Return timer status and attributes."""
@@ -2146,6 +2290,79 @@ class Vehicle:
2146
2290
  return True
2147
2291
  return False
2148
2292
 
2293
+ # Departure profiles
2294
+ @property
2295
+ def departure_profile1(self):
2296
+ """Return profile status and attributes."""
2297
+ if self.attrs.get('departureProfiles', False):
2298
+ try:
2299
+ data = {}
2300
+ timerdata = self.attrs.get('departureProfiles', {}).get('timers', [])
2301
+ timer = timerdata[0]
2302
+ #timer.pop('timestamp', None)
2303
+ #timer.pop('timerID', None)
2304
+ #timer.pop('profileID', None)
2305
+ data.update(timer)
2306
+ return data
2307
+ except:
2308
+ pass
2309
+ return None
2310
+
2311
+ @property
2312
+ def is_departure_profile1_supported(self):
2313
+ """Return true if profile 1 is supported."""
2314
+ if len(self.attrs.get('departureProfiles', {}).get('timers', [])) >= 1:
2315
+ return True
2316
+ return False
2317
+
2318
+ @property
2319
+ def departure_profile2(self):
2320
+ """Return profile status and attributes."""
2321
+ if self.attrs.get('departureProfiles', False):
2322
+ try:
2323
+ data = {}
2324
+ timerdata = self.attrs.get('departureProfiles', {}).get('timers', [])
2325
+ timer = timerdata[1]
2326
+ #timer.pop('timestamp', None)
2327
+ #timer.pop('timerID', None)
2328
+ #timer.pop('profileID', None)
2329
+ data.update(timer)
2330
+ return data
2331
+ except:
2332
+ pass
2333
+ return None
2334
+
2335
+ @property
2336
+ def is_departure_profile2_supported(self):
2337
+ """Return true if profile 2 is supported."""
2338
+ if len(self.attrs.get('departureProfiles', {}).get('timers', [])) >= 2:
2339
+ return True
2340
+ return False
2341
+
2342
+ @property
2343
+ def departure_profile3(self):
2344
+ """Return profile status and attributes."""
2345
+ if self.attrs.get('departureProfiles', False):
2346
+ try:
2347
+ data = {}
2348
+ timerdata = self.attrs.get('departureProfiles', {}).get('timers', [])
2349
+ timer = timerdata[2]
2350
+ #timer.pop('timestamp', None)
2351
+ #timer.pop('timerID', None)
2352
+ #timer.pop('profileID', None)
2353
+ data.update(timer)
2354
+ return data
2355
+ except:
2356
+ pass
2357
+ return None
2358
+
2359
+ @property
2360
+ def is_departure_profile3_supported(self):
2361
+ """Return true if profile 3 is supported."""
2362
+ if len(self.attrs.get('departureProfiles', {}).get('timers', [])) >= 3:
2363
+ return True
2364
+ return False
2365
+
2149
2366
  # Trip data
2150
2367
  @property
2151
2368
  def trip_last_entry(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
5
5
  Home-page: https://github.com/WulfgarW/pycupra
6
6
  Author: WulfgarW
@@ -0,0 +1,13 @@
1
+ pycupra/__init__.py,sha256=VPzUfKd5mBFD1UERNV61FbGHih5dQPupLgIfYtmIUi4,230
2
+ pycupra/__version__.py,sha256=YbGN0knQ3fpcD8GGO6OA7ZBYa-fQ4unLaw-NTYREVn8,207
3
+ pycupra/connection.py,sha256=4bcaHn6AwAAY9e3MfVwlPMQzKJZQo_BZttts0lubnro,81120
4
+ pycupra/const.py,sha256=VEYH8TUsJGJwBwloaajwoElYd0qxE7oesvoagvDdE-4,10161
5
+ pycupra/dashboard.py,sha256=difLM3R2uXUrVslUjqzH8nN6WPJA-u26rHlGUU6W8Oo,40454
6
+ pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
+ pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
8
+ pycupra/vehicle.py,sha256=vlSuvrspX_I1RlS8KoZkN09u5cCNNdcIslsA0xPUuqA,120442
9
+ pycupra-0.0.6.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
10
+ pycupra-0.0.6.dist-info/METADATA,sha256=k-diSYUQOySb2FxopmNeWMOXqf_FQG1A5yGwnMpo03M,2578
11
+ pycupra-0.0.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
12
+ pycupra-0.0.6.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
13
+ pycupra-0.0.6.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- pycupra/__init__.py,sha256=VPzUfKd5mBFD1UERNV61FbGHih5dQPupLgIfYtmIUi4,230
2
- pycupra/__version__.py,sha256=pbFZ_GKa38mHgdQKZpGKVzHcmF4OGZEi3EgIrJ-6wRo,207
3
- pycupra/connection.py,sha256=9Ha_zlAi6Cw6gNI1OA8roCoIJOEa1ZYgnRVO_Uk5c8s,77628
4
- pycupra/const.py,sha256=Mx9pPZifQBpn9lTsLH8R7xkUHrXRvul8w_b6LLLD7gE,10038
5
- pycupra/dashboard.py,sha256=7sVQI10lMspAOfVOlMEvMlndiNlUxjpWoNobUU9CZrw,37636
6
- pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
7
- pycupra/utilities.py,sha256=cH4MiIzT2WlHgmnl_E7rR0R5LvCXfDNvirJolct50V8,2563
8
- pycupra/vehicle.py,sha256=ea2InLdtWV1NNKG6V3VgYj-VJPe4IDVapB7aB14IKDQ,108646
9
- pycupra-0.0.5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
10
- pycupra-0.0.5.dist-info/METADATA,sha256=xjH9t2ZtKIuMSPUTt4y_a9pD_jKRP25UZsEccpIoY4s,2578
11
- pycupra-0.0.5.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
12
- pycupra-0.0.5.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
13
- pycupra-0.0.5.dist-info/RECORD,,