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 +1 -1
- pycupra/connection.py +102 -29
- pycupra/const.py +1 -0
- pycupra/dashboard.py +118 -22
- pycupra/vehicle.py +258 -41
- {pycupra-0.0.5.dist-info → pycupra-0.0.6.dist-info}/METADATA +1 -1
- pycupra-0.0.6.dist-info/RECORD +13 -0
- pycupra-0.0.5.dist-info/RECORD +0 -13
- {pycupra-0.0.5.dist-info → pycupra-0.0.6.dist-info}/WHEEL +0 -0
- {pycupra-0.0.5.dist-info → pycupra-0.0.6.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.0.5.dist-info → pycupra-0.0.6.dist-info}/top_level.txt +0 -0
pycupra/__version__.py
CHANGED
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
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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'
|
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.
|
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
|
-
|
97
|
-
|
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
|
108
|
-
hourago = datetime.now() - timedelta(hours =
|
119
|
+
# Rediscover if data is older than 2 hours
|
120
|
+
hourago = datetime.now() - timedelta(hours = 2)
|
109
121
|
if self._discovered < hourago:
|
110
|
-
|
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.
|
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
|
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('
|
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
|
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 <
|
325
|
-
await asyncio.sleep(
|
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
|
355
|
-
if not self._relevantCapabilties.get('departureTimers', {}).get('active', False) and
|
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
|
-
|
359
|
-
|
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 =
|
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
|
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
|
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 =
|
446
|
+
supported = "is_departure" + str(id) + "_supported"
|
401
447
|
if getattr(self, supported) is not True:
|
402
|
-
raise SeatConfigException(f'Timer id
|
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" :
|
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 <
|
554
|
-
await asyncio.sleep(
|
555
|
-
await self.
|
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
|
-
|
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 <
|
730
|
-
await asyncio.sleep(
|
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 == '
|
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.
|
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,,
|
pycupra-0.0.5.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|
File without changes
|