Skip to content

Commit

Permalink
Add methods for operations to the wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
Prior99 committed Sep 19, 2024
1 parent 05276fb commit 495e55f
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 57 deletions.
103 changes: 99 additions & 4 deletions myskoda/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,21 +364,116 @@ def on_event(event: Event) -> None:

@cli.command()
@click.option("temperature", "--temperature", type=float, required=True)
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.pass_context
async def start_air_conditioning(ctx: Context, temperature: float, vin: str) -> None:
async def start_air_conditioning(
ctx: Context,
temperature: float,
timeout: float, # noqa: ASYNC109
vin: str,
) -> None:
"""Start the air conditioning with the provided target temperature in °C."""
myskoda: MySkoda = ctx.obj["myskoda"]
await myskoda.start_air_conditioning(vin, temperature)
async with asyncio.timeout(timeout):
await myskoda.start_air_conditioning(vin, temperature)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.pass_context
async def stop_air_conditioning(ctx: Context, vin: str) -> None:
async def stop_air_conditioning(ctx: Context, timeout: float, vin: str) -> None: # noqa: ASYNC109
"""Stop the air conditioning."""
myskoda: MySkoda = ctx.obj["myskoda"]
await myskoda.stop_air_conditioning(vin)
async with asyncio.timeout(timeout):
await myskoda.stop_air_conditioning(vin)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.option("temperature", "--temperature", type=float, required=True)
@click.pass_context
async def set_target_temperature(
ctx: Context,
timeout: float, # noqa: ASYNC109
vin: str,
temperature: float,
) -> None:
"""Set the air conditioning's target temperature in °C."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.set_target_temperature(vin, temperature)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.pass_context
async def start_window_heating(ctx: Context, timeout: float, vin: str) -> None: # noqa: ASYNC109
"""Start heating both the front and rear window."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.start_window_heating(vin)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.pass_context
async def stop_window_heating(ctx: Context, timeout: float, vin: str) -> None: # noqa: ASYNC109
"""Stop heating both the front and rear window."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.stop_window_heating(vin)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.option("limit", "--limit", type=float, required=True)
@click.pass_context
async def set_charge_limit(ctx: Context, timeout: float, vin: str, limit: int) -> None: # noqa: ASYNC109
"""Set the maximum charge limit in percent."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.set_charge_limit(vin, limit)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.option("enabled", "--enabled", type=bool, required=True)
@click.pass_context
async def set_battery_care_mode(ctx: Context, timeout: float, vin: str, enabled: bool) -> None: # noqa: ASYNC109
"""Enable or disable the battery care mode."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.set_battery_care_mode(vin, enabled)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.option("enabled", "--enabled", type=bool, required=True)
@click.pass_context
async def set_reduced_current_limit(ctx: Context, timeout: float, vin: str, enabled: bool) -> None: # noqa: ASYNC109
"""Enable reducing the current limit by which the car is charged."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.set_reduced_current_limit(vin, enabled)


@cli.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.pass_context
async def wakeup(ctx: Context, timeout: float, vin: str) -> None: # noqa: ASYNC109
"""Wake the vehicle up. Can be called maximum three times a day."""
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
await myskoda.wakeup(vin)


def c_open(cond: OpenState) -> str:
Expand Down
69 changes: 19 additions & 50 deletions myskoda/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import ssl
from asyncio import Future, get_event_loop
from collections.abc import Callable
from enum import StrEnum
from typing import Literal, cast
from typing import cast

from asyncio_paho.client import AsyncioPahoClient
from paho.mqtt.client import MQTTMessage
Expand Down Expand Up @@ -37,13 +36,7 @@
TOPIC_RE = re.compile("^(.*?)/(.*?)/(.*?)/(.*?)$")


class OperationListenerType(StrEnum):
OPERATION_NAME = "OPERATION_NAME"
TRACE_ID = "TRACE_ID"


class OperationListenerForName:
type: Literal[OperationListenerType.OPERATION_NAME] = OperationListenerType.OPERATION_NAME
class OperationListener:
operation_name: OperationName
future: Future[OperationRequest]

Expand All @@ -54,19 +47,6 @@ def __init__( # noqa: D107
self.future = future


class OperationListenerForTraceId:
type: Literal[OperationListenerType.TRACE_ID] = OperationListenerType.TRACE_ID
trace_id: str
future: Future[OperationRequest]

def __init__(self, trace_id: str, future: Future[OperationRequest]) -> None: # noqa: D107
self.trace_id = trace_id
self.future = future


OperationListener = OperationListenerForTraceId | OperationListenerForName


class Mqtt:
api: RestApi
user: User
Expand Down Expand Up @@ -116,10 +96,10 @@ def _wait_for_connection(self) -> Future[None]:

def wait_for_operation(self, operation_name: OperationName) -> Future[OperationRequest]:
"""Wait until the next operation of the specified type completes."""
_LOGGER.debug("Waiting for operation %s to start and complete", operation_name)
_LOGGER.debug("Waiting for operation %s complete.", operation_name)
future: Future[OperationRequest] = get_event_loop().create_future()

self._operation_listeners.append(OperationListenerForName(operation_name, future))
self._operation_listeners.append(OperationListener(operation_name, future))

return future

Expand Down Expand Up @@ -151,37 +131,27 @@ def _emit(self, event: Event) -> None:

self._handle_operation(event)

def _handle_operation_in_progress(self, operation: OperationRequest) -> None:
listeners = self._operation_listeners
self._operation_listeners = []
for listener in listeners:
if (
listener.type != OperationListenerType.OPERATION_NAME
or listener.operation_name != operation.operation
):
self._operation_listeners.append(listener)
continue
_LOGGER.debug(
"Converting listener for operation name '%s' to trace '%s'.",
operation.operation,
operation.trace_id,
)
self._operation_listeners.append(
OperationListenerForTraceId(operation.trace_id, listener.future)
)

def _handle_operation_completed(self, operation: OperationRequest) -> None:
listeners = self._operation_listeners
self._operation_listeners = []
for listener in listeners:
if (
listener.type != OperationListenerType.TRACE_ID
or listener.trace_id != operation.trace_id
):
if listener.operation_name != operation.operation:
self._operation_listeners.append(listener)
continue
_LOGGER.debug("Resolving listener for trace id '%s'.", operation.trace_id)
listener.future.set_result(operation)

if operation.status == OperationStatus.ERROR:
_LOGGER.error(
"Resolving listener for operation '%s' with error '%s'.",
operation.operation,
operation.error_code,
)
listener.future.set_exception(OperationFailedError(operation))
else:
if operation.status == OperationStatus.COMPLETED_WARNING:
_LOGGER.warning("Operation '%s' completed with warnings.", operation.operation)

_LOGGER.debug("Resolving listener for operation '%s'.", operation.operation)
listener.future.set_result(operation)

def _handle_operation(self, event: Event) -> None:
if event.type != EventType.OPERATION:
Expand All @@ -193,7 +163,6 @@ def _handle_operation(self, event: Event) -> None:
event.operation.operation,
event.operation.trace_id,
)
self._handle_operation_in_progress(event.operation)
return

_LOGGER.debug(
Expand Down
2 changes: 1 addition & 1 deletion myskoda/myskoda.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async def start_window_heating(self, vin: str) -> None:

async def set_target_temperature(self, vin: str, temperature: float) -> None:
"""Set the air conditioning's target temperature in °C."""
future = self.mqtt.wait_for_operation(OperationName.UPDATE_TARGET_TEMPERATURE)
future = self.mqtt.wait_for_operation(OperationName.SET_AIR_CONDITIONING_TARGET_TEMPERATURE)
await self.rest_api.set_target_temperature(vin, temperature)
await future

Expand Down
4 changes: 2 additions & 2 deletions myskoda/rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ async def set_charge_limit(self, vin: str, limit: int) -> None:
# TODO @dvx76: Maybe refactor for FBT001
async def set_battery_care_mode(self, vin: str, enabled: bool) -> None:
"""Enable or disable the battery care mode."""
_LOGGER.debug("Setting battery care mode for vehicle %s to %b", vin, enabled)
_LOGGER.debug("Setting battery care mode for vehicle %s to %r", vin, enabled)
json_data = {"chargingCareMode": "ACTIVATED" if enabled else "DEACTIVATED"}
async with self.session.put(
f"{BASE_URL_SKODA}/api/v1/charging/{vin}/set-care-mode",
Expand All @@ -290,7 +290,7 @@ async def set_battery_care_mode(self, vin: str, enabled: bool) -> None:
# TODO @dvx76: Maybe refactor for FBT001
async def set_reduced_current_limit(self, vin: str, reduced: bool) -> None:
"""Enable reducing the current limit by which the car is charged."""
_LOGGER.debug("Setting reduced charging for vehicle %s to %b", vin, reduced)
_LOGGER.debug("Setting reduced charging for vehicle %s to %r", vin, reduced)
json_data = {"chargingCurrent": "REDUCED" if reduced else "MAXIMUM"}
async with self.session.put(
f"{BASE_URL_SKODA}/api/v1/charging/{vin}/set-charging-current",
Expand Down

0 comments on commit 495e55f

Please sign in to comment.