diff --git a/README.md b/README.md index 0167198..c7ab9ef 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,115 @@ async def main(): await stream.disconnect() ``` + +## Public Methods in TeslemetryStream Class + +### `__init__(self, session: aiohttp.ClientSession, access_token: str, server: str | None = None, vin: str | None = None, parse_timestamp: bool = False)` +Initialize the TeslemetryStream client. + +### `get_vehicle(self, vin: str) -> TeslemetryStreamVehicle` +Create a vehicle stream. + +### `connected(self) -> bool` +Return if connected. + +### `get_config(self, vin: str | None = None) -> None` +Get the current stream config. + +### `find_server(self) -> None` +Find the server using metadata. + +### `update_fields(self, fields: dict, vin: str) -> dict` +Modify the Fleet Telemetry configuration. + +### `replace_fields(self, fields: dict, vin: str) -> dict` +Replace the Fleet Telemetry configuration. + +### `config(self) -> dict` +Return current configuration. + +### `connect(self) -> None` +Connect to the telemetry stream. + +### `close(self) -> None` +Close connection. + +### `async_add_listener(self, callback: Callable, filters: dict | None = None) -> Callable[[], None]` +Add listener for data updates. + +### `listen(self)` +Listen to the telemetry stream. + +### `listen_Credits(self, callback: Callable[[dict[str, str | int]], None]) -> Callable[[], None]` +Add listener for credit events. + +### `listen_Balance(self, callback: Callable[[int], None]) -> Callable[[], None]` +Add listener for credit balance. + +## Public Methods in TeslemetryStreamVehicle Class + +### `__init__(self, stream: TeslemetryStream, vin: str)` +Initialize the TeslemetryStreamVehicle instance. + +### `get_config(self) -> None` +Get the current vehicle config. + +### `update_fields(self, fields: dict) -> dict` +Update Fleet Telemetry configuration for the vehicle. + +### `replace_fields(self, fields: dict) -> dict` +Replace Fleet Telemetry configuration for the vehicle. + +### `config(self) -> dict` +Return current configuration for the vehicle. + +### `listen_*` Methods +The `TeslemetryStreamVehicle` class contains several `listen_*` methods for various telemetry signals. These methods allow you to listen to specific signals and handle their data in a type-safe manner. The available `listen_*` methods and their callback types are: + +- `listen_BatteryLevel(callback: Callable[[int], None])` +- `listen_VehicleSpeed(callback: Callable[[int], None])` +- `listen_Location(callback: Callable[[dict], None])` +- `listen_ChargeState(callback: Callable[[str], None])` +- `listen_DoorState(callback: Callable[[dict], None])` +- `listen_HvacPower(callback: Callable[[str], None])` +- `listen_ClimateKeeperMode(callback: Callable[[str], None])` +- `listen_CabinOverheatProtectionMode(callback: Callable[[str], None])` +- `listen_DefrostMode(callback: Callable[[str], None])` +- `listen_SeatHeaterLeft(callback: Callable[[int], None])` +- `listen_SeatHeaterRight(callback: Callable[[int], None])` +- `listen_SeatHeaterRearLeft(callback: Callable[[int], None])` +- `listen_SeatHeaterRearRight(callback: Callable[[int], None])` +- `listen_SeatHeaterRearCenter(callback: Callable[[int], None])` +- `listen_SentryMode(callback: Callable[[bool], None])` +- `listen_ScheduledChargingMode(callback: Callable[[str], None])` +- `listen_ScheduledChargingPending(callback: Callable[[bool], None])` +- `listen_ScheduledChargingStartTime(callback: Callable[[str], None])` +- `listen_ScheduledDepartureTime(callback: Callable[[str], None])` +- `listen_SoftwareUpdateVersion(callback: Callable[[str], None])` +- `listen_SoftwareUpdateDownloadPercentComplete(callback: Callable[[int], None])` +- `listen_SoftwareUpdateExpectedDurationMinutes(callback: Callable[[int], None])` +- `listen_SoftwareUpdateInstallationPercentComplete(callback: Callable[[int], None])` +- `listen_SoftwareUpdateScheduledStartTime(callback: Callable[[str], None])` +- `listen_ChargingCableType(callback: Callable[[str], None])` +- `listen_FastChargerType(callback: Callable[[str], None])` +- `listen_ChargePort(callback: Callable[[str], None])` +- `listen_ChargePortLatch(callback: Callable[[str], None])` +- `listen_ChargePortDoorOpen(callback: Callable[[bool], None])` +- `listen_ChargeEnableRequest(callback: Callable[[bool], None])` +- `listen_ChargeCurrentRequest(callback: Callable[[int], None])` +- `listen_ChargeCurrentRequestMax(callback: Callable[[int], None])` +- `listen_ChargeAmps(callback: Callable[[int], None])` +- `listen_ChargerPhases(callback: Callable[[int], None])` +- `listen_ChargeLimitSoc(callback: Callable[[int], None])` +- `listen_ChargeState(callback: Callable[[str], None])` +- `listen_ChargingCableType(callback: Callable[[str], None])` +- `listen_FastChargerType(callback: Callable[[str], None])` +- `listen_ChargePort(callback: Callable[[str], None])` +- `listen_ChargePortLatch(callback: Callable[[str], None])` +- `listen_ChargePortDoorOpen(callback: Callable[[bool], None])` +- `listen_ChargeEnableRequest(callback: Callable[[bool], None])` +- `listen_ChargeCurrentRequest(callback: Callable[[int], None])` +- `listen_ChargeCurrentRequestMax(callback: Callable[[int], None])` +- `listen_ChargeAmps(callback: Callable[[int], None])` +- `listen_ChargerPhases(callback: Callable[[int], None])` +- `listen_ChargeLimitSoc(callback: Callable[[int], None])` diff --git a/teslemetry_stream/stream.py b/teslemetry_stream/stream.py index 5c95cd9..52aa406 100644 --- a/teslemetry_stream/stream.py +++ b/teslemetry_stream/stream.py @@ -29,6 +29,15 @@ def __init__( vin: str | None = None, parse_timestamp: bool = False, ): + """ + Initialize the TeslemetryStream client. + + :param session: An aiohttp ClientSession. + :param access_token: Access token for authentication. + :param server: Teslemetry server to connect to. + :param vin: Vehicle Identification Number. + :param parse_timestamp: Whether to parse timestamps. + """ if server and not server.endswith(".teslemetry.com"): raise ValueError("Server must be on the teslemetry.com domain") @@ -44,7 +53,12 @@ def __init__( self.vehicle = self.get_vehicle(self.vin) def get_vehicle(self, vin: str) -> TeslemetryStreamVehicle: - """Create a vehicle stream.""" + """ + Create a vehicle stream. + + :param vin: Vehicle Identification Number. + :return: TeslemetryStreamVehicle instance. + """ if self.vin is not None and self.vin != vin: raise AttributeError("Stream started in single vehicle mode") if vin not in self.vehicles: @@ -53,19 +67,28 @@ def get_vehicle(self, vin: str) -> TeslemetryStreamVehicle: @property def connected(self) -> bool: - """Return if connected.""" + """ + Return if connected. + + :return: True if connected, False otherwise. + """ return self._response is not None async def get_config(self, vin: str | None = None) -> None: - """Get the current stream config.""" + """ + Get the current stream config. + + :param vin: Vehicle Identification Number. + """ if not self.server: await self.find_server() if hasattr(self, 'vehicle'): await self.vehicle.get_config() async def find_server(self) -> None: - """Find the server using metadata.""" - + """ + Find the server using metadata. + """ req = await self._session.get( "https://api.teslemetry.com/api/metadata", headers=self._headers, @@ -74,10 +97,14 @@ async def find_server(self) -> None: response = await req.json() self.server = f"{response["region"].lower()}.teslemetry.com" - - async def update_fields(self, fields: dict, vin: str) -> dict: - """Update Fleet Telemetry configuration""" + """ + Update Fleet Telemetry configuration. + + :param fields: Dictionary of fields to update. + :param vin: Vehicle Identification Number. + :return: Response JSON as a dictionary. + """ resp = await self._session.patch( f"https://api.teslemetry.com/api/config/{self.vin}", headers=self._headers, @@ -89,7 +116,13 @@ async def update_fields(self, fields: dict, vin: str) -> dict: return await resp.json() async def replace_fields(self, fields: dict, vin: str) -> dict: - """Replace Fleet Telemetry configuration""" + """ + Replace Fleet Telemetry configuration. + + :param fields: Dictionary of fields to replace. + :param vin: Vehicle Identification Number. + :return: Response JSON as a dictionary. + """ resp = await self._session.post( f"https://api.teslemetry.com/api/config/{self.vin}", headers=self._headers, @@ -102,13 +135,19 @@ async def replace_fields(self, fields: dict, vin: str) -> dict: @property def config(self) -> dict: - """Return current configuration.""" + """ + Return current configuration. + + :return: Configuration dictionary. + """ return { "hostname": self.server, } async def connect(self) -> None: - """Connect to the telemetry stream.""" + """ + Connect to the telemetry stream. + """ self.active = True if not self.server: await self.get_config() @@ -127,18 +166,30 @@ async def connect(self) -> None: ) def close(self) -> None: - """Close connection.""" + """ + Close connection. + """ if self._response is not None: LOGGER.debug("Disconnecting from %s", self.server) self._response.close() self._response = None def __aiter__(self): - """Return""" + """ + Return an asynchronous iterator. + + :return: Asynchronous iterator. + """ return self async def __anext__(self) -> dict: - """Return next event.""" + """ + Return next event. + + :return: Next event as a dictionary. + :raises StopAsyncIteration: If the stream is stopped. + :raises TeslemetryStreamEnded: If the stream is ended by the server. + """ try: if self.active is False: # Stop the stream and loop @@ -172,11 +223,19 @@ async def __anext__(self) -> dict: def async_add_listener( self, callback: Callable, filters: dict | None = None ) -> Callable[[], None]: - """Listen for data updates.""" + """ + Listen for data updates. + + :param callback: Callback function to handle updates. + :param filters: Filters to apply to the updates. + :return: Function to remove the listener. + """ schedule_refresh = not self._listeners def remove_listener() -> None: - """Remove update listener.""" + """ + Remove update listener. + """ self._listeners.pop(remove_listener) if not self._listeners: self.active = False @@ -190,8 +249,9 @@ def remove_listener() -> None: return remove_listener async def listen(self): - """Listen to the telemetry stream.""" - + """ + Listen to the telemetry stream. + """ async for event in self: if event: for listener, filters in self._listeners.values(): @@ -203,23 +263,37 @@ async def listen(self): LOGGER.debug("Listen has finished") def listen_Credits(self, callback: Callable[[dict[str, str | int]], None]) -> Callable[[], None]: - """Listen for credits update.""" + """ + Listen for credits update. + :param callback: Callback function to handle credits update. + :return: Function to remove the listener. + """ return self.async_add_listener( lambda x: callback(x["credits"]), {"credits": None} ) def listen_Balance(self, callback: Callable[[int], None]) -> Callable[[], None]: - """Listen for credits balance.""" + """ + Listen for credits balance. + :param callback: Callback function to handle credits balance. + :return: Function to remove the listener. + """ return self.async_add_listener( lambda x: callback(x["credits"]["balance"]), {"credits": {"balance": None}} ) def recursive_match(dict1, dict2): - """Recursively match dict1 with dict2.""" + """ + Recursively match dict1 with dict2. + + :param dict1: First dictionary. + :param dict2: Second dictionary. + :return: True if dict1 matches dict2, False otherwise. + """ if dict1 is not None: for key, value1 in dict1.items(): if key not in dict2: