Skip to content

Commit 967f5b3

Browse files
committed
More improvements
1 parent ded6eb9 commit 967f5b3

File tree

7 files changed

+179
-61
lines changed

7 files changed

+179
-61
lines changed

tesla_fleet_api/const.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,17 @@ class TeslaEnergyPeriod(StrEnum):
159159
MONTH = "month"
160160
YEAR = "year"
161161
LIFETIME = "lifetime"
162+
163+
class BluetoothVehicleData(StrEnum):
164+
CHARGE_STATE = "GetChargeState"
165+
CLIMATE_STATE = "GetClimateState"
166+
DRIVE_STATE = "GetDriveState"
167+
LOCATION_STATE = "GetLocationState"
168+
CLOSURES_STATE = "GetClosuresState"
169+
CHARGE_SCHEDULE_STATE = "GetChargeScheduleState"
170+
PRECONDITIONING_SCHEDULE_STATE = "GetPreconditioningScheduleState"
171+
TIRE_PRESSURE_STATE = "GetTirePressureState"
172+
MEDIA_STATE = "GetMediaState"
173+
MEDIA_DETAIL_STATE = "GetMediaDetailState"
174+
SOFTWARE_UPDATE_STATE = "GetSoftwareUpdateState"
175+
PARENTAL_CONTROLS_STATE = "GetParentalControlsState"

tesla_fleet_api/exceptions.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,26 @@ class TeslaFleetMessageFaultResponseSizeExceedsMTU(TeslaFleetMessageFault):
656656
message = "Client's request was received, but response size exceeded MTU"
657657
code = 25
658658

659+
class TeslaFleetMessageFaultRepeatedCounter(TeslaFleetMessageFault):
660+
"""The vehicle has seen this counter value before. Reset the counter and try again"""
661+
662+
message = "The vehicle has seen this counter value before. Reset the counter and try again"
663+
code = 26
664+
665+
666+
class TeslaFleetMessageFaultInvalidKeyHandle(TeslaFleetMessageFault):
667+
"""The key handle is not valid. The key may have been revoked or expired"""
668+
669+
message = "The key handle is not valid. The key may have been revoked or expired"
670+
code = 27
671+
672+
673+
class TeslaFleetMessageFaultRequiresResponseEncryption(TeslaFleetMessageFault):
674+
"""The response requires encryption but encryption was not requested"""
675+
676+
message = "The response requires encryption but encryption was not requested"
677+
code = 28
678+
659679

660680
MESSAGE_FAULTS = [
661681
None,
@@ -684,9 +704,9 @@ class TeslaFleetMessageFaultResponseSizeExceedsMTU(TeslaFleetMessageFault):
684704
TeslaFleetMessageFaultCommandRequiresAccountCredentials,
685705
TeslaFleetMessageFaultFieldExceedsMTU,
686706
TeslaFleetMessageFaultResponseSizeExceedsMTU,
687-
None,
688-
None,
689-
None,
707+
TeslaFleetMessageFaultRepeatedCounter,
708+
TeslaFleetMessageFaultInvalidKeyHandle,
709+
TeslaFleetMessageFaultRequiresResponseEncryption,
690710
]
691711

692712
class SignedMessageInformationFault(TeslaFleetError):

tesla_fleet_api/tesla/bluetooth.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import hashlib
44
import re
5+
from bleak.backends.device import BLEDevice
6+
from cryptography.hazmat.primitives.asymmetric import ec
57

68
from tesla_fleet_api.tesla.tesla import Tesla
79
from tesla_fleet_api.tesla.vehicle.bluetooth import VehicleBluetooth
@@ -36,8 +38,8 @@ def create(self, vin: str) -> VehicleBluetooth:
3638
"""Creates a specific vehicle."""
3739
return self.createBluetooth(vin)
3840

39-
def createBluetooth(self, vin: str) -> VehicleBluetooth:
41+
def createBluetooth(self, vin: str, key: ec.EllipticCurvePrivateKey | None = None, device: None | str | BLEDevice = None) -> VehicleBluetooth:
4042
"""Creates a specific vehicle."""
41-
vehicle = VehicleBluetooth(self._parent, vin)
43+
vehicle = VehicleBluetooth(self._parent, vin, key, device)
4244
self[vin] = vehicle
4345
return vehicle

tesla_fleet_api/tesla/partner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Any
2-
from ..const import Method
2+
from tesla_fleet_api.const import Method
33

44

55
class Partner:

tesla_fleet_api/tesla/vehicle/bluetooth.py

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,37 @@
1616

1717
from tesla_fleet_api.const import (
1818
LOGGER,
19+
BluetoothVehicleData
1920
)
2021
from tesla_fleet_api.exceptions import (
2122
MESSAGE_FAULTS,
2223
WHITELIST_OPERATION_STATUS,
2324
WhitelistOperationStatus,
25+
NotOnWhitelistFault,
2426
)
2527

2628
# Protocol
2729
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
30+
Action,
31+
VehicleAction,
2832
Response,
33+
GetVehicleData,
34+
GetChargeState,
35+
GetClimateState,
36+
GetDriveState,
37+
GetLocationState,
38+
GetClosuresState,
39+
GetChargeScheduleState,
40+
GetPreconditioningScheduleState,
41+
GetTirePressureState,
42+
GetMediaState,
43+
GetMediaDetailState,
44+
GetSoftwareUpdateState,
45+
GetParentalControlsState,
2946
)
3047
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
3148
SessionInfo,
49+
Session_Info_Status
3250
)
3351
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
3452
Destination,
@@ -44,7 +62,6 @@
4462
RKEAction_E,
4563
UnsignedMessage,
4664
WhitelistOperation,
47-
4865
)
4966

5067
SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
@@ -64,40 +81,40 @@ class VehicleBluetooth(Commands):
6481

6582
ble_name: str
6683
client: BleakClient
67-
_device: BLEDevice
6884
_futures: dict[Domain, Future]
6985
_ekey: ec.EllipticCurvePublicKey
7086
_recv: bytearray = bytearray()
7187
_recv_len: int = 0
7288
_auth_method = "aes"
7389

7490
def __init__(
75-
self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None
91+
self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None, device: None | str | BLEDevice = None
7692
):
7793
super().__init__(parent, vin, key)
7894
self.ble_name = "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
7995
self._futures = {}
96+
if device is not None:
97+
self.client = BleakClient(device, services=[SERVICE_UUID])
8098

8199
async def find_client(self, scanner: BleakScanner = BleakScanner()) -> BleakClient:
82100
"""Find the Tesla BLE device."""
83101

84102
device = await scanner.find_device_by_name(self.ble_name)
85103
if not device:
86104
raise ValueError(f"Device {self.ble_name} not found")
87-
self._device = device
88-
self.client = BleakClient(self._device, services=[SERVICE_UUID])
89-
LOGGER.debug(f"Discovered device {self._device.name} {self._device.address}")
105+
self.client = BleakClient(device, services=[SERVICE_UUID])
106+
LOGGER.debug(f"Discovered device {device.name} {device.address}")
90107
return self.client
91108

92-
def create_client(self, mac:str) -> BleakClient:
93-
"""Create a client using a MAC address."""
94-
self.client = BleakClient(mac, services=[SERVICE_UUID])
109+
def create_client(self, device: str|BLEDevice) -> BleakClient:
110+
"""Create a client using a MAC address or Bleak Device."""
111+
self.client = BleakClient(device, services=[SERVICE_UUID])
95112
return self.client
96113

97-
async def connect(self, mac:str | None = None) -> None:
114+
async def connect(self, device: str|BLEDevice | None = None) -> None:
98115
"""Connect to the Tesla BLE device."""
99-
if mac is not None:
100-
self.create_client(mac)
116+
if device is not None:
117+
self.create_client(device)
101118
await self.client.connect()
102119
await self.client.start_notify(READ_UUID, self._on_notify)
103120

@@ -139,12 +156,17 @@ def _on_message(self, data:bytes) -> None:
139156
msg = RoutableMessage.FromString(data)
140157
except DecodeError as e:
141158
LOGGER.error(f"Error parsing message: {e}")
159+
self._recv = bytearray()
160+
self._recv_len = 0
142161
return
143162

144163
# Update Session
145164
if(msg.session_info):
146165
info = SessionInfo.FromString(msg.session_info)
147-
LOGGER.debug(f"Received session info: {info}")
166+
# maybe dont?
167+
if(info.status == Session_Info_Status.SESSION_INFO_STATUS_KEY_NOT_ON_WHITELIST):
168+
self._futures[msg.from_destination.domain].set_exception(NotOnWhitelistFault())
169+
return
148170
self._sessions[msg.from_destination.domain].update(info)
149171

150172
if(msg.to_destination.routing_address != self._from_destination):
@@ -162,6 +184,8 @@ def _on_message(self, data:bytes) -> None:
162184
elif msg.from_destination.domain == Domain.DOMAIN_INFOTAINMENT:
163185
submsg = Response.FromString(msg.protobuf_message_as_bytes)
164186
LOGGER.warning(f"Received orphaned INFOTAINMENT response: {submsg}")
187+
else:
188+
LOGGER.warning(f"Received orphaned response: {msg}")
165189

166190
async def _create_future(self, domain: Domain) -> Future:
167191
if(not self._sessions[domain].lock.locked):
@@ -182,7 +206,7 @@ async def _send(self, msg: RoutableMessage) -> RoutableMessage:
182206
resp = await future
183207
LOGGER.debug(f"Received message {resp}")
184208

185-
if resp.signedMessageStatus.signed_message_fault:
209+
if resp.signedMessageStatus.signed_message_fault > 0:
186210
raise MESSAGE_FAULTS[resp.signedMessageStatus.signed_message_fault]
187211

188212
return resp
@@ -224,3 +248,26 @@ async def wake_up(self):
224248
return await self._sendVehicleSecurity(
225249
UnsignedMessage(RKEAction=RKEAction_E.RKE_ACTION_WAKE_VEHICLE)
226250
)
251+
252+
async def vehicle_data(self, endpoints: list[BluetoothVehicleData]):
253+
"""Get vehicle data."""
254+
return await self._sendInfotainment(
255+
Action(
256+
vehicleAction=VehicleAction(
257+
getVehicleData=GetVehicleData(
258+
getChargeState = GetChargeState() if BluetoothVehicleData.CHARGE_STATE in endpoints else None,
259+
getClimateState = GetClimateState() if BluetoothVehicleData.CLIMATE_STATE in endpoints else None,
260+
getDriveState = GetDriveState() if BluetoothVehicleData.DRIVE_STATE in endpoints else None,
261+
getLocationState = GetLocationState() if BluetoothVehicleData.LOCATION_STATE in endpoints else None,
262+
getClosuresState = GetClosuresState() if BluetoothVehicleData.CLOSURES_STATE in endpoints else None,
263+
getChargeScheduleState = GetChargeScheduleState() if BluetoothVehicleData.CHARGE_SCHEDULE_STATE in endpoints else None,
264+
getPreconditioningScheduleState = GetPreconditioningScheduleState() if BluetoothVehicleData.PRECONDITIONING_SCHEDULE_STATE in endpoints else None,
265+
getTirePressureState = GetTirePressureState() if BluetoothVehicleData.TIRE_PRESSURE_STATE in endpoints else None,
266+
getMediaState = GetMediaState() if BluetoothVehicleData.MEDIA_STATE in endpoints else None,
267+
getMediaDetailState = GetMediaDetailState() if BluetoothVehicleData.MEDIA_DETAIL_STATE in endpoints else None,
268+
getSoftwareUpdateState = GetSoftwareUpdateState() if BluetoothVehicleData.SOFTWARE_UPDATE_STATE in endpoints else None,
269+
getParentalControlsState = GetParentalControlsState() if BluetoothVehicleData.PARENTAL_CONTROLS_STATE in endpoints else None,
270+
)
271+
)
272+
)
273+
)

0 commit comments

Comments
 (0)