pycupra 0.1.6__py3-none-any.whl → 0.1.8__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 +35 -10
- pycupra/const.py +2 -1
- pycupra/dashboard.py +39 -0
- pycupra/firebase_messaging/fcmpushclient.py +21 -5
- pycupra/vehicle.py +170 -74
- {pycupra-0.1.6.dist-info → pycupra-0.1.8.dist-info}/METADATA +1 -1
- {pycupra-0.1.6.dist-info → pycupra-0.1.8.dist-info}/RECORD +11 -11
- {pycupra-0.1.6.dist-info → pycupra-0.1.8.dist-info}/WHEEL +0 -0
- {pycupra-0.1.6.dist-info → pycupra-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {pycupra-0.1.6.dist-info → pycupra-0.1.8.dist-info}/top_level.txt +0 -0
pycupra/__version__.py
CHANGED
pycupra/connection.py
CHANGED
@@ -109,13 +109,14 @@ TIMEOUT = timedelta(seconds=90)
|
|
109
109
|
class Connection:
|
110
110
|
""" Connection to Connect services """
|
111
111
|
# Init connection class
|
112
|
-
def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, **optional):
|
112
|
+
def __init__(self, session, brand='cupra', username='', password='', fulldebug=False, nightlyUpdateReduction=False, anonymise=True, tripStatisticsStartYear=None, **optional):
|
113
113
|
""" Initialize """
|
114
114
|
self._session = session
|
115
115
|
self._lock = asyncio.Lock()
|
116
116
|
self._session_fulldebug = fulldebug
|
117
117
|
self._session_nightlyUpdateReduction = nightlyUpdateReduction
|
118
118
|
self._session_anonymise = anonymise
|
119
|
+
self._session_tripStatisticsStartYear = tripStatisticsStartYear
|
119
120
|
self._session_headers = HEADERS_SESSION.get(brand).copy()
|
120
121
|
self._session_base = BASE_SESSION
|
121
122
|
self._session_auth_headers = HEADERS_AUTH.copy()
|
@@ -151,6 +152,7 @@ class Connection:
|
|
151
152
|
self.addToAnonymisationKeys('family_name')
|
152
153
|
self.addToAnonymisationKeys('birthdate')
|
153
154
|
self.addToAnonymisationKeys('vin')
|
155
|
+
self._error401 = False
|
154
156
|
|
155
157
|
|
156
158
|
def _clear_cookies(self):
|
@@ -596,22 +598,31 @@ class Connection:
|
|
596
598
|
'request_info': error.request_info
|
597
599
|
}
|
598
600
|
if error.status == 401:
|
599
|
-
_LOGGER.warning('Received "Unauthorized" while fetching data
|
601
|
+
_LOGGER.warning('Received "Unauthorized" while fetching data. This can occur if tokens expired or refresh service is unavailable.')
|
602
|
+
if self._error401 != True:
|
603
|
+
self._error401 = True
|
604
|
+
rc=await self.refresh_token(self._session_auth_brand)
|
605
|
+
if rc:
|
606
|
+
_LOGGER.info('Successfully refreshed tokens after error 401.')
|
607
|
+
self._error401 = False
|
608
|
+
#return True
|
609
|
+
else:
|
610
|
+
_LOGGER.info('Refresh of tokens after error 401 not successful.')
|
600
611
|
elif error.status == 400:
|
601
|
-
_LOGGER.error('Received "Bad Request" from server
|
612
|
+
_LOGGER.error('Received "Bad Request" from server. The request might be malformed or not implemented correctly for this vehicle.')
|
602
613
|
elif error.status == 412:
|
603
|
-
_LOGGER.debug('Received "Pre-condition failed"
|
614
|
+
_LOGGER.debug('Received "Pre-condition failed". Service might be temporarily unavailable.')
|
604
615
|
elif error.status == 500:
|
605
|
-
_LOGGER.info('Received "Internal server error"
|
616
|
+
_LOGGER.info('Received "Internal server error". The service is temporarily unavailable.')
|
606
617
|
elif error.status == 502:
|
607
|
-
_LOGGER.info('Received "Bad gateway"
|
618
|
+
_LOGGER.info('Received "Bad gateway". Either the endpoint is temporarily unavailable or not supported for this vehicle.')
|
608
619
|
elif 400 <= error.status <= 499:
|
609
620
|
_LOGGER.error('Received unhandled error indicating client-side problem.\nRestart or try again later.')
|
610
621
|
elif 500 <= error.status <= 599:
|
611
622
|
_LOGGER.error('Received unhandled error indicating server-side problem.\nThe service might be temporarily unavailable.')
|
612
623
|
else:
|
613
624
|
_LOGGER.error('Received unhandled error while requesting API endpoint.')
|
614
|
-
_LOGGER.debug(f'HTTP request information: {data}')
|
625
|
+
_LOGGER.debug(self.anonymise(f'HTTP request information: {data}'))
|
615
626
|
return data
|
616
627
|
except Exception as e:
|
617
628
|
_LOGGER.debug(f'Got non HTTP related error: {e}')
|
@@ -626,7 +637,12 @@ class Connection:
|
|
626
637
|
async def _request(self, method, url, **kwargs):
|
627
638
|
"""Perform a HTTP query"""
|
628
639
|
if self._session_fulldebug:
|
629
|
-
|
640
|
+
argsString =''
|
641
|
+
if len(kwargs)>0:
|
642
|
+
argsString = 'with '
|
643
|
+
for k, val in kwargs.items():
|
644
|
+
argsString = argsString + f"{k}=\'{val}\' "
|
645
|
+
_LOGGER.debug(self.anonymise(f'HTTP {method} "{url}" {argsString}'))
|
630
646
|
try:
|
631
647
|
if datetime.now(tz=None).date() != self._sessionRequestTimestamp.date():
|
632
648
|
# A new day has begun. Store _sessionRequestCounter in history and reset timestamp and counter
|
@@ -691,6 +707,9 @@ class Connection:
|
|
691
707
|
if self._session_fulldebug:
|
692
708
|
if 'image/png' in response.headers.get('Content-Type', ''):
|
693
709
|
_LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}]. Not showing response for Content-Type image/png.'))
|
710
|
+
elif method==METH_PUT or method==METH_DELETE:
|
711
|
+
# deepcopy() of res can produce errors, if res is the API response on PUT or DELETE
|
712
|
+
_LOGGER.debug(f'Request for "{self.anonymise(url)}" returned with status code [{response.status}]. Not showing response for http {method}')
|
694
713
|
else:
|
695
714
|
_LOGGER.debug(self.anonymise(f'Request for "{url}" returned with status code [{response.status}], response: {self.anonymise(deepcopy(res))}'))
|
696
715
|
else:
|
@@ -704,7 +723,7 @@ class Connection:
|
|
704
723
|
_LOGGER.debug(self.anonymise(f'Data call returned: {response}'))
|
705
724
|
return response
|
706
725
|
except aiohttp.client_exceptions.ClientResponseError as error:
|
707
|
-
_LOGGER.debug(f'Request failed. Data: {data}, HTTP request headers: {self._session_headers}')
|
726
|
+
_LOGGER.debug(self.anonymise(f'Request failed. Data: {data}, HTTP request headers: {self._session_headers}'))
|
708
727
|
if error.status == 401:
|
709
728
|
_LOGGER.error('Unauthorized')
|
710
729
|
elif error.status == 400:
|
@@ -1070,6 +1089,12 @@ class Connection:
|
|
1070
1089
|
async def getTripStatistics(self, vin, baseurl, supportsCyclicTrips):
|
1071
1090
|
"""Get short term and cyclic trip statistics."""
|
1072
1091
|
await self.set_token(self._session_auth_brand)
|
1092
|
+
if self._session_tripStatisticsStartYear==None:
|
1093
|
+
# If connection was not initialised with parameter tripStatisticsStartYear, then the value of the last year is used
|
1094
|
+
# (This keeps the statistics shorter in Home Assistant)
|
1095
|
+
startYear = datetime.now().year - 1
|
1096
|
+
else:
|
1097
|
+
startYear = self._session_tripStatisticsStartYear
|
1073
1098
|
try:
|
1074
1099
|
data={'tripstatistics': {}}
|
1075
1100
|
if supportsCyclicTrips:
|
@@ -1625,7 +1650,7 @@ class Connection:
|
|
1625
1650
|
if expires > now:
|
1626
1651
|
return expires
|
1627
1652
|
else:
|
1628
|
-
_LOGGER.debug(f'Token expired at {expires.strftime("%Y-%m-%d %H:%M:%S")}
|
1653
|
+
_LOGGER.debug(f'Token expired at {expires.strftime("%Y-%m-%d %H:%M:%S")}')
|
1629
1654
|
return False
|
1630
1655
|
except Exception as e:
|
1631
1656
|
_LOGGER.info(f'Token validation failed, {e}')
|
pycupra/const.py
CHANGED
@@ -138,7 +138,7 @@ API_DEPARTURE_TIMERS = '{baseurl}/v1/vehicles/{vin}/departure-timers'
|
|
138
138
|
API_DEPARTURE_PROFILES = '{baseurl}/v1/vehicles/{vin}/departure/profiles' # Departure profiles
|
139
139
|
API_POSITION = '{baseurl}/v1/vehicles/{vin}/parkingposition' # Position data
|
140
140
|
API_POS_TO_ADDRESS= 'https://maps.googleapis.com/maps/api/directions/json?origin={lat},{lon}&destination={lat},{lon}&traffic_model=best_guess&departure_time=now&language=de&key={apiKeyForGoogle}&mode=driving'
|
141
|
-
API_TRIP = '{baseurl}/v1/vehicles/{vin}/driving-data/{dataType}?from=
|
141
|
+
API_TRIP = '{baseurl}/v1/vehicles/{vin}/driving-data/{dataType}?from={startYear}-07-01T00:00:00Z&to=2099-12-31T09:59:01Z' # Trip statistics (whole history) SHORT/LONG/CYCLIC (WEEK only with from)
|
142
142
|
API_MILEAGE = '{baseurl}/v1/vehicles/{vin}/mileage' # Total km etc
|
143
143
|
API_MAINTENANCE = '{baseurl}/v1/vehicles/{vin}/maintenance' # Inspection information
|
144
144
|
API_MEASUREMENTS = '{baseurl}/v1/vehicles/{vin}/measurements/engines' # ???
|
@@ -192,3 +192,4 @@ FIREBASE_STATUS_NOT_INITIALISED= 0
|
|
192
192
|
FIREBASE_STATUS_ACTIVATED= 1
|
193
193
|
FIREBASE_STATUS_NOT_WANTED= -2
|
194
194
|
FIREBASE_STATUS_ACTIVATION_FAILED= -1
|
195
|
+
FIREBASE_STATUS_ACTIVATION_STOPPED= -3
|
pycupra/dashboard.py
CHANGED
@@ -755,6 +755,18 @@ class Warnings(Sensor):
|
|
755
755
|
def assumed_state(self):
|
756
756
|
return False
|
757
757
|
|
758
|
+
@property
|
759
|
+
def attributes(self):
|
760
|
+
attrs = {'warnings': 'No warnings'}
|
761
|
+
if self.vehicle.attrs.get('warninglights', {}).get('statuses',[]):
|
762
|
+
warningTextList = []
|
763
|
+
for elem in self.vehicle.attrs['warninglights']['statuses']:
|
764
|
+
if isinstance(elem, dict):
|
765
|
+
if elem.get('text',''):
|
766
|
+
warningTextList.append(elem.get('text',''))
|
767
|
+
attrs['warnings'] = warningTextList
|
768
|
+
return attrs
|
769
|
+
|
758
770
|
class DepartureTimer1(Switch):
|
759
771
|
def __init__(self):
|
760
772
|
super().__init__(attr="departure1", name="Departure timer 1", icon="mdi:radiator")
|
@@ -990,6 +1002,32 @@ class ChargingState(BinarySensor):
|
|
990
1002
|
attr['mode']=mode
|
991
1003
|
return attr
|
992
1004
|
|
1005
|
+
class AreaAlarm(BinarySensor):
|
1006
|
+
def __init__(self):
|
1007
|
+
super().__init__(attr="area_alarm", name="Area alarm", icon="mdi:alarm-light", device_class='safety')
|
1008
|
+
|
1009
|
+
@property
|
1010
|
+
def state(self):
|
1011
|
+
return self.vehicle.area_alarm
|
1012
|
+
|
1013
|
+
@property
|
1014
|
+
def assumed_state(self):
|
1015
|
+
return False
|
1016
|
+
|
1017
|
+
@property
|
1018
|
+
def attributes(self):
|
1019
|
+
attr = {}
|
1020
|
+
type = self.vehicle.attrs.get('areaAlarm', {}).get('type', '')
|
1021
|
+
zones = self.vehicle.attrs.get('areaAlarm', {}).get('zones', [])
|
1022
|
+
timestamp = self.vehicle.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
1023
|
+
if type != '':
|
1024
|
+
attr['type']=type
|
1025
|
+
if len(zones) > 0:
|
1026
|
+
attr['zone']=zones[0]
|
1027
|
+
if timestamp != 0:
|
1028
|
+
attr['timestamp']=timestamp
|
1029
|
+
return attr
|
1030
|
+
|
993
1031
|
def create_instruments():
|
994
1032
|
return [
|
995
1033
|
Position(),
|
@@ -1018,6 +1056,7 @@ def create_instruments():
|
|
1018
1056
|
DepartureProfile2(),
|
1019
1057
|
DepartureProfile3(),
|
1020
1058
|
ChargingState(),
|
1059
|
+
AreaAlarm(),
|
1021
1060
|
Sensor(
|
1022
1061
|
attr="distance",
|
1023
1062
|
name="Odometer",
|
@@ -92,10 +92,10 @@ class FcmPushClientConfig: # pylint:disable=too-many-instance-attributes
|
|
92
92
|
"""Class to provide configuration to
|
93
93
|
:class:`firebase_messaging.FcmPushClientConfig`.FcmPushClient."""
|
94
94
|
|
95
|
-
server_heartbeat_interval: int | None =
|
95
|
+
server_heartbeat_interval: int | None = 30 # original value was 10
|
96
96
|
"""Time in seconds to request the server to send heartbeats"""
|
97
97
|
|
98
|
-
client_heartbeat_interval: int | None =
|
98
|
+
client_heartbeat_interval: int | None = 40 # original value was 20
|
99
99
|
"""Time in seconds to send heartbeats to the server"""
|
100
100
|
|
101
101
|
send_selective_acknowledgements: bool = True
|
@@ -218,6 +218,7 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
218
218
|
or (self.stopping_lock and self.stopping_lock.locked())
|
219
219
|
or not self.do_listen
|
220
220
|
):
|
221
|
+
_logger.debug(f"In _reset. reset_lock={self.reset_lock}, reset_lock.locked={self.reset_lock.locked()}, stopping_lock={self.stopping_lock}, stopping_lock.locked={self.stopping_lock.locked()}, do_listen={self.do_listen}")
|
221
222
|
return
|
222
223
|
|
223
224
|
async with self.reset_lock: # type: ignore[union-attr]
|
@@ -599,10 +600,21 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
599
600
|
return
|
600
601
|
|
601
602
|
if isinstance(msg, DataMessageStanza):
|
602
|
-
await self._handle_data_message(msg)
|
603
|
-
self.persistent_ids.append(msg.persistent_id)
|
603
|
+
#await self._handle_data_message(msg)
|
604
|
+
#self.persistent_ids.append(msg.persistent_id)
|
605
|
+
#if self.config.send_selective_acknowledgements:
|
606
|
+
# await self._send_selective_ack(msg.persistent_id)
|
604
607
|
if self.config.send_selective_acknowledgements:
|
605
|
-
|
608
|
+
# As handle_data_message with the callback of onNotification can take some time, send_selective_ack is called in parallel
|
609
|
+
await asyncio.gather(
|
610
|
+
self._handle_data_message(msg),
|
611
|
+
self._send_selective_ack(msg.persistent_id),
|
612
|
+
return_exceptions=True
|
613
|
+
)
|
614
|
+
self.persistent_ids.append(msg.persistent_id),
|
615
|
+
else:
|
616
|
+
await self._handle_data_message(msg)
|
617
|
+
self.persistent_ids.append(msg.persistent_id)
|
606
618
|
elif isinstance(msg, HeartbeatPing):
|
607
619
|
await self._handle_ping(msg)
|
608
620
|
elif isinstance(msg, HeartbeatAck):
|
@@ -714,7 +726,10 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
714
726
|
else:
|
715
727
|
_logger.exception("Unexpected exception during read\n")
|
716
728
|
if self._try_increment_error_count(ErrorType.CONNECTION):
|
729
|
+
_logger.debug("Calling reset()\n")
|
717
730
|
await self._reset()
|
731
|
+
else:
|
732
|
+
_logger.debug("Not calling reset()\n")
|
718
733
|
except Exception as ex:
|
719
734
|
_logger.error(
|
720
735
|
"Unknown error: %s, shutting down FcmPushClient.\n%s",
|
@@ -794,3 +809,4 @@ class FcmPushClient: # pylint:disable=too-many-instance-attributes
|
|
794
809
|
dms.persistent_id = persistent_id
|
795
810
|
|
796
811
|
# Not supported yet
|
812
|
+
|
pycupra/vehicle.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
import re
|
5
5
|
import logging
|
6
6
|
import asyncio
|
7
|
+
import json
|
7
8
|
|
8
9
|
from copy import deepcopy
|
9
10
|
from datetime import datetime, timedelta, timezone
|
@@ -24,6 +25,7 @@ from .const import (
|
|
24
25
|
FIREBASE_STATUS_NOT_INITIALISED,
|
25
26
|
FIREBASE_STATUS_ACTIVATED,
|
26
27
|
FIREBASE_STATUS_ACTIVATION_FAILED,
|
28
|
+
FIREBASE_STATUS_ACTIVATION_STOPPED,
|
27
29
|
FIREBASE_STATUS_NOT_WANTED,
|
28
30
|
)
|
29
31
|
|
@@ -71,7 +73,6 @@ class Vehicle:
|
|
71
73
|
self._relevantCapabilties = {
|
72
74
|
'measurements': {'active': False, 'reason': 'not supported', },
|
73
75
|
'climatisation': {'active': False, 'reason': 'not supported'},
|
74
|
-
#'parkingInformation': {'active': False, 'reason': 'not supported'},
|
75
76
|
'tripStatistics': {'active': False, 'reason': 'not supported', 'supportsCyclicTrips': False},
|
76
77
|
'vehicleHealthInspection': {'active': False, 'reason': 'not supported'},
|
77
78
|
'vehicleHealthWarnings': {'active': False, 'reason': 'not supported'},
|
@@ -94,6 +95,7 @@ class Vehicle:
|
|
94
95
|
self._last_get_charger = datetime.now(tz=None) - timedelta(seconds=600)
|
95
96
|
self._last_get_climater = datetime.now(tz=None) - timedelta(seconds=600)
|
96
97
|
self._last_get_mileage = datetime.now(tz=None) - timedelta(seconds=600)
|
98
|
+
self._last_get_position = datetime.now(tz=None) - timedelta(seconds=600)
|
97
99
|
|
98
100
|
|
99
101
|
#### API get and set functions ####
|
@@ -130,8 +132,7 @@ class Vehicle:
|
|
130
132
|
self._relevantCapabilties[id].update(data)
|
131
133
|
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
+
|
135
136
|
# Get URLs for model image
|
136
137
|
self._modelimages = await self.get_modelimageurl()
|
137
138
|
|
@@ -147,12 +148,22 @@ class Vehicle:
|
|
147
148
|
hourago = datetime.now() - timedelta(hours = 2)
|
148
149
|
if self._discovered < hourago:
|
149
150
|
await self.discover()
|
150
|
-
#_LOGGER.debug('Achtung! self.discover() auskommentiert')
|
151
151
|
|
152
152
|
# Fetch all data if car is not deactivated
|
153
153
|
if not self.deactivated:
|
154
154
|
try:
|
155
|
+
if self.attrs.get('areaAlarm', {}) !={}:
|
156
|
+
# Delete an area alarm if it is older than 900 seconds
|
157
|
+
alarmTimestamp = self.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
158
|
+
if alarmTimestamp < datetime.now(tz=None) - timedelta(seconds= 900):
|
159
|
+
self.attrs.pop("areaAlarm")
|
160
|
+
|
155
161
|
if self.firebaseStatus == FIREBASE_STATUS_ACTIVATED:
|
162
|
+
# Check, if fcmpushclient still started
|
163
|
+
if not self.firebase._pushClient.is_started():
|
164
|
+
_LOGGER.warning(f'firebaseStatus={self.firebaseStatus}, but state of push client is not started. Changing firebaseStatus to {FIREBASE_STATUS_ACTIVATION_STOPPED}')
|
165
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_STOPPED
|
166
|
+
|
156
167
|
fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1700)
|
157
168
|
oldMileage = self.distance
|
158
169
|
if self._last_get_mileage < datetime.now(tz=None) - timedelta(seconds= 300):
|
@@ -164,6 +175,26 @@ class Vehicle:
|
|
164
175
|
else:
|
165
176
|
fullUpdateExpired = datetime.now(tz=None) - timedelta(seconds= 1100)
|
166
177
|
|
178
|
+
if self.firebaseStatus == FIREBASE_STATUS_ACTIVATION_STOPPED:
|
179
|
+
# Trying to activate firebase connection again
|
180
|
+
"""_LOGGER.debug(f'As firebase status={self.firebaseStatus}, fcmpushclient.start() is called.')
|
181
|
+
await self.firebase._pushClient.start()
|
182
|
+
#await asyncio.sleep(5)
|
183
|
+
if self.firebase._pushClient.is_started():
|
184
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
185
|
+
_LOGGER.debug(f'Successfully restarted push client. New firebase status={self.firebaseStatus}')
|
186
|
+
else:
|
187
|
+
_LOGGER.warning(f'Restart of push client failed. Firebase status={self.firebaseStatus}')"""
|
188
|
+
newStatus = await self.stopFirebase()
|
189
|
+
if newStatus != FIREBASE_STATUS_NOT_INITIALISED:
|
190
|
+
_LOGGER.debug(f'stopFirebase() not successful.')
|
191
|
+
newStatus = await self.initialiseFirebase(self._firebaseCredentialsFileName, self.updateCallback)
|
192
|
+
if newStatus == FIREBASE_STATUS_ACTIVATED:
|
193
|
+
_LOGGER.debug(f'Reinitialisation of firebase successful.New firebase status={self.firebaseStatus}.')
|
194
|
+
else:
|
195
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATION_STOPPED
|
196
|
+
_LOGGER.warning(f'Reinitialisation of firebase failed. New firebase status={self.firebaseStatus}.')
|
197
|
+
|
167
198
|
if self._connection._session_nightlyUpdateReduction:
|
168
199
|
# nightlyUpdateReduction is activated
|
169
200
|
if datetime.now(tz=None).hour<5 or datetime.now(tz=None).hour>=22:
|
@@ -192,6 +223,7 @@ class Vehicle:
|
|
192
223
|
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
|
193
224
|
await self.get_mileage()
|
194
225
|
|
226
|
+
|
195
227
|
await asyncio.gather(
|
196
228
|
#self.get_statusreport(),
|
197
229
|
self.get_charger(),
|
@@ -227,6 +259,10 @@ class Vehicle:
|
|
227
259
|
data = await self._connection.getBasicCarData(self.vin, self._apibase)
|
228
260
|
if data:
|
229
261
|
self._states.update(data)
|
262
|
+
return True
|
263
|
+
else:
|
264
|
+
_LOGGER.debug('Could not fetch basic car data')
|
265
|
+
return False
|
230
266
|
|
231
267
|
async def get_mileage(self):
|
232
268
|
"""Fetch basic car data."""
|
@@ -234,6 +270,10 @@ class Vehicle:
|
|
234
270
|
if data:
|
235
271
|
self._states.update(data)
|
236
272
|
self._last_get_mileage = datetime.now(tz=None)
|
273
|
+
return True
|
274
|
+
else:
|
275
|
+
_LOGGER.debug('Could not fetch mileage data')
|
276
|
+
return False
|
237
277
|
|
238
278
|
async def get_preheater(self):
|
239
279
|
"""Fetch pre-heater data if function is enabled."""
|
@@ -255,8 +295,10 @@ class Vehicle:
|
|
255
295
|
if data:
|
256
296
|
self._states.update(data)
|
257
297
|
self._last_get_climater = datetime.now(tz=None)
|
298
|
+
return True
|
258
299
|
else:
|
259
300
|
_LOGGER.debug('Could not fetch climater data')
|
301
|
+
return False
|
260
302
|
#else:
|
261
303
|
# self._requests.pop('climatisation', None)
|
262
304
|
|
@@ -266,8 +308,10 @@ class Vehicle:
|
|
266
308
|
data = await self._connection.getTripStatistics(self.vin, self._apibase, self._relevantCapabilties['tripStatistics'].get('supportsCyclicTrips', False))
|
267
309
|
if data:
|
268
310
|
self._states.update(data)
|
311
|
+
return True
|
269
312
|
else:
|
270
313
|
_LOGGER.debug('Could not fetch trip statistics')
|
314
|
+
return False
|
271
315
|
|
272
316
|
async def get_position(self):
|
273
317
|
"""Fetch position data if function is enabled."""
|
@@ -284,24 +328,21 @@ class Vehicle:
|
|
284
328
|
except:
|
285
329
|
pass
|
286
330
|
self._states.update(data)
|
331
|
+
self._last_get_position = datetime.now(tz=None)
|
332
|
+
return True
|
287
333
|
else:
|
288
334
|
_LOGGER.debug('Could not fetch any positional data')
|
335
|
+
return False
|
289
336
|
|
290
337
|
async def get_vehicleHealthWarnings(self):
|
291
338
|
if self._relevantCapabilties.get('vehicleHealthWarnings', {}).get('active', False):
|
292
339
|
data = await self._connection.getVehicleHealthWarnings(self.vin, self._apibase)
|
293
340
|
if data:
|
294
|
-
#warningsList = data.get('warninglights',{}).get('statuses',[])
|
295
|
-
#for i in range(len(warningsList)):
|
296
|
-
# _LOGGER.debug(f'Element {i} in warninglights: {warningsList[i]}')
|
297
|
-
# if isinstance(warningsList[i], dict):
|
298
|
-
# if warningsList[i].get('icon',''):
|
299
|
-
# #Value of icon is very long and can lead to problems
|
300
|
-
# _LOGGER.debug(f'Substituting value of icon by \'DELETED\'')
|
301
|
-
# data['warninglights']['statuses'][i]['icon']='DELETED'
|
302
341
|
self._states.update(data)
|
342
|
+
return True
|
303
343
|
else:
|
304
344
|
_LOGGER.debug('Could not fetch vehicle health warnings')
|
345
|
+
return False
|
305
346
|
|
306
347
|
async def get_statusreport(self):
|
307
348
|
"""Fetch status data if function is enabled."""
|
@@ -310,8 +351,10 @@ class Vehicle:
|
|
310
351
|
if data:
|
311
352
|
self._states.update(data)
|
312
353
|
self._last_get_statusreport = datetime.now(tz=None)
|
354
|
+
return True
|
313
355
|
else:
|
314
356
|
_LOGGER.debug('Could not fetch status report')
|
357
|
+
return False
|
315
358
|
|
316
359
|
async def get_maintenance(self):
|
317
360
|
"""Fetch maintenance data if function is enabled."""
|
@@ -319,8 +362,10 @@ class Vehicle:
|
|
319
362
|
data = await self._connection.getMaintenance(self.vin, self._apibase)
|
320
363
|
if data:
|
321
364
|
self._states.update(data)
|
365
|
+
return True
|
322
366
|
else:
|
323
367
|
_LOGGER.debug('Could not fetch status report')
|
368
|
+
return False
|
324
369
|
|
325
370
|
async def get_charger(self):
|
326
371
|
"""Fetch charger data if function is enabled."""
|
@@ -329,8 +374,10 @@ class Vehicle:
|
|
329
374
|
if data:
|
330
375
|
self._states.update(data)
|
331
376
|
self._last_get_charger = datetime.now(tz=None)
|
377
|
+
return True
|
332
378
|
else:
|
333
379
|
_LOGGER.debug('Could not fetch charger data')
|
380
|
+
return False
|
334
381
|
|
335
382
|
async def get_departure_timers(self):
|
336
383
|
"""Fetch timer data if function is enabled."""
|
@@ -339,8 +386,10 @@ class Vehicle:
|
|
339
386
|
if data:
|
340
387
|
self._states.update(data)
|
341
388
|
self._last_get_departure_timers = datetime.now(tz=None)
|
389
|
+
return True
|
342
390
|
else:
|
343
391
|
_LOGGER.debug('Could not fetch timers')
|
392
|
+
return False
|
344
393
|
|
345
394
|
async def get_departure_profiles(self):
|
346
395
|
"""Fetch timer data if function is enabled."""
|
@@ -349,8 +398,10 @@ class Vehicle:
|
|
349
398
|
if data:
|
350
399
|
self._states.update(data)
|
351
400
|
self._last_get_departure_profiles = datetime.now(tz=None)
|
401
|
+
return True
|
352
402
|
else:
|
353
403
|
_LOGGER.debug('Could not fetch timers')
|
404
|
+
return False
|
354
405
|
|
355
406
|
#async def wait_for_request(self, section, request, retryCount=36):
|
356
407
|
"""Update status of outstanding requests."""
|
@@ -983,7 +1034,7 @@ class Vehicle:
|
|
983
1034
|
async def set_climatisation_temp(self, temperature=20):
|
984
1035
|
"""Set climatisation target temp."""
|
985
1036
|
if self.is_electric_climatisation_supported or self.is_auxiliary_climatisation_supported:
|
986
|
-
if 16 <=
|
1037
|
+
if 16 <= float(temperature) <= 30:
|
987
1038
|
data = {
|
988
1039
|
'climatisationWithoutExternalPower': self.climatisation_without_external_power,
|
989
1040
|
'targetTemperature': temperature,
|
@@ -1034,28 +1085,30 @@ class Vehicle:
|
|
1034
1085
|
data = {}
|
1035
1086
|
# Validate user input
|
1036
1087
|
if mode.lower() not in ['electric', 'auxiliary', 'start', 'stop', 'on', 'off']:
|
1088
|
+
_LOGGER.error(f"Invalid mode for 'set_climatisation': {mode}")
|
1037
1089
|
raise SeatInvalidRequestException(f"Invalid mode for set_climatisation: {mode}")
|
1038
1090
|
elif mode == 'auxiliary' and spin is None:
|
1039
1091
|
raise SeatInvalidRequestException("Starting auxiliary heater requires provided S-PIN")
|
1040
1092
|
if temp is not None:
|
1041
|
-
if not isinstance(temp, float):
|
1093
|
+
if not isinstance(temp, float) and not isinstance(temp, int):
|
1094
|
+
_LOGGER.error(f"Invalid type for temp. type={type(temp)}")
|
1042
1095
|
raise SeatInvalidRequestException(f"Invalid type for temp")
|
1043
1096
|
elif not 16 <= float(temp) <=30:
|
1044
1097
|
raise SeatInvalidRequestException(f"Invalid value for temp")
|
1045
1098
|
else:
|
1046
1099
|
temp = self.climatisation_target_temperature
|
1047
|
-
if hvpower is not None:
|
1048
|
-
|
1049
|
-
|
1100
|
+
#if hvpower is not None:
|
1101
|
+
# if not isinstance(hvpower, bool):
|
1102
|
+
# raise SeatInvalidRequestException(f"Invalid type for hvpower")
|
1050
1103
|
if self.is_electric_climatisation_supported:
|
1051
1104
|
if self._relevantCapabilties.get('climatisation', {}).get('active', False):
|
1052
1105
|
if mode in ['Start', 'start', 'Electric', 'electric', 'On', 'on']:
|
1053
1106
|
mode = 'start'
|
1054
1107
|
if mode in ['start', 'auxiliary']:
|
1055
|
-
if hvpower is not None:
|
1056
|
-
|
1057
|
-
else:
|
1058
|
-
|
1108
|
+
#if hvpower is not None:
|
1109
|
+
# withoutHVPower = hvpower
|
1110
|
+
#else:
|
1111
|
+
# withoutHVPower = self.climatisation_without_external_power
|
1059
1112
|
data = {
|
1060
1113
|
'targetTemperature': temp,
|
1061
1114
|
'targetTemperatureUnit': 'celsius',
|
@@ -1142,7 +1195,7 @@ class Vehicle:
|
|
1142
1195
|
self._requests['climatisation'] = {'status': 'Exception'}
|
1143
1196
|
raise SeatException('Climatisation action failed')
|
1144
1197
|
|
1145
|
-
|
1198
|
+
# Parking heater heating/ventilation (RS)
|
1146
1199
|
async def set_pheater(self, mode, spin):
|
1147
1200
|
"""Set the mode for the parking heater."""
|
1148
1201
|
if not self.is_pheater_heating_supported:
|
@@ -1185,7 +1238,7 @@ class Vehicle:
|
|
1185
1238
|
self._requests['preheater'] = {'status': 'Exception'}
|
1186
1239
|
raise SeatException('Pre-heater action failed')
|
1187
1240
|
|
1188
|
-
|
1241
|
+
# Lock
|
1189
1242
|
async def set_lock(self, action, spin):
|
1190
1243
|
"""Remote lock and unlock actions."""
|
1191
1244
|
#if not self._services.get('rlu_v1', False):
|
@@ -1247,7 +1300,7 @@ class Vehicle:
|
|
1247
1300
|
self._requests['lock'] = {'status': 'Exception'}
|
1248
1301
|
raise SeatException('Lock action failed')
|
1249
1302
|
|
1250
|
-
|
1303
|
+
# Honk and flash (RHF)
|
1251
1304
|
async def set_honkandflash(self, action, lat=None, lng=None):
|
1252
1305
|
"""Turn on/off honk and flash."""
|
1253
1306
|
if not self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
|
@@ -1304,7 +1357,7 @@ class Vehicle:
|
|
1304
1357
|
self._requests['honkandflash'] = {'status': 'Exception'}
|
1305
1358
|
raise SeatException('Honk and flash action failed')
|
1306
1359
|
|
1307
|
-
|
1360
|
+
# Refresh vehicle data (VSR)
|
1308
1361
|
async def set_refresh(self):
|
1309
1362
|
"""Wake up vehicle and update status data."""
|
1310
1363
|
if not self._relevantCapabilties.get('state', {}).get('active', False):
|
@@ -1340,7 +1393,7 @@ class Vehicle:
|
|
1340
1393
|
raise SeatException('Data refresh failed')
|
1341
1394
|
|
1342
1395
|
#### Vehicle class helpers ####
|
1343
|
-
|
1396
|
+
# Vehicle info
|
1344
1397
|
@property
|
1345
1398
|
def attrs(self):
|
1346
1399
|
return self._states
|
@@ -1373,7 +1426,7 @@ class Vehicle:
|
|
1373
1426
|
|
1374
1427
|
|
1375
1428
|
#### Information from vehicle states ####
|
1376
|
-
|
1429
|
+
# Car information
|
1377
1430
|
@property
|
1378
1431
|
def nickname(self):
|
1379
1432
|
return self._properties.get('vehicleNickname', '')
|
@@ -1462,7 +1515,7 @@ class Vehicle:
|
|
1462
1515
|
if self._modelimages is not None:
|
1463
1516
|
return True
|
1464
1517
|
|
1465
|
-
|
1518
|
+
# Lights
|
1466
1519
|
@property
|
1467
1520
|
def parking_light(self):
|
1468
1521
|
"""Return true if parking light is on"""
|
@@ -1481,7 +1534,7 @@ class Vehicle:
|
|
1481
1534
|
else:
|
1482
1535
|
return False
|
1483
1536
|
|
1484
|
-
|
1537
|
+
# Connection status
|
1485
1538
|
@property
|
1486
1539
|
def last_connected(self):
|
1487
1540
|
"""Return when vehicle was last connected to connect servers."""
|
@@ -1498,7 +1551,7 @@ class Vehicle:
|
|
1498
1551
|
if 'updatedAt' in self.attrs.get('status', {}):
|
1499
1552
|
return True
|
1500
1553
|
|
1501
|
-
|
1554
|
+
# Update status
|
1502
1555
|
@property
|
1503
1556
|
def last_full_update(self):
|
1504
1557
|
"""Return when the last full update for the vehicle took place."""
|
@@ -1510,7 +1563,7 @@ class Vehicle:
|
|
1510
1563
|
if hasattr(self,'_last_full_update'):
|
1511
1564
|
return True
|
1512
1565
|
|
1513
|
-
|
1566
|
+
# Service information
|
1514
1567
|
@property
|
1515
1568
|
def distance(self):
|
1516
1569
|
"""Return vehicle odometer."""
|
@@ -1598,7 +1651,7 @@ class Vehicle:
|
|
1598
1651
|
return True
|
1599
1652
|
return False
|
1600
1653
|
|
1601
|
-
|
1654
|
+
# Charger related states for EV and PHEV
|
1602
1655
|
@property
|
1603
1656
|
def charging(self):
|
1604
1657
|
"""Return battery level"""
|
@@ -1857,7 +1910,7 @@ class Vehicle:
|
|
1857
1910
|
if self.attrs.get('charging', {}).get('info', {}).get('settings', {}).get('targetSoc', False):
|
1858
1911
|
return True
|
1859
1912
|
|
1860
|
-
|
1913
|
+
# Vehicle location states
|
1861
1914
|
@property
|
1862
1915
|
def position(self):
|
1863
1916
|
"""Return position."""
|
@@ -1924,7 +1977,7 @@ class Vehicle:
|
|
1924
1977
|
if 'parkingTimeUTC' in self.attrs.get('findCarResponse', {}):
|
1925
1978
|
return True
|
1926
1979
|
|
1927
|
-
|
1980
|
+
# Vehicle fuel level and range
|
1928
1981
|
@property
|
1929
1982
|
def primary_range(self):
|
1930
1983
|
value = -1
|
@@ -2063,7 +2116,7 @@ class Vehicle:
|
|
2063
2116
|
return self.is_secondary_range_supported
|
2064
2117
|
return False
|
2065
2118
|
|
2066
|
-
|
2119
|
+
# Climatisation settings
|
2067
2120
|
@property
|
2068
2121
|
def climatisation_target_temperature(self):
|
2069
2122
|
"""Return the target temperature from climater."""
|
@@ -2137,7 +2190,7 @@ class Vehicle:
|
|
2137
2190
|
else:
|
2138
2191
|
return False
|
2139
2192
|
|
2140
|
-
|
2193
|
+
# Climatisation, electric
|
2141
2194
|
@property
|
2142
2195
|
def electric_climatisation_attributes(self):
|
2143
2196
|
"""Return climatisation attributes."""
|
@@ -2236,14 +2289,7 @@ class Vehicle:
|
|
2236
2289
|
@property
|
2237
2290
|
def warnings(self):
|
2238
2291
|
"""Return warnings."""
|
2239
|
-
|
2240
|
-
warningTextList = []
|
2241
|
-
for elem in self.attrs['warninglights']['statuses']:
|
2242
|
-
if isinstance(elem, dict):
|
2243
|
-
if elem.get('text',''):
|
2244
|
-
warningTextList.append(elem.get('text',''))
|
2245
|
-
return warningTextList
|
2246
|
-
return 'No warnings'
|
2292
|
+
return len(self.attrs.get('warninglights', {}).get('statuses',[]))
|
2247
2293
|
|
2248
2294
|
@property
|
2249
2295
|
def is_warnings_supported(self):
|
@@ -2252,7 +2298,7 @@ class Vehicle:
|
|
2252
2298
|
return True
|
2253
2299
|
return False
|
2254
2300
|
|
2255
|
-
|
2301
|
+
# Parking heater, "legacy" auxiliary climatisation
|
2256
2302
|
@property
|
2257
2303
|
def pheater_duration(self):
|
2258
2304
|
return self._climate_duration
|
@@ -2300,7 +2346,7 @@ class Vehicle:
|
|
2300
2346
|
if self.attrs.get('heating', {}).get('climatisationStateReport', {}).get('climatisationState', False):
|
2301
2347
|
return True
|
2302
2348
|
|
2303
|
-
|
2349
|
+
# Windows
|
2304
2350
|
@property
|
2305
2351
|
def windows_closed(self):
|
2306
2352
|
return (self.window_closed_left_front and self.window_closed_left_back and self.window_closed_right_front and self.window_closed_right_back)
|
@@ -2407,7 +2453,7 @@ class Vehicle:
|
|
2407
2453
|
# response = self.attrs.get('status')['windows'].get('sunRoof', '')
|
2408
2454
|
return True if response != '' else False
|
2409
2455
|
|
2410
|
-
|
2456
|
+
# Locks
|
2411
2457
|
@property
|
2412
2458
|
def door_locked(self):
|
2413
2459
|
# LEFT FRONT
|
@@ -2450,7 +2496,7 @@ class Vehicle:
|
|
2450
2496
|
return True
|
2451
2497
|
return False
|
2452
2498
|
|
2453
|
-
|
2499
|
+
# Doors, hood and trunk
|
2454
2500
|
@property
|
2455
2501
|
def hood_closed(self):
|
2456
2502
|
"""Return true if hood is closed"""
|
@@ -2535,7 +2581,7 @@ class Vehicle:
|
|
2535
2581
|
response = self.attrs.get('status')['trunk'].get('open', 0)
|
2536
2582
|
return True if response != 0 else False
|
2537
2583
|
|
2538
|
-
|
2584
|
+
# Departure timers
|
2539
2585
|
@property
|
2540
2586
|
def departure1(self):
|
2541
2587
|
"""Return timer status and attributes."""
|
@@ -2647,7 +2693,7 @@ class Vehicle:
|
|
2647
2693
|
return True
|
2648
2694
|
return False
|
2649
2695
|
|
2650
|
-
|
2696
|
+
# Departure profiles
|
2651
2697
|
@property
|
2652
2698
|
def departure_profile1(self):
|
2653
2699
|
"""Return profile status and attributes."""
|
@@ -2720,7 +2766,7 @@ class Vehicle:
|
|
2720
2766
|
return True
|
2721
2767
|
return False
|
2722
2768
|
|
2723
|
-
|
2769
|
+
# Trip data
|
2724
2770
|
@property
|
2725
2771
|
def trip_last_entry(self):
|
2726
2772
|
return self.attrs.get('tripstatistics', {}).get('short', [{},{}])[-1]
|
@@ -2945,7 +2991,26 @@ class Vehicle:
|
|
2945
2991
|
if response and type(response.get('totalElectricConsumption', None)) in (float, int):
|
2946
2992
|
return True
|
2947
2993
|
|
2948
|
-
#
|
2994
|
+
# Area alarm
|
2995
|
+
@property
|
2996
|
+
def area_alarm(self):
|
2997
|
+
"""Return True, if attribute areaAlarm is not {}"""
|
2998
|
+
alarmPresent = self.attrs.get('areaAlarm', {})
|
2999
|
+
if alarmPresent !={}:
|
3000
|
+
# Delete an area alarm if it is older than 900 seconds
|
3001
|
+
alarmTimestamp = self.attrs.get('areaAlarm', {}).get('timestamp', 0)
|
3002
|
+
if alarmTimestamp < datetime.now(tz=None) - timedelta(seconds= 900):
|
3003
|
+
self.attrs.pop("areaAlarm")
|
3004
|
+
alarmPresent = {}
|
3005
|
+
return False if alarmPresent == {} else True
|
3006
|
+
|
3007
|
+
@property
|
3008
|
+
def is_area_alarm_supported(self):
|
3009
|
+
"""Return True, if vehicle supports area alarm (always True at the moment)"""
|
3010
|
+
# Always True at the moment. Have to check, if the geofence capability is a necessary condition
|
3011
|
+
return True
|
3012
|
+
|
3013
|
+
# Status of set data requests
|
2949
3014
|
@property
|
2950
3015
|
def refresh_action_status(self):
|
2951
3016
|
"""Return latest status of data refresh request."""
|
@@ -3050,7 +3115,7 @@ class Vehicle:
|
|
3050
3115
|
"""Data update is supported."""
|
3051
3116
|
return True
|
3052
3117
|
|
3053
|
-
|
3118
|
+
# Honk and flash
|
3054
3119
|
@property
|
3055
3120
|
def request_honkandflash(self):
|
3056
3121
|
"""State is always False"""
|
@@ -3073,7 +3138,7 @@ class Vehicle:
|
|
3073
3138
|
if self._relevantCapabilties.get('honkAndFlash', {}).get('active', False):
|
3074
3139
|
return True
|
3075
3140
|
|
3076
|
-
|
3141
|
+
# Requests data
|
3077
3142
|
@property
|
3078
3143
|
def request_in_progress(self):
|
3079
3144
|
"""Request in progress is always supported."""
|
@@ -3128,7 +3193,7 @@ class Vehicle:
|
|
3128
3193
|
#if self.is_request_in_progress_supported:
|
3129
3194
|
# return True if self._requests.get('remaining', False) else False
|
3130
3195
|
|
3131
|
-
|
3196
|
+
#### Helper functions ####
|
3132
3197
|
def __str__(self):
|
3133
3198
|
return self.vin
|
3134
3199
|
|
@@ -3147,7 +3212,7 @@ class Vehicle:
|
|
3147
3212
|
|
3148
3213
|
async def stopFirebase(self):
|
3149
3214
|
# Check if firebase is activated
|
3150
|
-
if self.firebaseStatus
|
3215
|
+
if self.firebaseStatus not in (FIREBASE_STATUS_ACTIVATED, FIREBASE_STATUS_ACTIVATION_STOPPED):
|
3151
3216
|
_LOGGER.info(f'No need to stop firebase. Firebase status={self.firebaseStatus}')
|
3152
3217
|
return self.firebaseStatus
|
3153
3218
|
|
@@ -3161,7 +3226,7 @@ class Vehicle:
|
|
3161
3226
|
_LOGGER.warning('Stopping of firebase messaging failed.')
|
3162
3227
|
return self.firebaseStatus
|
3163
3228
|
|
3164
|
-
#await asyncio.sleep(5)
|
3229
|
+
#await asyncio.sleep(5)
|
3165
3230
|
self.firebaseStatus = FIREBASE_STATUS_NOT_INITIALISED
|
3166
3231
|
_LOGGER.info('Stopping of firebase messaging was successful.')
|
3167
3232
|
return self.firebaseStatus
|
@@ -3226,17 +3291,25 @@ class Vehicle:
|
|
3226
3291
|
_LOGGER.debug(f'Received push notification: notification id={notification}, type={obj.get('data',{}).get('type','')}, requestId={obj.get('data',{}).get('requestId','[None]')}')
|
3227
3292
|
_LOGGER.debug(f' data_message={data_message}, payload={obj.get('data',{}).get('payload','[None]')}')
|
3228
3293
|
|
3229
|
-
#temporary output of notifications in a file
|
3230
|
-
if self.updateCallback == self.update:
|
3231
|
-
|
3294
|
+
#temporary output of notifications in a file, will be removed in the next release
|
3295
|
+
#if self.updateCallback == self.update:
|
3296
|
+
# self.storeFirebaseNotifications(obj, notification, data_message)
|
3232
3297
|
|
3233
3298
|
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATED:
|
3234
|
-
|
3235
|
-
|
3236
|
-
|
3299
|
+
if self.firebaseStatus != FIREBASE_STATUS_ACTIVATION_STOPPED:
|
3300
|
+
_LOGGER.info(f'While firebase is not fully activated, received notifications are just acknowledged.')
|
3301
|
+
# As long as the firebase status is not set to activated, ignore the notifications
|
3302
|
+
return False
|
3303
|
+
else:
|
3304
|
+
# It seems that the firebase connection still works although fcmpushclient.is_started() returned False some time ago
|
3305
|
+
_LOGGER.info(f'Firebase status={self.firebaseStatus}, but PyCupra still receives push notifications.')
|
3306
|
+
self.firebaseStatus = FIREBASE_STATUS_ACTIVATED
|
3307
|
+
_LOGGER.info(f'Set firebase status back to {self.firebaseStatus}.')
|
3308
|
+
|
3237
3309
|
|
3238
3310
|
type = obj.get('data',{}).get('type','')
|
3239
3311
|
requestId = obj.get('data',{}).get('requestId','')
|
3312
|
+
payload = obj.get('data',{}).get('payload','')
|
3240
3313
|
openRequest = -1
|
3241
3314
|
if requestId != '':
|
3242
3315
|
_LOGGER.info(f'Received notification of type \'{type}\', request id={requestId} ')
|
@@ -3307,6 +3380,28 @@ class Vehicle:
|
|
3307
3380
|
await self.updateCallback(2)
|
3308
3381
|
else:
|
3309
3382
|
_LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_climater was at {self._last_get_climater}. So no need to update.')
|
3383
|
+
elif type in ('vehicle-area-alarm-vehicle-exits-zone-triggered', 'vehicle-area-alarm-vehicle-enters-zone-triggered'):
|
3384
|
+
#if self._last_get_position < datetime.now(tz=None) - timedelta(seconds= 30):
|
3385
|
+
# # Update position data only if the last one is older than timedelta
|
3386
|
+
# await self.get_position()
|
3387
|
+
#else:
|
3388
|
+
# _LOGGER.debug(f'It is now {datetime.now(tz=None)}. Last get_position was at {self._last_get_position}. So no need to update.')
|
3389
|
+
if payload != '':
|
3390
|
+
payloadDict = json.loads(payload) # Convert json string to dict
|
3391
|
+
#_LOGGER.debug(f'payloadDict is dict: {isinstance(payloadDict, dict)}')
|
3392
|
+
zones = payloadDict.get('description',{}).get('values',[])
|
3393
|
+
else:
|
3394
|
+
_LOGGER.warning(f'Missing information about areas. Payload ={payload}')
|
3395
|
+
zones = []
|
3396
|
+
areaAlarm = {'areaAlarm' : {
|
3397
|
+
'type': 'vehicle-exits-zone' if type=='vehicle-area-alarm-vehicle-exits-zone-triggered' else 'vehicle-enters-zone',
|
3398
|
+
'timestamp': datetime.now(tz=None),
|
3399
|
+
'zones': zones
|
3400
|
+
}
|
3401
|
+
}
|
3402
|
+
self._states.update(areaAlarm)
|
3403
|
+
if self.updateCallback:
|
3404
|
+
await self.updateCallback(2)
|
3310
3405
|
elif type == 'vehicle-wakeup-succeeded':
|
3311
3406
|
if self._requests.get('refresh', {}).get('id', None):
|
3312
3407
|
openRequest= self._requests.get('refresh', {}).get('id', None)
|
@@ -3317,22 +3412,23 @@ class Vehicle:
|
|
3317
3412
|
# Do full update only if the last one is older than timedelta or if the notification belongs to an open request initiated by PyCupra
|
3318
3413
|
if self.updateCallback:
|
3319
3414
|
await self.updateCallback(1)
|
3415
|
+
elif type in ('vehicle-area-alert-added', 'vehicle-area-alert-updated'):
|
3416
|
+
_LOGGER.info(f' Intentionally ignoring a notification of type \'{type}\')')
|
3320
3417
|
else:
|
3321
3418
|
_LOGGER.warning(f' Don\'t know what to do with a notification of type \'{type}\')')
|
3322
3419
|
|
3323
|
-
|
3324
|
-
|
3325
3420
|
|
3326
|
-
|
3327
|
-
|
3328
|
-
|
3329
|
-
|
3421
|
+
#temporary output of notifications in a file, will be removed in the next release
|
3422
|
+
#def storeFirebaseNotifications(self, obj, notification, data_message):
|
3423
|
+
# _LOGGER.debug(f'In storeFirebaseNotifications. notification={notification}')
|
3424
|
+
# fName = self._firebaseCredentialsFileName
|
3425
|
+
# fName = fName.replace('pycupra_firebase_credentials.json', 'pycupra_firebasenotifications.txt')
|
3330
3426
|
|
3331
|
-
|
3332
|
-
|
3333
|
-
|
3334
|
-
|
3335
|
-
|
3427
|
+
# with open(fName, "a") as ofile:
|
3428
|
+
# ofile.write(f'{datetime.now()}\n')
|
3429
|
+
# ofile.write(f' notification id={notification}, data_message={data_message}\n')
|
3430
|
+
# ofile.write(f' obj={obj}\n')
|
3431
|
+
# ofile.write("----------------------------------------------------------------\n")
|
3336
3432
|
|
3337
3433
|
|
3338
3434
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pycupra
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
4
4
|
Summary: A library to read and send vehicle data via Cupra/Seat portal using the same API calls as the MyCupra/MySeat mobile app.
|
5
5
|
Home-page: https://github.com/WulfgarW/pycupra
|
6
6
|
Author: WulfgarW
|
@@ -1,25 +1,25 @@
|
|
1
1
|
pycupra/__init__.py,sha256=p0880jPkLqOErX3u3qaLboBLOsEHFpe44axApdaGeqI,231
|
2
|
-
pycupra/__version__.py,sha256=
|
3
|
-
pycupra/connection.py,sha256=
|
4
|
-
pycupra/const.py,sha256=
|
5
|
-
pycupra/dashboard.py,sha256=
|
2
|
+
pycupra/__version__.py,sha256=ahbRianoeRb9v2bqw7g3dKIyoW7jt_rvwY0nas5Bn0c,207
|
3
|
+
pycupra/connection.py,sha256=5bUxSUC03O-4kceZn8fPWKyOaxOq13JVMjY-_UqrOwo,92187
|
4
|
+
pycupra/const.py,sha256=hCtDfI0gRewmVjXOQRrC4pUphOxGwCA3_Plfp2NwFXU,10637
|
5
|
+
pycupra/dashboard.py,sha256=bEmIRvxX08WNa0rftpQ6JrD0GW5FeVTDu5yQr0IUjMM,45178
|
6
6
|
pycupra/exceptions.py,sha256=Nq_F79GP8wjHf5lpvPy9TbSIrRHAJrFMo0T1N9TcgSQ,2917
|
7
7
|
pycupra/firebase.py,sha256=tuN_W3OX3h3-yfdprWzmCn6z_T-BMx-OpL7Z6hOA8Lc,3451
|
8
8
|
pycupra/utilities.py,sha256=6sDxWP13-XtxmqhuBJBGdVbkj48BQ9AxFMrBPxH0J7g,2679
|
9
|
-
pycupra/vehicle.py,sha256=
|
9
|
+
pycupra/vehicle.py,sha256=1sKJJ4xuBIrx6ZWnH50-b7bzjmqyBgRiETOSzDiNNN8,162609
|
10
10
|
pycupra/firebase_messaging/__init__.py,sha256=oerLHWvEf4qRqu3GxSX6SLY_OYI430ydAiAhKtzyMEM,666
|
11
11
|
pycupra/firebase_messaging/android_checkin_pb2.py,sha256=-U1oGroFt3KRuGDieae3iTcux6mAfx1TFkE1Q35ul2E,2849
|
12
12
|
pycupra/firebase_messaging/android_checkin_pb2.pyi,sha256=7KL-zQIz2Zz7uftcLkv57Podzu-yk6trn50FN4X4A8E,9379
|
13
13
|
pycupra/firebase_messaging/checkin_pb2.py,sha256=lFzCIAkYz9NFUpRbVuW-2kM_EaYKVWHeifHS1PV2eHQ,2795
|
14
14
|
pycupra/firebase_messaging/checkin_pb2.pyi,sha256=mHOqbedt5jZDI20jcyFrTMSnQ0f_tq4zkIlHiaSC3xI,14626
|
15
15
|
pycupra/firebase_messaging/const.py,sha256=XMy8kJ37uBSkTpVpdLeSjxk5UIPuvDuo-rxYdgmo2G8,1191
|
16
|
-
pycupra/firebase_messaging/fcmpushclient.py,sha256=
|
16
|
+
pycupra/firebase_messaging/fcmpushclient.py,sha256=5kAp6rnl9EJqVPn9OfHFKUfFDRWA9LAVbfiL-pLRSqo,30550
|
17
17
|
pycupra/firebase_messaging/fcmregister.py,sha256=yZngC-0ZfTygtjfdzg03OW_3xk2n_uSQQ3Lrash5Y_E,18091
|
18
18
|
pycupra/firebase_messaging/mcs_pb2.py,sha256=nwXY7IDgLYPxgpSGs6wyTSyYDdomQsyGqH8R8EgODLg,7733
|
19
19
|
pycupra/firebase_messaging/mcs_pb2.pyi,sha256=HfIhInC3wRg8_caKwUm-V3knE2jTdEQvBy6uXgQ5rHY,33959
|
20
20
|
pycupra/firebase_messaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
pycupra-0.1.
|
22
|
-
pycupra-0.1.
|
23
|
-
pycupra-0.1.
|
24
|
-
pycupra-0.1.
|
25
|
-
pycupra-0.1.
|
21
|
+
pycupra-0.1.8.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
22
|
+
pycupra-0.1.8.dist-info/METADATA,sha256=GC1Av2ilKrcM3hNfIoFPsBRKsSBjzlkIQrH8nmUzijY,3757
|
23
|
+
pycupra-0.1.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
24
|
+
pycupra-0.1.8.dist-info/top_level.txt,sha256=9Lbj_jG4JvpGwt6K3AwhWFc0XieDnuHFOP4x44wSXSQ,8
|
25
|
+
pycupra-0.1.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|