carconnectivity-connector-seatcupra 0.1a3__py3-none-any.whl → 0.1a5__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.
- {carconnectivity_connector_seatcupra-0.1a3.dist-info → carconnectivity_connector_seatcupra-0.1a5.dist-info}/METADATA +2 -2
- {carconnectivity_connector_seatcupra-0.1a3.dist-info → carconnectivity_connector_seatcupra-0.1a5.dist-info}/RECORD +11 -9
- carconnectivity_connectors/seatcupra/_version.py +1 -1
- carconnectivity_connectors/seatcupra/charging.py +1 -1
- carconnectivity_connectors/seatcupra/climatization.py +39 -0
- carconnectivity_connectors/seatcupra/command_impl.py +72 -0
- carconnectivity_connectors/seatcupra/connector.py +394 -73
- carconnectivity_connectors/seatcupra/vehicle.py +3 -1
- {carconnectivity_connector_seatcupra-0.1a3.dist-info → carconnectivity_connector_seatcupra-0.1a5.dist-info}/LICENSE +0 -0
- {carconnectivity_connector_seatcupra-0.1a3.dist-info → carconnectivity_connector_seatcupra-0.1a5.dist-info}/WHEEL +0 -0
- {carconnectivity_connector_seatcupra-0.1a3.dist-info → carconnectivity_connector_seatcupra-0.1a5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: carconnectivity-connector-seatcupra
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.1a5
|
4
4
|
Summary: CarConnectivity connector for Seat and Cupra services
|
5
5
|
Author: Till Steinbach
|
6
6
|
License: MIT License
|
@@ -37,7 +37,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
37
37
|
Requires-Python: >=3.9
|
38
38
|
Description-Content-Type: text/markdown
|
39
39
|
License-File: LICENSE
|
40
|
-
Requires-Dist: carconnectivity>=0.
|
40
|
+
Requires-Dist: carconnectivity>=0.4a3
|
41
41
|
Requires-Dist: oauthlib~=3.2.2
|
42
42
|
Requires-Dist: requests~=2.32.3
|
43
43
|
Requires-Dist: jwt~=1.3.1
|
@@ -1,9 +1,11 @@
|
|
1
1
|
carconnectivity_connectors/seatcupra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
carconnectivity_connectors/seatcupra/_version.py,sha256=
|
2
|
+
carconnectivity_connectors/seatcupra/_version.py,sha256=3vWvGIvcurJ1u-UTyD0NkVyWEC9cDflcmq5cu0o_AiM,508
|
3
3
|
carconnectivity_connectors/seatcupra/capability.py,sha256=Oe9tC_u69bj6VmOuNJ21RKoETe2j3QyZCoz-VgcZPQ0,4523
|
4
|
-
carconnectivity_connectors/seatcupra/charging.py,sha256=
|
5
|
-
carconnectivity_connectors/seatcupra/
|
6
|
-
carconnectivity_connectors/seatcupra/
|
4
|
+
carconnectivity_connectors/seatcupra/charging.py,sha256=BJe_5GEB0JkP78tpU6kyKpwuwjDZHvm-kt3PTlpQHeU,3336
|
5
|
+
carconnectivity_connectors/seatcupra/climatization.py,sha256=0xxWlxrheAPzkVT8WRQtbm6ExZmVdgW7lUdOXyS_qWY,1695
|
6
|
+
carconnectivity_connectors/seatcupra/command_impl.py,sha256=mtw8ZwJLmf79fPDZ1N3ImLfB8Gt9JPbzjMuIo2y5v3M,2879
|
7
|
+
carconnectivity_connectors/seatcupra/connector.py,sha256=7O_sQCfz5doQxcrVGW0_2e9I4HkOhL-WAx87CjQSkg0,90129
|
8
|
+
carconnectivity_connectors/seatcupra/vehicle.py,sha256=kiFVbJgq5VQOzf-vSli_2NsMgY0x4pwvJsjPWLGdr1g,3404
|
7
9
|
carconnectivity_connectors/seatcupra/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
10
|
carconnectivity_connectors/seatcupra/auth/auth_util.py,sha256=Y81h8fGOMSMgPtE4wI_TI9WgE_s43uaPjRLBBINhj4g,4433
|
9
11
|
carconnectivity_connectors/seatcupra/auth/my_cupra_session.py,sha256=iK5SlankZqaeneC3SNad8nHHcrP0tTmYxToI_9cqwlo,10744
|
@@ -12,8 +14,8 @@ carconnectivity_connectors/seatcupra/auth/session_manager.py,sha256=NizIuY-pvkVB
|
|
12
14
|
carconnectivity_connectors/seatcupra/auth/vw_web_session.py,sha256=hgsCdXugVnSgvLta4hBNtoNgMhAA83paAYO2fUOOFyM,10657
|
13
15
|
carconnectivity_connectors/seatcupra/auth/helpers/blacklist_retry.py,sha256=f3wsiY5bpHDBxp7Va1Mv9nKJ4u3qnCHZZmDu78_AhMk,1251
|
14
16
|
carconnectivity_connectors/seatcupra/ui/connector_ui.py,sha256=SNYnlcGJpbWhuLiIHD2l6H9IfSiMz3IgmvXsdossDnE,1412
|
15
|
-
carconnectivity_connector_seatcupra-0.
|
16
|
-
carconnectivity_connector_seatcupra-0.
|
17
|
-
carconnectivity_connector_seatcupra-0.
|
18
|
-
carconnectivity_connector_seatcupra-0.
|
19
|
-
carconnectivity_connector_seatcupra-0.
|
17
|
+
carconnectivity_connector_seatcupra-0.1a5.dist-info/LICENSE,sha256=PIwI1alwDyOfvEQHdGCm2u9uf_mGE8030xZDfun0xTo,1071
|
18
|
+
carconnectivity_connector_seatcupra-0.1a5.dist-info/METADATA,sha256=BIc37cr52KvMOs_dhmOQVOpQZZ6tX-BeEaeWxfP311Q,5386
|
19
|
+
carconnectivity_connector_seatcupra-0.1a5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
20
|
+
carconnectivity_connector_seatcupra-0.1a5.dist-info/top_level.txt,sha256=KqA8GviZsDH4PtmnwSQsz0HB_w-TWkeEHLIRNo5dTaI,27
|
21
|
+
carconnectivity_connector_seatcupra-0.1a5.dist-info/RECORD,,
|
@@ -32,7 +32,7 @@ class SeatCupraCharging(Charging): # pylint: disable=too-many-instance-attribut
|
|
32
32
|
"""
|
33
33
|
OFF = 'off'
|
34
34
|
READY_FOR_CHARGING = 'readyForCharging'
|
35
|
-
NOT_READY_FOR_CHARGING = '
|
35
|
+
NOT_READY_FOR_CHARGING = 'notReadyForCharging'
|
36
36
|
CONSERVATION = 'conservation'
|
37
37
|
CHARGE_PURPOSE_REACHED_NOT_CONSERVATION_CHARGING = 'chargePurposeReachedAndNotConservationCharging'
|
38
38
|
CHARGE_PURPOSE_REACHED_CONSERVATION = 'chargePurposeReachedAndConservation'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"""
|
2
|
+
Module for charging for Seat/Cupra vehicles.
|
3
|
+
"""
|
4
|
+
from __future__ import annotations
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
|
7
|
+
from carconnectivity.climatization import Climatization
|
8
|
+
from carconnectivity.objects import GenericObject
|
9
|
+
from carconnectivity.vehicle import GenericVehicle
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from typing import Optional
|
13
|
+
|
14
|
+
|
15
|
+
class SeatCupraClimatization(Climatization): # pylint: disable=too-many-instance-attributes
|
16
|
+
"""
|
17
|
+
SeatCupraClimatization class for handling Seat/Cupra vehicle climatization information.
|
18
|
+
|
19
|
+
This class extends the Climatization class and includes an enumeration of various
|
20
|
+
climatization states specific to Volkswagen vehicles.
|
21
|
+
"""
|
22
|
+
def __init__(self, vehicle: GenericVehicle | None = None, origin: Optional[Climatization] = None) -> None:
|
23
|
+
if origin is not None:
|
24
|
+
super().__init__(vehicle=vehicle, origin=origin)
|
25
|
+
if not isinstance(self.settings, SeatCupraClimatization.Settings):
|
26
|
+
self.settings: Climatization.Settings = SeatCupraClimatization.Settings(parent=self, origin=origin.settings)
|
27
|
+
else:
|
28
|
+
super().__init__(vehicle=vehicle)
|
29
|
+
self.settings: Climatization.Settings = SeatCupraClimatization.Settings(parent=self, origin=self.settings)
|
30
|
+
|
31
|
+
class Settings(Climatization.Settings):
|
32
|
+
"""
|
33
|
+
This class represents the settings for a skoda car climatiation.
|
34
|
+
"""
|
35
|
+
def __init__(self, parent: Optional[GenericObject] = None, origin: Optional[Climatization.Settings] = None) -> None:
|
36
|
+
if origin is not None:
|
37
|
+
super().__init__(parent=parent, origin=origin)
|
38
|
+
else:
|
39
|
+
super().__init__(parent=parent)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""This module defines the classes that represent attributes in the CarConnectivity system."""
|
2
|
+
from __future__ import annotations
|
3
|
+
from typing import TYPE_CHECKING, Dict, Union
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
import argparse
|
7
|
+
import logging
|
8
|
+
|
9
|
+
from carconnectivity.commands import GenericCommand
|
10
|
+
from carconnectivity.objects import GenericObject
|
11
|
+
from carconnectivity.errors import SetterError
|
12
|
+
from carconnectivity.util import ThrowingArgumentParser
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from carconnectivity.objects import Optional
|
16
|
+
|
17
|
+
LOG: logging.Logger = logging.getLogger("carconnectivity.connectors.seatcupra")
|
18
|
+
|
19
|
+
|
20
|
+
class SpinCommand(GenericCommand):
|
21
|
+
"""
|
22
|
+
SpinCommand is a command class for verifying the spin
|
23
|
+
|
24
|
+
"""
|
25
|
+
def __init__(self, name: str = 'spin', parent: Optional[GenericObject] = None) -> None:
|
26
|
+
super().__init__(name=name, parent=parent)
|
27
|
+
|
28
|
+
@property
|
29
|
+
def value(self) -> Optional[Union[str, Dict]]:
|
30
|
+
return super().value
|
31
|
+
|
32
|
+
@value.setter
|
33
|
+
def value(self, new_value: Optional[Union[str, Dict]]) -> None:
|
34
|
+
if isinstance(new_value, str):
|
35
|
+
parser = ThrowingArgumentParser(prog='', add_help=False, exit_on_error=False)
|
36
|
+
parser.add_argument('command', help='Command to execute', type=SpinCommand.Command,
|
37
|
+
choices=list(SpinCommand.Command))
|
38
|
+
parser.add_argument('--spin', dest='spin', help='Spin to be used instead of spin from config or .netrc', type=str, required=False,
|
39
|
+
default=None)
|
40
|
+
try:
|
41
|
+
args = parser.parse_args(new_value.split(sep=' '))
|
42
|
+
except argparse.ArgumentError as e:
|
43
|
+
raise SetterError(f'Invalid format for SpinCommand: {e.message} {parser.format_usage()}') from e
|
44
|
+
|
45
|
+
newvalue_dict = {}
|
46
|
+
newvalue_dict['command'] = args.command
|
47
|
+
if args.spin is not None:
|
48
|
+
newvalue_dict['spin'] = args.spin
|
49
|
+
new_value = newvalue_dict
|
50
|
+
elif isinstance(new_value, dict):
|
51
|
+
if 'command' in new_value and isinstance(new_value['command'], str):
|
52
|
+
if new_value['command'] in SpinCommand.Command:
|
53
|
+
new_value['command'] = SpinCommand.Command(new_value['command'])
|
54
|
+
else:
|
55
|
+
raise ValueError('Invalid value for SpinCommand. '
|
56
|
+
f'Command must be one of {SpinCommand.Command}')
|
57
|
+
if self._is_changeable:
|
58
|
+
for hook in self._on_set_hooks:
|
59
|
+
new_value = hook(self, new_value)
|
60
|
+
self._set_value(new_value)
|
61
|
+
else:
|
62
|
+
raise TypeError('You cannot set this attribute. Attribute is not mutable.')
|
63
|
+
|
64
|
+
class Command(Enum):
|
65
|
+
"""
|
66
|
+
Enum class representing different commands for SPIN.
|
67
|
+
|
68
|
+
"""
|
69
|
+
VERIFY = 'verify'
|
70
|
+
|
71
|
+
def __str__(self) -> str:
|
72
|
+
return self.value
|
@@ -15,7 +15,7 @@ from carconnectivity.garage import Garage
|
|
15
15
|
from carconnectivity.errors import AuthenticationError, TooManyRequestsError, RetrievalError, APIError, APICompatibilityError, \
|
16
16
|
TemporaryAuthenticationError, SetterError, CommandError
|
17
17
|
from carconnectivity.util import robust_time_parse, log_extra_keys, config_remove_credentials
|
18
|
-
from carconnectivity.units import Length,
|
18
|
+
from carconnectivity.units import Length, Current
|
19
19
|
from carconnectivity.doors import Doors
|
20
20
|
from carconnectivity.windows import Windows
|
21
21
|
from carconnectivity.lights import Lights
|
@@ -27,6 +27,7 @@ from carconnectivity.command_impl import ClimatizationStartStopCommand, WakeSlee
|
|
27
27
|
from carconnectivity.climatization import Climatization
|
28
28
|
from carconnectivity.commands import Commands
|
29
29
|
from carconnectivity.charging import Charging
|
30
|
+
from carconnectivity.charging_connector import ChargingConnector
|
30
31
|
from carconnectivity.position import Position
|
31
32
|
|
32
33
|
from carconnectivity_connectors.base.connector import BaseConnector
|
@@ -36,6 +37,8 @@ from carconnectivity_connectors.seatcupra._version import __version__
|
|
36
37
|
from carconnectivity_connectors.seatcupra.capability import Capability
|
37
38
|
from carconnectivity_connectors.seatcupra.vehicle import SeatCupraVehicle, SeatCupraElectricVehicle, SeatCupraCombustionVehicle, SeatCupraHybridVehicle
|
38
39
|
from carconnectivity_connectors.seatcupra.charging import SeatCupraCharging, mapping_seatcupra_charging_state
|
40
|
+
from carconnectivity_connectors.seatcupra.climatization import SeatCupraClimatization
|
41
|
+
from carconnectivity_connectors.seatcupra.command_impl import SpinCommand
|
39
42
|
|
40
43
|
SUPPORT_IMAGES = False
|
41
44
|
try:
|
@@ -222,6 +225,12 @@ class Connector(BaseConnector):
|
|
222
225
|
|
223
226
|
This method calls the `fetch_vehicles` method to retrieve vehicle data.
|
224
227
|
"""
|
228
|
+
# Add spin command
|
229
|
+
if self.commands is not None and not self.commands.contains_command('spin'):
|
230
|
+
spin_command = SpinCommand(parent=self.commands)
|
231
|
+
spin_command._add_on_set_hook(self.__on_spin) # pylint: disable=protected-access
|
232
|
+
spin_command.enabled = True
|
233
|
+
self.commands.add_command(spin_command)
|
225
234
|
self.fetch_vehicles()
|
226
235
|
self.car_connectivity.transaction_end()
|
227
236
|
|
@@ -339,10 +348,10 @@ class Connector(BaseConnector):
|
|
339
348
|
else:
|
340
349
|
raise APIError('Could not fetch capabilities, capability ID missing')
|
341
350
|
log_extra_keys(LOG_API, 'capability', capability_dict, {'id', 'expirationDate', 'editable', 'parameters'})
|
342
|
-
|
351
|
+
|
343
352
|
for capability_id in vehicle.capabilities.capabilities.keys() - found_capabilities:
|
344
353
|
vehicle.capabilities.remove_capability(capability_id)
|
345
|
-
|
354
|
+
|
346
355
|
if vehicle.capabilities.has_capability('charging'):
|
347
356
|
if not isinstance(vehicle, SeatCupraElectricVehicle):
|
348
357
|
LOG.debug('Promoting %s to SeatCupraElectricVehicle object for %s', vehicle.__class__.__name__, vin)
|
@@ -353,7 +362,7 @@ class Connector(BaseConnector):
|
|
353
362
|
charging_start_stop_command._add_on_set_hook(self.__on_charging_start_stop) # pylint: disable=protected-access
|
354
363
|
charging_start_stop_command.enabled = True
|
355
364
|
vehicle.charging.commands.add_command(charging_start_stop_command)
|
356
|
-
|
365
|
+
|
357
366
|
if vehicle.capabilities.has_capability('climatisation'):
|
358
367
|
if vehicle.climatization is not None and vehicle.climatization.commands is not None \
|
359
368
|
and not vehicle.climatization.commands.contains_command('start-stop'):
|
@@ -363,8 +372,34 @@ class Connector(BaseConnector):
|
|
363
372
|
climatisation_start_stop_command._add_on_set_hook(self.__on_air_conditioning_start_stop)
|
364
373
|
climatisation_start_stop_command.enabled = True
|
365
374
|
vehicle.climatization.commands.add_command(climatisation_start_stop_command)
|
366
|
-
|
367
|
-
vehicle.capabilities.
|
375
|
+
|
376
|
+
if vehicle.capabilities.has_capability('vehicleWakeUpTrigger'):
|
377
|
+
if vehicle.commands is not None and vehicle.commands.commands is not None \
|
378
|
+
and not vehicle.commands.contains_command('wake-sleep'):
|
379
|
+
wake_sleep_command = WakeSleepCommand(parent=vehicle.commands)
|
380
|
+
wake_sleep_command._add_on_set_hook(self.__on_wake_sleep) # pylint: disable=protected-access
|
381
|
+
wake_sleep_command.enabled = True
|
382
|
+
vehicle.commands.add_command(wake_sleep_command)
|
383
|
+
|
384
|
+
# Add honkAndFlash command if necessary capabilities are available
|
385
|
+
if vehicle.capabilities.has_capability('honkAndFlash'):
|
386
|
+
if vehicle.commands is not None and vehicle.commands.commands is not None \
|
387
|
+
and not vehicle.commands.contains_command('honk-flash'):
|
388
|
+
honk_flash_command = HonkAndFlashCommand(parent=vehicle.commands, with_duration=True)
|
389
|
+
honk_flash_command._add_on_set_hook(self.__on_honk_flash) # pylint: disable=protected-access
|
390
|
+
honk_flash_command.enabled = True
|
391
|
+
vehicle.commands.add_command(honk_flash_command)
|
392
|
+
|
393
|
+
# Add lock and unlock command
|
394
|
+
if vehicle.capabilities.has_capability('access'):
|
395
|
+
if vehicle.doors is not None and vehicle.doors.commands is not None and vehicle.doors.commands.commands is not None \
|
396
|
+
and not vehicle.doors.commands.contains_command('lock-unlock'):
|
397
|
+
lock_unlock_command = LockUnlockCommand(parent=vehicle.doors.commands)
|
398
|
+
lock_unlock_command._add_on_set_hook(self.__on_lock_unlock) # pylint: disable=protected-access
|
399
|
+
lock_unlock_command.enabled = True
|
400
|
+
vehicle.doors.commands.add_command(lock_unlock_command)
|
401
|
+
else:
|
402
|
+
vehicle.capabilities.clear_capabilities()
|
368
403
|
if isinstance(vehicle, SeatCupraVehicle):
|
369
404
|
vehicle = self.fetch_image(vehicle)
|
370
405
|
else:
|
@@ -388,7 +423,7 @@ class Connector(BaseConnector):
|
|
388
423
|
vin = vehicle.vin.value
|
389
424
|
if vin is None:
|
390
425
|
raise APIError('VIN is missing')
|
391
|
-
|
426
|
+
|
392
427
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/connection'
|
393
428
|
vehicle_connection_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
394
429
|
if vehicle_connection_data is not None:
|
@@ -516,11 +551,10 @@ class Connector(BaseConnector):
|
|
516
551
|
vin = vehicle.vin.value
|
517
552
|
if vin is None:
|
518
553
|
raise APIError('VIN is missing')
|
519
|
-
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/measurements/engines'
|
520
|
-
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
521
|
-
#measurements
|
522
|
-
#{'primary': {'fuelType': 'gasoline', 'rangeInKm': 120.0}, 'secondary': {'fuelType': 'electric', 'rangeInKm': 40.0}}
|
523
|
-
print(vehicle_status_data)
|
554
|
+
# url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/measurements/engines'
|
555
|
+
# vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
556
|
+
# measurements
|
557
|
+
# {'primary': {'fuelType': 'gasoline', 'rangeInKm': 120.0}, 'secondary': {'fuelType': 'electric', 'rangeInKm': 40.0}}
|
524
558
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v5/users/{self.session.user_id}/vehicles/{vin}/mycar'
|
525
559
|
vehicle_status_data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
526
560
|
if vehicle_status_data:
|
@@ -592,23 +626,6 @@ class Connector(BaseConnector):
|
|
592
626
|
if 'services' in vehicle_status_data and vehicle_status_data['services'] is not None:
|
593
627
|
if 'charging' in vehicle_status_data['services'] and vehicle_status_data['services']['charging'] is not None:
|
594
628
|
charging_status: Dict = vehicle_status_data['services']['charging']
|
595
|
-
if 'status' in charging_status and charging_status['status'] is not None:
|
596
|
-
if charging_status['status'] in SeatCupraCharging.SeatCupraChargingState:
|
597
|
-
volkswagen_charging_state = SeatCupraCharging.SeatCupraChargingState(charging_status['status'])
|
598
|
-
charging_state: Charging.ChargingState = mapping_seatcupra_charging_state[volkswagen_charging_state]
|
599
|
-
else:
|
600
|
-
LOG_API.info('Unkown charging state %s not in %s', charging_status['status'],
|
601
|
-
str(SeatCupraCharging.SeatCupraChargingState))
|
602
|
-
charging_state = Charging.ChargingState.UNKNOWN
|
603
|
-
if isinstance(vehicle, ElectricVehicle):
|
604
|
-
vehicle.charging.state._set_value(value=charging_state) # pylint: disable=protected-access
|
605
|
-
else:
|
606
|
-
LOG_API.warning('Vehicle is not an electric or hybrid vehicle, but charging state was fetched')
|
607
|
-
else:
|
608
|
-
if isinstance(vehicle, ElectricVehicle):
|
609
|
-
vehicle.charging.state._set_value(None) # pylint: disable=protected-access
|
610
|
-
else:
|
611
|
-
LOG_API.warning('Vehicle is not an electric or hybrid vehicle, but charging state was fetched')
|
612
629
|
if 'targetPct' in charging_status and charging_status['targetPct'] is not None:
|
613
630
|
if isinstance(vehicle, ElectricVehicle):
|
614
631
|
vehicle.charging.settings.target_level._set_value(charging_status['targetPct']) # pylint: disable=protected-access
|
@@ -638,26 +655,7 @@ class Connector(BaseConnector):
|
|
638
655
|
vehicle.charging.enabled = False
|
639
656
|
if 'climatisation' in vehicle_status_data['services'] and vehicle_status_data['services']['climatisation'] is not None:
|
640
657
|
climatisation_status: Dict = vehicle_status_data['services']['climatisation']
|
641
|
-
|
642
|
-
if climatisation_status['status'].lower() in [item.value for item in Climatization.ClimatizationState]:
|
643
|
-
climatization_state: Climatization.ClimatizationState = Climatization.ClimatizationState(climatisation_status['status'].lower())
|
644
|
-
else:
|
645
|
-
LOG_API.info('Unknown climatization state %s not in %s', climatisation_status['status'],
|
646
|
-
str(Climatization.ClimatizationState))
|
647
|
-
climatization_state = Climatization.ClimatizationState.UNKNOWN
|
648
|
-
vehicle.climatization.state._set_value(value=climatization_state) # pylint: disable=protected-access
|
649
|
-
else:
|
650
|
-
vehicle.climatization.state._set_value(None) # pylint: disable=protected-access
|
651
|
-
if 'targetTemperatureCelsius' in climatisation_status and climatisation_status['targetTemperatureCelsius'] is not None:
|
652
|
-
target_temperature: Optional[float] = climatisation_status['targetTemperatureCelsius']
|
653
|
-
vehicle.climatization.settings.target_temperature._set_value(value=target_temperature, # pylint: disable=protected-access
|
654
|
-
unit=Temperature.C)
|
655
|
-
elif 'targetTemperatureFahrenheit' in climatisation_status and climatisation_status['targetTemperatureFahrenheit'] is not None:
|
656
|
-
target_temperature = climatisation_status['targetTemperatureFahrenheit']
|
657
|
-
vehicle.climatization.settings.target_temperature._set_value(value=target_temperature, # pylint: disable=protected-access
|
658
|
-
unit=Temperature.F)
|
659
|
-
else:
|
660
|
-
vehicle.climatization.settings.target_temperature._set_value(None) # pylint: disable=protected-access
|
658
|
+
|
661
659
|
if 'remainingTime' in climatisation_status and climatisation_status['remainingTime'] is not None:
|
662
660
|
remaining_duration: timedelta = timedelta(minutes=climatisation_status['remainingTime'])
|
663
661
|
estimated_date_reached: datetime = datetime.now(tz=timezone.utc) + remaining_duration
|
@@ -665,6 +663,7 @@ class Connector(BaseConnector):
|
|
665
663
|
vehicle.climatization.estimated_date_reached._set_value(value=estimated_date_reached) # pylint: disable=protected-access
|
666
664
|
else:
|
667
665
|
vehicle.climatization.estimated_date_reached._set_value(None) # pylint: disable=protected-access
|
666
|
+
# we take status, targetTemperatureCelsius, targetTemperatureFahrenheit, from climatization request
|
668
667
|
log_extra_keys(LOG_API, 'climatisation', climatisation_status, {'status', 'targetTemperatureCelsius', 'targetTemperatureFahrenheit',
|
669
668
|
'remainingTime'})
|
670
669
|
return vehicle
|
@@ -757,12 +756,68 @@ class Connector(BaseConnector):
|
|
757
756
|
raise APIError('VIN is missing')
|
758
757
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/climatisation/status'
|
759
758
|
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
760
|
-
#{'climatisationStatus': {'carCapturedTimestamp': '2025-02-18T17:24:02Z', 'climatisationState': 'off', 'climatisationTrigger': 'unsupported'}, 'windowHeatingStatus': {'carCapturedTimestamp': '2025-02-18T16:57:51Z', 'windowHeatingStatus': [{'windowLocation': 'front', 'windowHeatingState': 'off'}, {'windowLocation': 'rear', 'windowHeatingState': 'off'}]}}
|
761
|
-
|
759
|
+
# {'climatisationStatus': {'carCapturedTimestamp': '2025-02-18T17:24:02Z', 'climatisationState': 'off', 'climatisationTrigger': 'unsupported'}, 'windowHeatingStatus': {'carCapturedTimestamp': '2025-02-18T16:57:51Z', 'windowHeatingStatus': [{'windowLocation': 'front', 'windowHeatingState': 'off'}, {'windowLocation': 'rear', 'windowHeatingState': 'off'}]}}
|
760
|
+
if data is not None:
|
761
|
+
if 'climatisationStatus' in data and data['climatisationStatus'] is not None:
|
762
|
+
climatisation_status: Dict = data['climatisationStatus']
|
763
|
+
if 'carCapturedTimestamp' not in climatisation_status or climatisation_status['carCapturedTimestamp'] is None:
|
764
|
+
raise APIError('Could not fetch vehicle status, carCapturedTimestamp missing')
|
765
|
+
captured_at: datetime = robust_time_parse(climatisation_status['carCapturedTimestamp'])
|
766
|
+
if 'climatisationState' in climatisation_status and climatisation_status['climatisationState'] is not None:
|
767
|
+
if climatisation_status['climatisationState'].lower() in [item.value for item in Climatization.ClimatizationState]:
|
768
|
+
climatization_state: Climatization.ClimatizationState = \
|
769
|
+
Climatization.ClimatizationState(climatisation_status['climatisationState'].lower())
|
770
|
+
else:
|
771
|
+
LOG_API.info('Unknown climatization state %s not in %s', climatisation_status['climatisationState'],
|
772
|
+
str(Climatization.ClimatizationState))
|
773
|
+
climatization_state = Climatization.ClimatizationState.UNKNOWN
|
774
|
+
vehicle.climatization.state._set_value(value=climatization_state, measured=captured_at) # pylint: disable=protected-access
|
775
|
+
else:
|
776
|
+
vehicle.climatization.state._set_value(None) # pylint: disable=protected-access
|
777
|
+
log_extra_keys(LOG_API, 'climatisation', data, {'carCapturedTimestamp', 'climatisationState'})
|
778
|
+
else:
|
779
|
+
vehicle.climatization.state._set_value(None) # pylint: disable=protected-access
|
780
|
+
log_extra_keys(LOG_API, 'climatisation', data, {'climatisationStatus'})
|
762
781
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/{vin}/climatisation/settings'
|
763
782
|
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
764
|
-
|
765
|
-
|
783
|
+
if data is not None:
|
784
|
+
if not isinstance(vehicle.climatization, SeatCupraClimatization):
|
785
|
+
vehicle.climatization = SeatCupraClimatization(vehicle=vehicle, origin=vehicle.climatization)
|
786
|
+
if 'carCapturedTimestamp' not in data or data['carCapturedTimestamp'] is None:
|
787
|
+
raise APIError('Could not fetch vehicle status, carCapturedTimestamp missing')
|
788
|
+
captured_at: datetime = robust_time_parse(data['carCapturedTimestamp'])
|
789
|
+
if 'targetTemperatureInCelsius' in data and data['targetTemperatureInCelsius'] is not None:
|
790
|
+
# pylint: disable-next=protected-access
|
791
|
+
vehicle.climatization.settings.target_temperature._add_on_set_hook(self.__on_air_conditioning_settings_change)
|
792
|
+
vehicle.climatization.settings.target_temperature._is_changeable = True # pylint: disable=protected-access
|
793
|
+
|
794
|
+
target_temperature: Optional[float] = data['targetTemperatureInCelsius']
|
795
|
+
vehicle.climatization.settings.target_temperature._set_value(value=target_temperature, # pylint: disable=protected-access
|
796
|
+
measured=captured_at,
|
797
|
+
unit=Temperature.C)
|
798
|
+
elif 'targetTemperatureInFahrenheit' in data and data['targetTemperatureInFahrenheit'] is not None:
|
799
|
+
# pylint: disable-next=protected-access
|
800
|
+
vehicle.climatization.settings.target_temperature._add_on_set_hook(self.__on_air_conditioning_settings_change)
|
801
|
+
vehicle.climatization.settings.target_temperature._is_changeable = True # pylint: disable=protected-access
|
802
|
+
|
803
|
+
target_temperature = data['targetTemperatureInFahrenheit']
|
804
|
+
vehicle.climatization.settings.target_temperature._set_value(value=target_temperature, # pylint: disable=protected-access
|
805
|
+
measured=captured_at,
|
806
|
+
unit=Temperature.F)
|
807
|
+
else:
|
808
|
+
vehicle.climatization.settings.target_temperature._set_value(None) # pylint: disable=protected-access
|
809
|
+
if 'climatisationWithoutExternalPower' in data and data['climatisationWithoutExternalPower'] is not None:
|
810
|
+
# pylint: disable-next=protected-access
|
811
|
+
vehicle.climatization.settings.climatization_without_external_power._add_on_set_hook(self.__on_air_conditioning_settings_change)
|
812
|
+
vehicle.climatization.settings.climatization_without_external_power._is_changeable = True # pylint: disable=protected-access
|
813
|
+
|
814
|
+
# pylint: disable-next=protected-access
|
815
|
+
vehicle.climatization.settings.climatization_without_external_power._set_value(data['climatisationWithoutExternalPower'],
|
816
|
+
measured=captured_at)
|
817
|
+
else:
|
818
|
+
vehicle.climatization.settings.climatization_without_external_power._set_value(None) # pylint: disable=protected-access
|
819
|
+
log_extra_keys(LOG_API, f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/{vin}/climatisation/settings', data,
|
820
|
+
{'carCapturedTimestamp', 'targetTemperatureInCelsius', 'targetTemperatureInFahrenheit', 'climatisationWithoutExternalPower'})
|
766
821
|
return vehicle
|
767
822
|
|
768
823
|
def fetch_charging(self, vehicle: SeatCupraVehicle, no_cache: bool = False) -> SeatCupraVehicle:
|
@@ -782,24 +837,98 @@ class Connector(BaseConnector):
|
|
782
837
|
vin = vehicle.vin.value
|
783
838
|
if vin is None:
|
784
839
|
raise APIError('VIN is missing')
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
840
|
+
if isinstance(vehicle, ElectricVehicle):
|
841
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/charging/status'
|
842
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
843
|
+
|
844
|
+
if data is not None:
|
845
|
+
if 'charging' in data and data['charging'] is not None:
|
846
|
+
if 'state' in data['charging'] and data['charging']['state'] is not None:
|
847
|
+
if data['charging']['state'] in SeatCupraCharging.SeatCupraChargingState:
|
848
|
+
volkswagen_charging_state = SeatCupraCharging.SeatCupraChargingState(data['charging']['state'])
|
849
|
+
charging_state: Charging.ChargingState = mapping_seatcupra_charging_state[volkswagen_charging_state]
|
850
|
+
else:
|
851
|
+
LOG_API.info('Unkown charging state %s not in %s', data['charging']['state'],
|
852
|
+
str(SeatCupraCharging.SeatCupraChargingState))
|
853
|
+
charging_state = Charging.ChargingState.UNKNOWN
|
854
|
+
vehicle.charging.state._set_value(value=charging_state) # pylint: disable=protected-access
|
855
|
+
else:
|
856
|
+
vehicle.charging.state._set_value(None) # pylint: disable=protected-access
|
857
|
+
log_extra_keys(LOG_API, 'charging', data['charging'], {'state'})
|
858
|
+
if 'plug' in data and data['plug'] is not None:
|
859
|
+
if 'connection' in data['plug'] and data['plug']['connection'] is not None:
|
860
|
+
if data['plug']['connection'] in [item.value for item in ChargingConnector.ChargingConnectorConnectionState]:
|
861
|
+
plug_state: ChargingConnector.ChargingConnectorConnectionState = \
|
862
|
+
ChargingConnector.ChargingConnectorConnectionState(data['plug']['connection'])
|
863
|
+
else:
|
864
|
+
LOG_API.info('Unknown plug state %s', data['plug']['connection'])
|
865
|
+
plug_state = ChargingConnector.ChargingConnectorConnectionState.UNKNOWN
|
866
|
+
vehicle.charging.connector.connection_state._set_value(value=plug_state) # pylint: disable=protected-access
|
867
|
+
else:
|
868
|
+
vehicle.charging.connector.connection_state._set_value(value=None) # pylint: disable=protected-access
|
869
|
+
if 'externalPower' in data['plug'] and data['plug']['externalPower'] is not None:
|
870
|
+
if data['plug']['externalPower'] in [item.value for item in ChargingConnector.ExternalPower]:
|
871
|
+
plug_power_state: ChargingConnector.ExternalPower = \
|
872
|
+
ChargingConnector.ExternalPower(data['plug']['externalPower'])
|
873
|
+
else:
|
874
|
+
LOG_API.info('Unknown plug power state %s', data['plug']['externalPower'])
|
875
|
+
plug_power_state = ChargingConnector.ExternalPower.UNKNOWN
|
876
|
+
vehicle.charging.connector.external_power._set_value(value=plug_power_state) # pylint: disable=protected-access
|
877
|
+
else:
|
878
|
+
vehicle.charging.connector.external_power._set_value(None) # pylint: disable=protected-access
|
879
|
+
if 'lock' in data['plug'] and data['plug']['lock'] is not None:
|
880
|
+
if data['plug']['lock'] in [item.value for item in ChargingConnector.ChargingConnectorLockState]:
|
881
|
+
plug_lock_state: ChargingConnector.ChargingConnectorLockState = \
|
882
|
+
ChargingConnector.ChargingConnectorLockState(data['plug']['lock'])
|
883
|
+
else:
|
884
|
+
LOG_API.info('Unknown plug lock state %s', data['plug']['lock'])
|
885
|
+
plug_lock_state = ChargingConnector.ChargingConnectorLockState.UNKNOWN
|
886
|
+
vehicle.charging.connector.lock_state._set_value(value=plug_lock_state) # pylint: disable=protected-access
|
887
|
+
else:
|
888
|
+
vehicle.charging.connector.lock_state._set_value(None) # pylint: disable=protected-access
|
889
|
+
log_extra_keys(LOG_API, 'plug', data['plug'], {'connection', 'externalPower', 'lock'})
|
890
|
+
log_extra_keys(LOG_API, f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/charging/status', data,
|
891
|
+
{'state', 'battery', 'charging', 'plug'})
|
892
|
+
|
893
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/charging/settings'
|
894
|
+
data: Dict[str, Any] | None = self._fetch_data(url=url, session=self.session, no_cache=no_cache)
|
895
|
+
if data is not None:
|
896
|
+
if 'maxChargeCurrentAc' in data and data['maxChargeCurrentAc'] is not None:
|
897
|
+
if data['maxChargeCurrentAc']:
|
898
|
+
vehicle.charging.settings.maximum_current._set_value(value=16, # pylint: disable=protected-access
|
899
|
+
unit=Current.A)
|
900
|
+
else:
|
901
|
+
vehicle.charging.settings.maximum_current._set_value(value=6, # pylint: disable=protected-access
|
902
|
+
unit=Current.A)
|
903
|
+
else:
|
904
|
+
vehicle.charging.settings.maximum_current._set_value(None) # pylint: disable=protected-access
|
905
|
+
if 'defaultMaxTargetSocPercentage' in data and data['defaultMaxTargetSocPercentage'] is not None:
|
906
|
+
vehicle.charging.settings.target_level._set_value(data['defaultMaxTargetSocPercentage']) # pylint: disable=protected-access
|
907
|
+
else:
|
908
|
+
vehicle.charging.settings.target_level._set_value(None) # pylint: disable=protected-access
|
797
909
|
return vehicle
|
798
910
|
|
799
911
|
def fetch_image(self, vehicle: SeatCupraVehicle, no_cache: bool = False) -> SeatCupraVehicle:
|
912
|
+
"""
|
913
|
+
Fetches the image of a given SeatCupraVehicle.
|
914
|
+
|
915
|
+
This method retrieves the image of the vehicle from a remote server. It supports caching to avoid redundant downloads.
|
916
|
+
If caching is enabled and the image is found in the cache and is not expired, it will be loaded from the cache.
|
917
|
+
Otherwise, it will be downloaded from the server.
|
918
|
+
|
919
|
+
Args:
|
920
|
+
vehicle (SeatCupraVehicle): The vehicle object for which the image is to be fetched.
|
921
|
+
no_cache (bool, optional): If True, bypasses the cache and fetches the image directly from the server. Defaults to False.
|
922
|
+
|
923
|
+
Returns:
|
924
|
+
SeatCupraVehicle: The vehicle object with the fetched image added to its attributes.
|
925
|
+
|
926
|
+
Raises:
|
927
|
+
RetrievalError: If there is a connection error, chunked encoding error, read timeout, or retry error during the image retrieval process.
|
928
|
+
"""
|
800
929
|
if SUPPORT_IMAGES:
|
801
930
|
url: str = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vehicle.vin.value}/renders'
|
802
|
-
data = self._fetch_data(url, session=self.session, allow_http_error=True)
|
931
|
+
data = self._fetch_data(url, session=self.session, allow_http_error=True, no_cache=no_cache)
|
803
932
|
if data is not None: # pylint: disable=too-many-nested-blocks
|
804
933
|
for image_id, image_url in data.items():
|
805
934
|
if image_id == 'isDefault':
|
@@ -814,7 +943,7 @@ class Connector(BaseConnector):
|
|
814
943
|
if img is None or self.active_config['max_age'] is None \
|
815
944
|
or (cache_date is not None and cache_date < (datetime.utcnow() - timedelta(seconds=self.active_config['max_age']))):
|
816
945
|
try:
|
817
|
-
image_download_response = requests.get(image_url, stream=True)
|
946
|
+
image_download_response = requests.get(image_url, stream=True, timeout=180)
|
818
947
|
if image_download_response.status_code == requests.codes['ok']:
|
819
948
|
img = Image.open(image_download_response.raw) # pyright: ignore[reportPossiblyUnboundVariable]
|
820
949
|
if self.session.cache is not None:
|
@@ -921,9 +1050,19 @@ class Connector(BaseConnector):
|
|
921
1050
|
raise CommandError('VIN in object hierarchy missing')
|
922
1051
|
if 'command' not in command_arguments:
|
923
1052
|
raise CommandError('Command argument missing')
|
1053
|
+
command_dict: Dict = {}
|
924
1054
|
if command_arguments['command'] == ChargingStartStopCommand.Command.START:
|
925
1055
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/charging/requests/start'
|
926
|
-
|
1056
|
+
if isinstance(vehicle, SeatCupraElectricVehicle) and vehicle.charging is not None and vehicle.charging.settings is not None \
|
1057
|
+
and vehicle.charging.settings.maximum_current is not None and vehicle.charging.settings.maximum_current.enabled \
|
1058
|
+
and vehicle.charging.settings.maximum_current.value is not None:
|
1059
|
+
if vehicle.charging.settings.maximum_current.value <= 6:
|
1060
|
+
command_dict['maxChargeCurrentAC'] = 'reduced'
|
1061
|
+
else:
|
1062
|
+
command_dict['maxChargeCurrentAC'] = 'maximum'
|
1063
|
+
else:
|
1064
|
+
command_dict['maxChargeCurrentAC'] = 'maximum'
|
1065
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
927
1066
|
elif command_arguments['command'] == ChargingStartStopCommand.Command.STOP:
|
928
1067
|
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/charging/requests/stop'
|
929
1068
|
command_response: requests.Response = self.session.post(url, data='{}', allow_redirects=True)
|
@@ -949,21 +1088,203 @@ class Connector(BaseConnector):
|
|
949
1088
|
if 'command' not in command_arguments:
|
950
1089
|
raise CommandError('Command argument missing')
|
951
1090
|
command_dict = {}
|
952
|
-
command_str: Optional[str] = None
|
953
1091
|
if command_arguments['command'] == ClimatizationStartStopCommand.Command.START:
|
954
|
-
|
1092
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/{vin}/climatisation/start'
|
1093
|
+
if vehicle.climatization.settings is None:
|
1094
|
+
raise CommandError('Could not control climatisation, there are no climatisation settings for the vehicle available.')
|
1095
|
+
if 'target_temperature' in command_arguments:
|
1096
|
+
# Round target temperature to nearest 0.5
|
1097
|
+
command_dict['targetTemperature'] = round(command_arguments['target_temperature'] * 2) / 2
|
1098
|
+
elif vehicle.climatization.settings.target_temperature is not None and vehicle.climatization.settings.target_temperature.enabled \
|
1099
|
+
and vehicle.climatization.settings.target_temperature.value is not None:
|
1100
|
+
temperature_value = vehicle.climatization.settings.target_temperature.value
|
1101
|
+
if vehicle.climatization.settings.target_temperature.unit == Temperature.C:
|
1102
|
+
command_dict['targetTemperatureUnit'] = 'celsius'
|
1103
|
+
elif vehicle.climatization.settings.target_temperature.unit == Temperature.F:
|
1104
|
+
command_dict['targetTemperatureUnit'] = 'farenheit'
|
1105
|
+
else:
|
1106
|
+
command_dict['targetTemperatureUnit'] = 'celsius'
|
1107
|
+
if temperature_value is not None:
|
1108
|
+
command_dict['targetTemperature'] = round(temperature_value * 2) / 2
|
1109
|
+
if 'target_temperature_unit' in command_arguments:
|
1110
|
+
if command_arguments['target_temperature_unit'] == Temperature.C:
|
1111
|
+
command_dict['targetTemperatureUnit'] = 'celsius'
|
1112
|
+
elif command_arguments['target_temperature_unit'] == Temperature.F:
|
1113
|
+
command_dict['targetTemperatureUnit'] = 'farenheit'
|
1114
|
+
else:
|
1115
|
+
command_dict['targetTemperatureUnit'] = 'celsius'
|
955
1116
|
elif command_arguments['command'] == ClimatizationStartStopCommand.Command.STOP:
|
956
|
-
|
1117
|
+
url: str = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/climatisation/requests/stop'
|
957
1118
|
else:
|
958
1119
|
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
959
|
-
|
960
|
-
url: str = f'https://ola.prod.code.seat.cloud.vwgroup.com/vehicles/{vin}/climatisation/requests/{command_str}'
|
961
1120
|
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
962
1121
|
if command_response.status_code not in [requests.codes['ok'], requests.codes['created']]:
|
963
1122
|
LOG.error('Could not start/stop air conditioning (%s: %s)', command_response.status_code, command_response.text)
|
964
1123
|
raise CommandError(f'Could not start/stop air conditioning ({command_response.status_code}: {command_response.text})')
|
965
1124
|
return command_arguments
|
966
1125
|
|
1126
|
+
def __on_spin(self, spin_command: SpinCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
1127
|
+
-> Union[str, Dict[str, Any]]:
|
1128
|
+
del spin_command
|
1129
|
+
if not isinstance(command_arguments, dict):
|
1130
|
+
raise CommandError('Command arguments are not a dictionary')
|
1131
|
+
if 'command' not in command_arguments:
|
1132
|
+
raise CommandError('Command argument missing')
|
1133
|
+
command_dict = {}
|
1134
|
+
if self.active_config['spin'] is None:
|
1135
|
+
raise CommandError('S-PIN is missing, please add S-PIN to your configuration or .netrc file')
|
1136
|
+
if 'spin' in command_arguments:
|
1137
|
+
command_dict['currentSpin'] = command_arguments['spin']
|
1138
|
+
else:
|
1139
|
+
if self.active_config['spin'] is None or self.active_config['spin'] == '':
|
1140
|
+
raise CommandError('S-PIN is missing, please add S-PIN to your configuration or .netrc file')
|
1141
|
+
command_dict['spin'] = self.active_config['spin']
|
1142
|
+
if command_arguments['command'] == SpinCommand.Command.VERIFY:
|
1143
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/users/{self.session.user_id}/spin/verify'
|
1144
|
+
else:
|
1145
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
1146
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
1147
|
+
if command_response.status_code != requests.codes['ok']:
|
1148
|
+
LOG.error('Could not execute spin command (%s: %s)', command_response.status_code, command_response.text)
|
1149
|
+
raise CommandError(f'Could not execute spin command ({command_response.status_code}: {command_response.text})')
|
1150
|
+
else:
|
1151
|
+
LOG.info('Spin verify command executed successfully')
|
1152
|
+
return command_arguments
|
1153
|
+
|
1154
|
+
def __on_wake_sleep(self, wake_sleep_command: WakeSleepCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
1155
|
+
-> Union[str, Dict[str, Any]]:
|
1156
|
+
if wake_sleep_command.parent is None or wake_sleep_command.parent.parent is None \
|
1157
|
+
or not isinstance(wake_sleep_command.parent.parent, GenericVehicle):
|
1158
|
+
raise CommandError('Object hierarchy is not as expected')
|
1159
|
+
if not isinstance(command_arguments, dict):
|
1160
|
+
raise CommandError('Command arguments are not a dictionary')
|
1161
|
+
vehicle: GenericVehicle = wake_sleep_command.parent.parent
|
1162
|
+
vin: Optional[str] = vehicle.vin.value
|
1163
|
+
if vin is None:
|
1164
|
+
raise CommandError('VIN in object hierarchy missing')
|
1165
|
+
if 'command' not in command_arguments:
|
1166
|
+
raise CommandError('Command argument missing')
|
1167
|
+
if command_arguments['command'] == WakeSleepCommand.Command.WAKE:
|
1168
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/vehicle-wakeup/request'
|
1169
|
+
|
1170
|
+
command_response: requests.Response = self.session.post(url, data='{}', allow_redirects=True)
|
1171
|
+
if command_response.status_code not in (requests.codes['ok'], requests.codes['no_content']):
|
1172
|
+
LOG.error('Could not execute wake command (%s: %s)', command_response.status_code, command_response.text)
|
1173
|
+
raise CommandError(f'Could not execute wake command ({command_response.status_code}: {command_response.text})')
|
1174
|
+
elif command_arguments['command'] == WakeSleepCommand.Command.SLEEP:
|
1175
|
+
raise CommandError('Sleep command not supported by vehicle. Vehicle will put itself to sleep')
|
1176
|
+
else:
|
1177
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
1178
|
+
return command_arguments
|
1179
|
+
|
1180
|
+
def __on_honk_flash(self, honk_flash_command: HonkAndFlashCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
1181
|
+
-> Union[str, Dict[str, Any]]:
|
1182
|
+
if honk_flash_command.parent is None or honk_flash_command.parent.parent is None \
|
1183
|
+
or not isinstance(honk_flash_command.parent.parent, GenericVehicle):
|
1184
|
+
raise CommandError('Object hierarchy is not as expected')
|
1185
|
+
if not isinstance(command_arguments, dict):
|
1186
|
+
raise CommandError('Command arguments are not a dictionary')
|
1187
|
+
vehicle: GenericVehicle = honk_flash_command.parent.parent
|
1188
|
+
vin: Optional[str] = vehicle.vin.value
|
1189
|
+
if vin is None:
|
1190
|
+
raise CommandError('VIN in object hierarchy missing')
|
1191
|
+
if 'command' not in command_arguments:
|
1192
|
+
raise CommandError('Command argument missing')
|
1193
|
+
command_dict = {}
|
1194
|
+
if command_arguments['command'] in [HonkAndFlashCommand.Command.FLASH, HonkAndFlashCommand.Command.HONK_AND_FLASH]:
|
1195
|
+
if 'duration' in command_arguments:
|
1196
|
+
command_dict['durationInSeconds'] = command_arguments['duration']
|
1197
|
+
else:
|
1198
|
+
command_dict['durationInSeconds'] = 10
|
1199
|
+
command_dict['mode'] = command_arguments['command'].value
|
1200
|
+
command_dict['userPosition'] = {}
|
1201
|
+
if vehicle.position is None or vehicle.position.latitude is None or vehicle.position.longitude is None \
|
1202
|
+
or vehicle.position.latitude.value is None or vehicle.position.longitude.value is None \
|
1203
|
+
or not vehicle.position.latitude.enabled or not vehicle.position.longitude.enabled:
|
1204
|
+
raise CommandError('Can only execute honk and flash commands if vehicle position is known')
|
1205
|
+
command_dict['userPosition']['latitude'] = vehicle.position.latitude.value
|
1206
|
+
command_dict['userPosition']['longitude'] = vehicle.position.longitude.value
|
1207
|
+
|
1208
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/honk-and-flash'
|
1209
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
1210
|
+
if command_response.status_code not in (requests.codes['ok'], requests.codes['no_content']):
|
1211
|
+
LOG.error('Could not execute honk or flash command (%s: %s)', command_response.status_code, command_response.text)
|
1212
|
+
raise CommandError(f'Could not execute honk or flash command ({command_response.status_code}: {command_response.text})')
|
1213
|
+
else:
|
1214
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
1215
|
+
return command_arguments
|
1216
|
+
|
1217
|
+
def __on_lock_unlock(self, lock_unlock_command: LockUnlockCommand, command_arguments: Union[str, Dict[str, Any]]) \
|
1218
|
+
-> Union[str, Dict[str, Any]]:
|
1219
|
+
if lock_unlock_command.parent is None or lock_unlock_command.parent.parent is None \
|
1220
|
+
or lock_unlock_command.parent.parent.parent is None or not isinstance(lock_unlock_command.parent.parent.parent, GenericVehicle):
|
1221
|
+
raise CommandError('Object hierarchy is not as expected')
|
1222
|
+
if not isinstance(command_arguments, dict):
|
1223
|
+
raise SetterError('Command arguments are not a dictionary')
|
1224
|
+
vehicle: GenericVehicle = lock_unlock_command.parent.parent.parent
|
1225
|
+
vin: Optional[str] = vehicle.vin.value
|
1226
|
+
if vin is None:
|
1227
|
+
raise CommandError('VIN in object hierarchy missing')
|
1228
|
+
if 'command' not in command_arguments:
|
1229
|
+
raise CommandError('Command argument missing')
|
1230
|
+
command_dict = {}
|
1231
|
+
if 'spin' in command_arguments:
|
1232
|
+
command_dict['spin'] = command_arguments['spin']
|
1233
|
+
else:
|
1234
|
+
if self.active_config['spin'] is None:
|
1235
|
+
raise CommandError('S-PIN is missing, please add S-PIN to your configuration or .netrc file')
|
1236
|
+
command_dict['spin'] = self.active_config['spin']
|
1237
|
+
if command_arguments['command'] == LockUnlockCommand.Command.LOCK:
|
1238
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/access/lock'
|
1239
|
+
elif command_arguments['command'] == LockUnlockCommand.Command.UNLOCK:
|
1240
|
+
url = f'https://ola.prod.code.seat.cloud.vwgroup.com/v1/vehicles/{vin}/access/unlock'
|
1241
|
+
else:
|
1242
|
+
raise CommandError(f'Unknown command {command_arguments["command"]}')
|
1243
|
+
command_response: requests.Response = self.session.post(url, data=json.dumps(command_dict), allow_redirects=True)
|
1244
|
+
if command_response.status_code != requests.codes['ok']:
|
1245
|
+
LOG.error('Could not execute locking command (%s: %s)', command_response.status_code, command_response.text)
|
1246
|
+
raise CommandError(f'Could not execute locking command ({command_response.status_code}: {command_response.text})')
|
1247
|
+
return command_arguments
|
1248
|
+
|
1249
|
+
def __on_air_conditioning_settings_change(self, attribute: GenericAttribute, value: Any) -> Any:
|
1250
|
+
"""
|
1251
|
+
Callback for the climatization setting change.
|
1252
|
+
"""
|
1253
|
+
if attribute.parent is None or not isinstance(attribute.parent, SeatCupraClimatization.Settings) \
|
1254
|
+
or attribute.parent.parent is None \
|
1255
|
+
or attribute.parent.parent.parent is None or not isinstance(attribute.parent.parent.parent, SeatCupraVehicle):
|
1256
|
+
raise SetterError('Object hierarchy is not as expected')
|
1257
|
+
settings: SeatCupraClimatization.Settings = attribute.parent
|
1258
|
+
vehicle: SeatCupraVehicle = attribute.parent.parent.parent
|
1259
|
+
vin: Optional[str] = vehicle.vin.value
|
1260
|
+
if vin is None:
|
1261
|
+
raise SetterError('VIN in object hierarchy missing')
|
1262
|
+
setting_dict = {}
|
1263
|
+
if settings.target_temperature.enabled and settings.target_temperature.value is not None:
|
1264
|
+
# Round target temperature to nearest 0.5
|
1265
|
+
# Check if the attribute changed is the target_temperature attribute
|
1266
|
+
if isinstance(attribute, TemperatureAttribute) and attribute.id == 'target_temperature':
|
1267
|
+
setting_dict['targetTemperature'] = round(value * 2) / 2
|
1268
|
+
else:
|
1269
|
+
setting_dict['targetTemperature'] = round(settings.target_temperature.value * 2) / 2
|
1270
|
+
if settings.target_temperature.unit == Temperature.C:
|
1271
|
+
setting_dict['targetTemperatureUnit'] = 'celsius'
|
1272
|
+
elif settings.target_temperature.unit == Temperature.F:
|
1273
|
+
setting_dict['targetTemperatureUnit'] = 'farenheit'
|
1274
|
+
else:
|
1275
|
+
setting_dict['targetTemperatureUnit'] = 'celsius'
|
1276
|
+
if isinstance(attribute, BooleanAttribute) and attribute.id == 'climatisation_without_external_power':
|
1277
|
+
setting_dict['climatisationWithoutExternalPower'] = value
|
1278
|
+
elif settings.climatization_without_external_power.enabled and settings.climatization_without_external_power.value is not None:
|
1279
|
+
setting_dict['climatisationWithoutExternalPower'] = settings.climatization_without_external_power.value
|
1280
|
+
|
1281
|
+
url: str = f'https://ola.prod.code.seat.cloud.vwgroup.com/v2/vehicles/{vin}/climatisation/settings'
|
1282
|
+
settings_response: requests.Response = self.session.post(url, data=json.dumps(setting_dict), allow_redirects=True)
|
1283
|
+
if settings_response.status_code not in [requests.codes['ok'], requests.codes['created']]:
|
1284
|
+
LOG.error('Could not set climatization settings (%s) %s', settings_response.status_code, settings_response.text)
|
1285
|
+
raise SetterError(f'Could not set value ({settings_response.status_code}): {settings_response.text}')
|
1286
|
+
return value
|
1287
|
+
|
967
1288
|
def get_version(self) -> str:
|
968
1289
|
return __version__
|
969
1290
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|
5
5
|
from carconnectivity.vehicle import GenericVehicle, ElectricVehicle, CombustionVehicle, HybridVehicle
|
6
6
|
|
7
7
|
from carconnectivity_connectors.seatcupra.capability import Capabilities
|
8
|
+
from carconnectivity_connectors.seatcupra.climatization import SeatCupraClimatization
|
8
9
|
|
9
10
|
SUPPORT_IMAGES = False
|
10
11
|
try:
|
@@ -38,14 +39,15 @@ class SeatCupraVehicle(GenericVehicle): # pylint: disable=too-many-instance-att
|
|
38
39
|
self.capabilities.parent = self
|
39
40
|
if SUPPORT_IMAGES:
|
40
41
|
self._car_images = origin._car_images
|
41
|
-
|
42
42
|
else:
|
43
43
|
super().__init__(vin=vin, garage=garage, managing_connector=managing_connector)
|
44
|
+
self.climatization = SeatCupraClimatization(vehicle=self, origin=self.climatization)
|
44
45
|
self.capabilities: Capabilities = Capabilities(vehicle=self)
|
45
46
|
if SUPPORT_IMAGES:
|
46
47
|
self._car_images: Dict[str, Image.Image] = {}
|
47
48
|
|
48
49
|
|
50
|
+
|
49
51
|
class SeatCupraElectricVehicle(ElectricVehicle, SeatCupraVehicle):
|
50
52
|
"""
|
51
53
|
Represents a Seat/Cupra electric vehicle.
|
File without changes
|
File without changes
|