tesla-fleet-api 1.0.0__tar.gz → 1.0.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. {tesla_fleet_api-1.0.0/tesla_fleet_api.egg-info → tesla_fleet_api-1.0.1}/PKG-INFO +1 -1
  2. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/setup.py +1 -1
  3. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/const.py +1 -1
  4. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/bluetooth.py +6 -1
  5. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/__init__.py +13 -0
  6. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/bluetooth.py +219 -0
  7. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/commands.py +1286 -0
  8. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/fleet.py +847 -0
  9. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/__init__.py +0 -0
  10. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/__init__.pyi +9 -0
  11. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/car_server_pb2.py +175 -0
  12. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/car_server_pb2.pyi +904 -0
  13. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/common_pb2.py +33 -0
  14. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/common_pb2.pyi +130 -0
  15. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/errors_pb2.py +17 -0
  16. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/errors_pb2.pyi +32 -0
  17. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/keys_pb2.py +15 -0
  18. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/keys_pb2.pyi +21 -0
  19. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/managed_charging_pb2.py +15 -0
  20. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/managed_charging_pb2.pyi +17 -0
  21. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/signatures_pb2.py +35 -0
  22. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/signatures_pb2.pyi +152 -0
  23. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/universal_message_pb2.py +30 -0
  24. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/universal_message_pb2.pyi +148 -0
  25. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/vcsec_pb2.py +79 -0
  26. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/vcsec_pb2.pyi +482 -0
  27. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/vehicle_pb2.py +125 -0
  28. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/proto/vehicle_pb2.pyi +1183 -0
  29. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/signed.py +56 -0
  30. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/vehicle.py +19 -0
  31. tesla_fleet_api-1.0.1/tesla_fleet_api/tesla/vehicle/vehicles.py +46 -0
  32. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1/tesla_fleet_api.egg-info}/PKG-INFO +1 -1
  33. tesla_fleet_api-1.0.1/tesla_fleet_api.egg-info/SOURCES.txt +55 -0
  34. tesla_fleet_api-1.0.0/tesla_fleet_api.egg-info/SOURCES.txt +0 -28
  35. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/LICENSE +0 -0
  36. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/README.md +0 -0
  37. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/pyproject.toml +0 -0
  38. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/setup.cfg +0 -0
  39. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/__init__.py +0 -0
  40. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/exceptions.py +0 -0
  41. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/ratecalculator.py +0 -0
  42. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/__init__.py +0 -0
  43. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/charging.py +0 -0
  44. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/energysite.py +0 -0
  45. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/fleet.py +0 -0
  46. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/oauth.py +0 -0
  47. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/partner.py +0 -0
  48. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/tesla.py +0 -0
  49. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tesla/user.py +0 -0
  50. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/teslemetry/__init__.py +0 -0
  51. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/teslemetry/teslemetry.py +0 -0
  52. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/teslemetry/vehicle.py +0 -0
  53. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tessie/__init__.py +0 -0
  54. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tessie/tessie.py +0 -0
  55. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api/tessie/vehicle.py +0 -0
  56. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api.egg-info/dependency_links.txt +0 -0
  57. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api.egg-info/requires.txt +0 -0
  58. {tesla_fleet_api-1.0.0 → tesla_fleet_api-1.0.1}/tesla_fleet_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tesla_fleet_api
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Tesla Fleet API library for Python
5
5
  Home-page: https://github.com/Teslemetry/python-tesla-fleet-api
6
6
  Author: Brett Adams
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="tesla_fleet_api",
8
- version="1.0.0",
8
+ version="1.0.1",
9
9
  author="Brett Adams",
10
10
  author_email="hello@teslemetry.com",
11
11
  description="Tesla Fleet API library for Python",
@@ -3,7 +3,7 @@
3
3
  from enum import Enum
4
4
  import logging
5
5
 
6
- VERSION = "1.0.0"
6
+ VERSION = "1.0.1"
7
7
  LOGGER = logging.getLogger(__package__)
8
8
  SERVERS = {
9
9
  "na": "https://fleet-api.prd.na.vn.cloud.tesla.com",
@@ -1,5 +1,6 @@
1
1
  """Bluetooth only interface."""
2
2
 
3
+ import hashlib
3
4
  import re
4
5
  from .tesla import Tesla
5
6
  from .vehicle.bluetooth import VehicleBluetooth
@@ -16,7 +17,11 @@ class TeslaBluetooth(Tesla):
16
17
 
17
18
  def valid_name(self, name: str) -> bool:
18
19
  """Check if a BLE device name is a valid Tesla vehicle."""
19
- return bool(re.match("^S[a-f0-9]{16}[A-F]$", name))
20
+ return bool(re.match("^S[a-f0-9]{16}[CDRP]$", name))
21
+
22
+ def get_name(self, vin: str) -> str:
23
+ """Get the name of a vehicle."""
24
+ return "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
20
25
 
21
26
  class Vehicles(dict[str, VehicleBluetooth]):
22
27
  """Class containing and creating vehicles."""
@@ -0,0 +1,13 @@
1
+ """Tesla Fleet API classes."""
2
+
3
+ from .vehicles import Vehicles
4
+ from .fleet import VehicleFleet
5
+ from .bluetooth import VehicleBluetooth
6
+ from .signed import VehicleSigned
7
+
8
+ __all__ = [
9
+ "Vehicles",
10
+ "VehicleFleet",
11
+ "VehicleBluetooth",
12
+ "VehicleSigned",
13
+ ]
@@ -0,0 +1,219 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from asyncio import Future, get_running_loop
5
+ from typing import TYPE_CHECKING
6
+ from google.protobuf.message import DecodeError
7
+
8
+ from bleak import BleakClient, BleakScanner
9
+ from bleak.backends.characteristic import BleakGATTCharacteristic
10
+ from bleak.backends.device import BLEDevice
11
+ from cryptography.hazmat.primitives.asymmetric import ec
12
+
13
+ from tesla_fleet_api.tesla.vehicle.proto.keys_pb2 import Role
14
+
15
+ from .commands import Commands
16
+
17
+ from ...const import (
18
+ LOGGER,
19
+ )
20
+ from ...exceptions import (
21
+ MESSAGE_FAULTS,
22
+ WHITELIST_OPERATION_STATUS,
23
+ )
24
+
25
+ # Protocol
26
+ from .proto.car_server_pb2 import (
27
+ Response,
28
+ )
29
+ from .proto.signatures_pb2 import (
30
+ SessionInfo,
31
+ )
32
+ from .proto.universal_message_pb2 import (
33
+ Destination,
34
+ Domain,
35
+ RoutableMessage,
36
+ )
37
+ from .proto.vcsec_pb2 import (
38
+ FromVCSECMessage,
39
+ KeyFormFactor,
40
+ KeyMetadata,
41
+ PermissionChange,
42
+ PublicKey,
43
+ UnsignedMessage,
44
+ WhitelistOperation,
45
+
46
+ )
47
+
48
+ SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
49
+ WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
50
+ READ_UUID = "00000213-b2d1-43f0-9b88-960cebf8b91e"
51
+ VERSION_UUID = "00000214-b2d1-43f0-9b88-960cebf8b91e"
52
+
53
+ if TYPE_CHECKING:
54
+ from ..tesla import Tesla
55
+
56
+ def prependLength(message: bytes) -> bytearray:
57
+ """Prepend a 2-byte length to the payload."""
58
+ return bytearray([len(message) >> 8, len(message) & 0xFF]) + message
59
+
60
+ class VehicleBluetooth(Commands):
61
+ """Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
62
+
63
+ ble_name: str
64
+ client: BleakClient
65
+ _device: BLEDevice
66
+ _futures: dict[Domain, Future]
67
+ _ekey: ec.EllipticCurvePublicKey
68
+ _recv: bytearray = bytearray()
69
+ _recv_len: int = 0
70
+ _auth_method = "aes"
71
+
72
+ def __init__(
73
+ self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None
74
+ ):
75
+ super().__init__(parent, vin, key)
76
+ self.ble_name = "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
77
+ self._futures = {}
78
+
79
+ async def discover(self, scanner: BleakScanner = BleakScanner()) -> BleakClient:
80
+ """Find the Tesla BLE device."""
81
+
82
+ device = await scanner.find_device_by_name(self.ble_name)
83
+ if not device:
84
+ raise ValueError(f"Device {self.ble_name} not found")
85
+ self._device = device
86
+ self.client = BleakClient(self._device, services=[SERVICE_UUID])
87
+ LOGGER.debug(f"Discovered device {self._device.name} {self._device.address}")
88
+ return self.client
89
+
90
+ def create_client(self, mac:str):
91
+ """Create a client with a MAC."""
92
+ self.client = BleakClient(mac, services=[SERVICE_UUID])
93
+ return self.client
94
+
95
+ async def connect(self, mac:str | None = None) -> None:
96
+ """Connect to the Tesla BLE device."""
97
+ if mac is not None:
98
+ self.create_client(mac)
99
+ await self.client.connect()
100
+ await self.client.start_notify(READ_UUID, self._on_notify)
101
+
102
+ async def disconnect(self) -> bool:
103
+ """Disconnect from the Tesla BLE device."""
104
+ return await self.client.disconnect()
105
+
106
+ async def __aenter__(self) -> VehicleBluetooth:
107
+ """Enter the async context."""
108
+ await self.connect()
109
+ return self
110
+
111
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
112
+ """Exit the async context."""
113
+ await self.disconnect()
114
+
115
+ def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray):
116
+ """Receive data from the Tesla BLE device."""
117
+ if self._recv_len:
118
+ self._recv += data
119
+ else:
120
+ self._recv_len = int.from_bytes(data[:2], 'big')
121
+ self._recv = data[2:]
122
+ LOGGER.debug(f"Received {len(self._recv)} of {self._recv_len} bytes")
123
+ while len(self._recv) > self._recv_len:
124
+ LOGGER.warn(f"Received more data than expected: {len(self._recv)} > {self._recv_len}")
125
+ self._on_message(bytes(self._recv[:self._recv_len]))
126
+ self._recv_len = int.from_bytes(self._recv[self._recv_len:self._recv_len+2], 'big')
127
+ self._recv = self._recv[self._recv_len+2:]
128
+ continue
129
+ if len(self._recv) == self._recv_len:
130
+ self._on_message(bytes(self._recv))
131
+ self._recv = bytearray()
132
+ self._recv_len = 0
133
+
134
+ def _on_message(self, data:bytes):
135
+ """Receive messages from the Tesla BLE data."""
136
+ try:
137
+ msg = RoutableMessage.FromString(data)
138
+ except DecodeError as e:
139
+ LOGGER.error(f"Error parsing message: {e}")
140
+ return
141
+
142
+ # Update Session
143
+ if(msg.session_info):
144
+ info = SessionInfo.FromString(msg.session_info)
145
+ LOGGER.debug(f"Received session info: {info}")
146
+ self._sessions[msg.from_destination.domain].update(info)
147
+
148
+ if(msg.to_destination.routing_address != self._from_destination):
149
+ # Get the ephemeral key here and save to self._ekey
150
+ return
151
+
152
+ if msg.from_destination.domain == Domain.DOMAIN_VEHICLE_SECURITY:
153
+ submsg = FromVCSECMessage.FromString(msg.protobuf_message_as_bytes)
154
+ print(submsg)
155
+ elif msg.from_destination.domain == Domain.DOMAIN_INFOTAINMENT:
156
+ submsg = Response.FromString(msg.protobuf_message_as_bytes)
157
+ print(submsg)
158
+
159
+ if(self._futures[msg.from_destination.domain]):
160
+ LOGGER.debug(f"Received response for request {msg.request_uuid}")
161
+ self._futures[msg.from_destination.domain].set_result(msg)
162
+ return
163
+
164
+ async def _create_future(self, domain: Domain) -> Future:
165
+ if(not self._sessions[domain].lock.locked):
166
+ raise ValueError("Session is not locked")
167
+ self._futures[domain] = get_running_loop().create_future()
168
+ return self._futures[domain]
169
+
170
+ async def _send(self, msg: RoutableMessage) -> RoutableMessage:
171
+ """Serialize a message and send to the vehicle and wait for a response."""
172
+ domain = msg.to_destination.domain
173
+ async with self._sessions[domain].lock:
174
+ LOGGER.debug(f"Sending message {msg}")
175
+ future = await self._create_future(domain)
176
+ payload = prependLength(msg.SerializeToString())
177
+
178
+ await self.client.write_gatt_char(WRITE_UUID, payload, True)
179
+
180
+ resp = await future
181
+ LOGGER.debug(f"Received message {resp}")
182
+
183
+ if resp.signedMessageStatus.signed_message_fault:
184
+ raise MESSAGE_FAULTS[resp.signedMessageStatus.signed_message_fault]
185
+
186
+ return resp
187
+
188
+ async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY):
189
+ """Pair the key."""
190
+
191
+ request = UnsignedMessage(
192
+ WhitelistOperation=WhitelistOperation(
193
+ addKeyToWhitelistAndAddPermissions=PermissionChange(
194
+ key=PublicKey(PublicKeyRaw=self._public_key),
195
+ keyRole=role
196
+ ),
197
+ metadataForKey=KeyMetadata(
198
+ keyFormFactor=form
199
+ )
200
+ )
201
+ )
202
+ msg = RoutableMessage(
203
+ to_destination=Destination(
204
+ domain=Domain.DOMAIN_VEHICLE_SECURITY
205
+ ),
206
+ from_destination=Destination(
207
+ routing_address=self._from_destination
208
+ ),
209
+ protobuf_message_as_bytes=request.SerializeToString(),
210
+ )
211
+ resp = await self._send(msg)
212
+ respMsg = FromVCSECMessage.FromString(resp.protobuf_message_as_bytes)
213
+ print(respMsg)
214
+ if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation):
215
+ if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation < len(WHITELIST_OPERATION_STATUS)):
216
+ raise WHITELIST_OPERATION_STATUS[respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation]
217
+ else:
218
+ raise ValueError(f"Unknown whitelist operation status: {respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation}")
219
+ return