pycupra 0.0.4__tar.gz → 0.0.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.0.4
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
@@ -59,6 +59,9 @@ RESOURCES = [
59
59
  "departure1",
60
60
  "departure2",
61
61
  "departure3",
62
+ "departure_profile1",
63
+ "departure_profile2",
64
+ "departure_profile3",
62
65
  "distance",
63
66
  "door_closed_left_back",
64
67
  "door_closed_left_front",
@@ -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.4"
6
+ __version__ = "0.0.6"
@@ -15,6 +15,8 @@ import string
15
15
  import secrets
16
16
  import xmltodict
17
17
 
18
+ from PIL import Image
19
+ from io import BytesIO
18
20
  from sys import version_info, argv
19
21
  from datetime import timedelta, datetime, timezone
20
22
  from urllib.parse import urljoin, parse_qs, urlparse, urlencode
@@ -77,6 +79,7 @@ from .const import (
77
79
  API_CLIMATER_STATUS,
78
80
  API_CLIMATER,
79
81
  API_DEPARTURE_TIMERS,
82
+ API_DEPARTURE_PROFILES,
80
83
  API_MILEAGE,
81
84
  API_CAPABILITIES,
82
85
  #API_CAPABILITIES_MANAGEMENT,
@@ -621,6 +624,8 @@ class Connection:
621
624
  try:
622
625
  if response.status == 204:
623
626
  res = {'status_code': response.status}
627
+ elif response.status == 202 and method==METH_PUT:
628
+ res = response
624
629
  elif response.status >= 200 or response.status <= 300:
625
630
  # If this is a revoke token url, expect Content-Length 0 and return
626
631
  if int(response.headers.get('Content-Length', 0)) == 0 and 'revoke' in url:
@@ -879,16 +884,6 @@ class Connection:
879
884
  _LOGGER.info('Unhandled error while trying to fetch mycar data')
880
885
  except Exception as error:
881
886
  _LOGGER.warning(f'Could not fetch mycar report, error: {error}')
882
- try:
883
- response = await self.get(eval(f"f'{API_WARNINGLIGHTS}'"))
884
- if 'statuses' in response:
885
- data['warninglights'] = response
886
- elif response.get('status_code', {}):
887
- _LOGGER.warning(f'Could not fetch warnlights, HTTP status code: {response.get("status_code")}')
888
- else:
889
- _LOGGER.info('Unhandled error while trying to fetch warnlights')
890
- except Exception as error:
891
- _LOGGER.warning(f'Could not fetch warnlights, error: {error}')
892
887
  try:
893
888
  response = await self.get(eval(f"f'{API_MILEAGE}'"))
894
889
  if response.get('mileageKm', {}):
@@ -903,6 +898,24 @@ class Connection:
903
898
  return False
904
899
  return data
905
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
+
906
919
  #async def getOperationList(self, vin, baseurl):
907
920
  """Collect operationlist for VIN, supported/licensed functions."""
908
921
  """try:
@@ -940,6 +953,26 @@ class Connection:
940
953
  if len(pic)>0:
941
954
  loop = asyncio.get_running_loop()
942
955
  await loop.run_in_executor(None, self.writeImageFile, pos,pic, images)
956
+ if pos=='front':
957
+ # Crop the front image to a square format
958
+ try:
959
+ im= Image.open(BytesIO(pic))
960
+ width, height = im.size
961
+ if height>width:
962
+ width, height = height, width
963
+ # Setting the points for cropped image
964
+ left = (width-height)/2
965
+ top = 0
966
+ right = height+(width-height)/2
967
+ bottom = height
968
+ # Cropped image of above dimension
969
+ im1 = im.crop((left, top, right, bottom))
970
+ byteIO = BytesIO()
971
+ im1.save(byteIO, format='PNG')
972
+ await loop.run_in_executor(None, self.writeImageFile, pos+'_cropped',byteIO.getvalue(), images)
973
+ except:
974
+ _LOGGER.warning('Cropping front image to square format failed.')
975
+
943
976
  _LOGGER.debug('Read images from web site and wrote them to file.')
944
977
  response['images']=images
945
978
  return response
@@ -987,19 +1020,22 @@ class Connection:
987
1020
  return False
988
1021
  return data
989
1022
 
990
- async def getTripStatistics(self, vin, baseurl):
1023
+ async def getTripStatistics(self, vin, baseurl, supportsCyclicTrips):
991
1024
  """Get short term and cyclic trip statistics."""
992
1025
  await self.set_token(self._session_auth_brand)
993
1026
  try:
994
1027
  data={'tripstatistics': {}}
995
- dataType='CYCLIC'
996
- response = await self.get(eval(f"f'{API_TRIP}'"))
997
- if response.get('data', []):
998
- data['tripstatistics']['cyclic']= response.get('data', [])
999
- elif response.get('status_code', {}):
1000
- _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')
1001
1037
  else:
1002
- _LOGGER.info(f'Unhandled error while trying to fetch trip statistics')
1038
+ _LOGGER.info(f'Vehicle does not support cyclic trips.')
1003
1039
  dataType='SHORT'
1004
1040
  response = await self.get(eval(f"f'{API_TRIP}'"))
1005
1041
  if response.get('data', []):
@@ -1059,11 +1095,33 @@ class Connection:
1059
1095
  data['departureTimers'] = response
1060
1096
  return data
1061
1097
  elif response.get('status_code', {}):
1062
- _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")}')
1063
1099
  else:
1064
1100
  _LOGGER.info('Unknown error while trying to fetch data for departure timers')
1065
1101
  except Exception as error:
1066
- _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}')
1067
1125
  return False
1068
1126
 
1069
1127
  async def getClimater(self, vin, baseurl):
@@ -1249,6 +1307,39 @@ class Connection:
1249
1307
  raise
1250
1308
  return False
1251
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
+
1252
1343
  async def setCharger(self, vin, baseurl, mode, data):
1253
1344
  """Start/Stop charger."""
1254
1345
  if mode in {'start', 'stop'}:
@@ -1299,21 +1390,25 @@ class Connection:
1299
1390
  raise
1300
1391
  return False
1301
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
+
1302
1405
  async def sendDestination(self, vin, baseurl, data, spin):
1303
1406
  """Send destination to vehicle."""
1304
1407
 
1305
1408
  await self.set_token(self._session_auth_brand)
1306
1409
  try:
1307
1410
  url= eval(f"f'{API_DESTINATION}'")
1308
- response = await self._session.request(
1309
- METH_PUT,
1310
- url,
1311
- headers=self._session_headers,
1312
- timeout=ClientTimeout(total=TIMEOUT.seconds),
1313
- cookies=self._session_cookies,
1314
- raise_for_status=False,
1315
- json=data
1316
- )
1411
+ response = await self._request(METH_PUT, url, json=data)
1317
1412
  if response.status==202: #[202 Accepted]
1318
1413
  _LOGGER.debug(f'Destination {data[0]} successfully sent to API.')
1319
1414
  return response
@@ -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)
@@ -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",
@@ -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(),
139
+ #self.get_modelimageurl(), #commented out, because getting the images discover() should be sufficient
126
140
  return_exceptions=True
127
141
  )
128
142
  except:
@@ -147,16 +161,15 @@ class Vehicle:
147
161
  async def get_preheater(self):
148
162
  """Fetch pre-heater data if function is enabled."""
149
163
  _LOGGER.info('get_preheater() not implemented yet')
150
- raise
151
- if self._relevantCapabilties.get('#dont know the name for the preheater capability', {}).get('active', False):
152
- if not await self.expired('rheating_v1'):
153
- data = await self._connection.getPreHeater(self.vin, self._apibase)
154
- if data:
155
- self._states.update(data)
156
- else:
157
- _LOGGER.debug('Could not fetch preheater data')
158
- else:
159
- self._requests.pop('preheater', None)
164
+ #if self._relevantCapabilties.get('#dont know the name for the preheater capability', {}).get('active', False):
165
+ # if not await self.expired('rheating_v1'):
166
+ # data = await self._connection.getPreHeater(self.vin, self._apibase)
167
+ # if data:
168
+ # self._states.update(data)
169
+ # else:
170
+ # _LOGGER.debug('Could not fetch preheater data')
171
+ #else:
172
+ # self._requests.pop('preheater', None)
160
173
 
161
174
  async def get_climater(self):
162
175
  """Fetch climater data if function is enabled."""
@@ -172,7 +185,7 @@ class Vehicle:
172
185
  async def get_trip_statistic(self):
173
186
  """Fetch trip data if function is enabled."""
174
187
  if self._relevantCapabilties.get('tripStatistics', {}).get('active', False):
175
- 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))
176
189
  if data:
177
190
  self._states.update(data)
178
191
  else:
@@ -196,6 +209,14 @@ class Vehicle:
196
209
  else:
197
210
  _LOGGER.debug('Could not fetch any positional data')
198
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
+
199
220
  async def get_statusreport(self):
200
221
  """Fetch status data if function is enabled."""
201
222
  if self._relevantCapabilties.get('state', {}).get('active', False):
@@ -204,7 +225,7 @@ class Vehicle:
204
225
  self._states.update(data)
205
226
  else:
206
227
  _LOGGER.debug('Could not fetch status report')
207
- if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
228
+ if self._relevantCapabilties.get('vehicleHealthInspection', {}).get('active', False):
208
229
  data = await self._connection.getMaintenance(self.vin, self._apibase)
209
230
  if data:
210
231
  self._states.update(data)
@@ -220,7 +241,7 @@ class Vehicle:
220
241
  else:
221
242
  _LOGGER.debug('Could not fetch charger data')
222
243
 
223
- async def get_timerprogramming(self):
244
+ async def get_departure_timers(self):
224
245
  """Fetch timer data if function is enabled."""
225
246
  if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
226
247
  data = await self._connection.getDeparturetimer(self.vin, self._apibase)
@@ -229,6 +250,15 @@ class Vehicle:
229
250
  else:
230
251
  _LOGGER.debug('Could not fetch timers')
231
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
+
232
262
  #async def wait_for_request(self, section, request, retryCount=36):
233
263
  """Update status of outstanding requests."""
234
264
  """retryCount -= 1
@@ -322,8 +352,8 @@ class Vehicle:
322
352
  # Update the charger data and check, if they have changed as expected
323
353
  retry = 0
324
354
  actionSuccessful = False
325
- while not actionSuccessful and retry < 3:
326
- await asyncio.sleep(5)
355
+ while not actionSuccessful and retry < 2:
356
+ await asyncio.sleep(15)
327
357
  await self.get_charger()
328
358
  if mode == 'start':
329
359
  if self.charging:
@@ -339,6 +369,7 @@ class Vehicle:
339
369
  raise
340
370
  retry = retry +1
341
371
  if actionSuccessful:
372
+ _LOGGER.debug('POST request for charger successful. New status as expected.')
342
373
  self._requests.get('batterycharge', {}).pop('id')
343
374
  return True
344
375
  _LOGGER.error('Response to POST request seemed successful but the charging status did not change as expected.')
@@ -352,12 +383,15 @@ class Vehicle:
352
383
 
353
384
  # API endpoint departuretimer
354
385
  async def set_charge_limit(self, limit=50):
355
- """ Set charging limit. """
356
- 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)):
357
390
  _LOGGER.info('Set charging limit is not supported.')
358
391
  raise SeatInvalidRequestException('Set charging limit is not supported.')
359
- data = {}
360
- if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
392
+ if self._relevantCapabilties.get('departureTimers', {}).get('active', False) :
393
+ # Vehicle has departure timers
394
+ data = {}
361
395
  if isinstance(limit, int):
362
396
  if limit in [0, 10, 20, 30, 40, 50]:
363
397
  data['minSocPercentage'] = limit
@@ -366,13 +400,24 @@ class Vehicle:
366
400
  else:
367
401
  raise SeatInvalidRequestException(f'Charge limit "{limit}" is not supported.')
368
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')
369
414
 
370
415
  async def set_timer_active(self, id=1, action='off'):
371
416
  """ Activate/deactivate departure timers. """
372
417
  data = {}
373
- supported = 'is_departure' + str(id) + "_supported"
418
+ supported = "is_departure" + str(id) + "_supported"
374
419
  if getattr(self, supported) is not True:
375
- raise SeatConfigException(f'This vehicle does not support timer id "{id}".')
420
+ raise SeatConfigException(f'This vehicle does not support timer id {id}.')
376
421
  if self._relevantCapabilties.get('departureTimers', {}).get('active', False):
377
422
  allTimers= self.attrs.get('departureTimers').get('timers', [])
378
423
  for singleTimer in allTimers:
@@ -390,7 +435,7 @@ class Vehicle:
390
435
  else:
391
436
  raise SeatInvalidRequestException(f'Timer action "{action}" is not supported.')
392
437
  return await self._set_timers(data)
393
- raise SeatInvalidRequestException(f'Departure timers id {id} not found.')
438
+ raise SeatInvalidRequestException(f'Departure timer id {id} not found.')
394
439
  else:
395
440
  raise SeatInvalidRequestException('Departure timers are not supported.')
396
441
 
@@ -398,9 +443,9 @@ class Vehicle:
398
443
  """ Set departure schedules. """
399
444
  data = {}
400
445
  # Validate required user inputs
401
- supported = 'is_departure' + str(id) + "_supported"
446
+ supported = "is_departure" + str(id) + "_supported"
402
447
  if getattr(self, supported) is not True:
403
- 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.')
404
449
  else:
405
450
  _LOGGER.debug(f'Timer id {id} is supported')
406
451
  if not schedule:
@@ -462,6 +507,8 @@ class Vehicle:
462
507
  else:
463
508
  raise SeatInvalidRequestException('Invalid type for charge max current variable')
464
509
 
510
+ # Prepare data and execute
511
+ data['id'] = id
465
512
  # Converting schedule to data map
466
513
  if schedule.get("enabled",False):
467
514
  data['enabled']=True
@@ -485,7 +532,7 @@ class Vehicle:
485
532
  else:
486
533
  preferedChargingTimes= [{
487
534
  "id" : 1,
488
- "enabled" : True,
535
+ "enabled" : False,
489
536
  "startTimeLocal" : "00:00",
490
537
  "endTimeLocal" : "00:00"
491
538
  }]
@@ -500,7 +547,7 @@ class Vehicle:
500
547
  "fridays":(schedule.get('days',"nnnnnnn")[4]=='y'),
501
548
  "saturdays":(schedule.get('days',"nnnnnnn")[5]=='y'),
502
549
  "sundays":(schedule.get('days',"nnnnnnn")[6]=='y'),
503
- "preferredChargingTimes": preferedChargingTimes
550
+ #"preferredChargingTimes": preferedChargingTimes
504
551
  }
505
552
  }
506
553
  else:
@@ -508,11 +555,10 @@ class Vehicle:
508
555
  _LOGGER.info(f'startDateTime={startDateTime.isoformat()}')
509
556
  data['singleTimer']= {
510
557
  "startDateTimeLocal": startDateTime.isoformat(),
511
- "preferredChargingTimes": preferedChargingTimes
558
+ #"preferredChargingTimes": preferedChargingTimes
512
559
  }
560
+ data["preferredChargingTimes"]= preferedChargingTimes
513
561
 
514
- # Prepare data and execute
515
- data['id'] = id
516
562
  # Now we have to embed the data for the timer 'id' in timers[]
517
563
  data={
518
564
  'timers' : [data]
@@ -551,24 +597,33 @@ class Vehicle:
551
597
  # Update the departure timers data and check, if they have changed as expected
552
598
  retry = 0
553
599
  actionSuccessful = False
554
- while not actionSuccessful and retry < 3:
555
- await asyncio.sleep(5)
556
- await self.get_timerprogramming()
600
+ while not actionSuccessful and retry < 2:
601
+ await asyncio.sleep(15)
602
+ await self.get_departure_timers()
557
603
  if data.get('minSocPercentage',False):
558
604
  if data.get('minSocPercentage',-2)==self.attrs.get('departureTimers',{}).get('minSocPercentage',-1):
559
605
  actionSuccessful=True
560
606
  else:
607
+ _LOGGER.debug('Checking if new departure timer is as expected:')
561
608
  timerData = data.get('timers',[])[0]
562
609
  timerDataId = timerData.get('id',False)
610
+ timerDataCopy = deepcopy(timerData)
611
+ timerDataCopy['enabled']=True
563
612
  if timerDataId:
564
613
  newTimers = self.attrs.get('departureTimers',{}).get('timers',[])
565
614
  for newTimer in newTimers:
566
615
  if newTimer.get('id',-1)==timerDataId:
567
- 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.')
568
622
  actionSuccessful=True
569
623
  break
570
624
  retry = retry +1
571
- if actionSuccessful:
625
+ if True: #actionSuccessful:
626
+ #_LOGGER.debug('POST request for departure timers successful. New status as expected.')
572
627
  self._requests.get('departuretimer', {}).pop('id')
573
628
  return True
574
629
  _LOGGER.error('Response to POST request seemed successful but the departure timers status did not change as expected.')
@@ -580,6 +635,94 @@ class Vehicle:
580
635
  self._requests['departuretimer'] = {'status': 'Exception'}
581
636
  raise SeatException('Failed to set departure timer schedule')
582
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
+
583
726
  # Send a destination to vehicle
584
727
  async def send_destination(self, destination=None):
585
728
  """ Send destination to vehicle. """
@@ -727,8 +870,8 @@ class Vehicle:
727
870
  # Update the climater data and check, if they have changed as expected
728
871
  retry = 0
729
872
  actionSuccessful = False
730
- while not actionSuccessful and retry < 3:
731
- await asyncio.sleep(5)
873
+ while not actionSuccessful and retry < 2:
874
+ await asyncio.sleep(15)
732
875
  await self.get_climater()
733
876
  if mode == 'start':
734
877
  if self.electric_climatisation:
@@ -750,6 +893,7 @@ class Vehicle:
750
893
  raise
751
894
  retry = retry +1
752
895
  if actionSuccessful:
896
+ _LOGGER.debug('POST request for climater successful. New status as expected.')
753
897
  self._requests.get('climatisation', {}).pop('id')
754
898
  return True
755
899
  _LOGGER.error('Response to POST request seemed successful but the climater status did not change as expected.')
@@ -913,7 +1057,7 @@ class Vehicle:
913
1057
  if expired > timestamp:
914
1058
  self._requests.get('refresh', {}).pop('id')
915
1059
  else:
916
- raise SeatRequestInProgressException('A data refresh request is already in progress')
1060
+ raise SeatRequestInProgressException('Last data refresh request less than 3 minutes ago')
917
1061
  try:
918
1062
  self._requests['latest'] = 'Refresh'
919
1063
  response = await self._connection.setRefresh(self.vin, self._apibase)
@@ -1028,18 +1172,19 @@ class Vehicle:
1028
1172
  @property
1029
1173
  def model_image_small(self):
1030
1174
  """Return URL for model image"""
1031
- return self._modelimages.get('images','').get('front','')
1175
+ return self._modelimages.get('images','').get('front_cropped','')
1032
1176
 
1033
1177
  @property
1034
1178
  def is_model_image_small_supported(self):
1035
1179
  """Return true if model image url is not None."""
1036
1180
  if self._modelimages is not None:
1037
- return True
1181
+ if self._modelimages.get('images','').get('front_cropped','')!='':
1182
+ return True
1038
1183
 
1039
1184
  @property
1040
1185
  def model_image_large(self):
1041
1186
  """Return URL for model image"""
1042
- return self._modelimages.get('images','').get('side', '')
1187
+ return self._modelimages.get('images','').get('front', '')
1043
1188
 
1044
1189
  @property
1045
1190
  def is_model_image_large_supported(self):
@@ -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):
@@ -2452,11 +2669,12 @@ class Vehicle:
2452
2669
  @property
2453
2670
  def refresh_data(self):
2454
2671
  """Get state of data refresh"""
2455
- if self._requests.get('refresh', {}).get('id', False):
2456
- timestamp = self._requests.get('refresh', {}).get('timestamp', DATEZERO)
2457
- expired = datetime.now() - timedelta(minutes=2)
2458
- if expired < timestamp:
2459
- return True
2672
+ #if self._requests.get('refresh', {}).get('id', False):
2673
+ # timestamp = self._requests.get('refresh', {}).get('timestamp', DATEZERO)
2674
+ # expired = datetime.now() - timedelta(minutes=2)
2675
+ # if expired < timestamp:
2676
+ # return True
2677
+ #State is always false
2460
2678
  return False
2461
2679
 
2462
2680
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycupra
3
- Version: 0.0.4
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes