Skip to content

Commit

Permalink
add set-ac-timer feature
Browse files Browse the repository at this point in the history
  • Loading branch information
prvakt committed Jan 7, 2025
1 parent 4b0b323 commit 5759e93
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 1 deletion.
2 changes: 2 additions & 0 deletions myskoda/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
honk_flash,
lock,
set_ac_at_unlock,
set_ac_timer,
set_ac_without_external_power,
set_auto_unlock_plug,
set_charge_limit,
Expand Down Expand Up @@ -179,6 +180,7 @@ async def disconnect( # noqa: PLR0913
cli.add_command(set_auto_unlock_plug)
cli.add_command(departure_timers)
cli.add_command(set_departure_timer)
cli.add_command(set_ac_timer)


if __name__ == "__main__":
Expand Down
35 changes: 35 additions & 0 deletions myskoda/cli/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,38 @@ async def set_departure_timer(
print(f"No timer found with ID {timer_id}.")
else:
print("No DepartureInfo found for the given VIN.")


@click.command()
@click.option("timeout", "--timeout", type=float, default=300)
@click.argument("vin")
@click.option("timer", "--timer", type=click.Choice(["1", "2"]), required=True)
@click.option("enabled", "--enabled", type=bool, required=True)
@click.pass_context
@mqtt_required
async def set_ac_timer(
ctx: Context,
timeout: float, # noqa: ASYNC109
vin: str,
timer: str,
enabled: bool,
) -> None:
"""Enable or disable selected air-conditioning timer."""
timer_id = int(timer)
myskoda: MySkoda = ctx.obj["myskoda"]
async with asyncio.timeout(timeout):
# Get all timers from vehicle first
air_conditioning = await myskoda.get_air_conditioning(vin)
if air_conditioning is not None:
selected_timer = (
next((t for t in air_conditioning.timers if t.id == timer_id), None)
if air_conditioning.timers
else None
)
if selected_timer is not None:
selected_timer.enabled = enabled
await myskoda.set_ac_timer(vin, selected_timer)
else:
print(f"No timer found with ID {timer_id}.")
else:
print("No AirConditioning found for the given VIN.")
7 changes: 7 additions & 0 deletions myskoda/myskoda.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
AirConditioningAtUnlock,
AirConditioningWithoutExternalPower,
SeatHeating,
Timer,
WindowHeating,
)
from .models.auxiliary_heating import AuxiliaryConfig, AuxiliaryHeating
Expand Down Expand Up @@ -282,6 +283,12 @@ async def stop_auxiliary_heating(self, vin: str) -> None:
await self.rest_api.stop_auxiliary_heating(vin)
await future

async def set_ac_timer(self, vin: str, timer: Timer) -> None:
"""Send provided air-conditioning timer to the vehicle."""
future = self._wait_for_operation(OperationName.SET_AIR_CONDITIONING_TIMERS)
await self.rest_api.set_ac_timer(vin, timer)
await future

async def lock(self, vin: str, spin: str) -> None:
"""Lock the car."""
future = self._wait_for_operation(OperationName.LOCK)
Expand Down
13 changes: 13 additions & 0 deletions myskoda/rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
AirConditioningAtUnlock,
AirConditioningWithoutExternalPower,
SeatHeating,
Timer,
WindowHeating,
)
from .models.auxiliary_heating import AuxiliaryConfig, AuxiliaryHeating
Expand Down Expand Up @@ -584,6 +585,18 @@ async def set_departure_timer(self, vin: str, timer: DepartureTimer) -> None:
json=json_data,
)

async def set_ac_timer(self, vin: str, timer: Timer) -> None:
"""Set air-conditioning timer."""
_LOGGER.debug(
"Setting air-conditioning timer #%i for vehicle %s to %r", timer.id, vin, timer.enabled
)

json_data = {"timers": [timer.to_dict()]}
await self._make_post_request(
url=f"/v2/air-conditioning/{vin}/timers",
json=json_data,
)

def _deserialize[T](self, text: str, deserialize: Callable[[str], T]) -> T:
try:
data = deserialize(text)
Expand Down
36 changes: 36 additions & 0 deletions tests/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from myskoda.anonymize import ACCESS_TOKEN, LOCATION, USER_ID, VIN
from myskoda.const import BASE_URL_SKODA
from myskoda.models.air_conditioning import (
AirConditioning,
AirConditioningAtUnlock,
AirConditioningWithoutExternalPower,
HeaterSource,
Expand Down Expand Up @@ -706,3 +707,38 @@ async def test_set_departure_timer(
assert body is not None
# check only the timer as deviceDateTime can't be verified
assert body["timers"][0] == selected_timer.to_dict()


@pytest.mark.asyncio
@pytest.mark.parametrize(("timer_id", "enabled"), [(1, True), (2, False)])
async def test_set_ac_timer(
responses: aioresponses, mqtt_client: MQTTClient, myskoda: MySkoda, timer_id: int, enabled: bool
) -> None:
url = f"{BASE_URL_SKODA}/api/v2/air-conditioning/{VIN}/timers"
responses.post(url=url)

ac_info_json = FIXTURES_DIR.joinpath("other/air-conditioning-idle.json").read_text()
ac_info = AirConditioning.from_json(ac_info_json)

selected_timer = (
next((timer for timer in ac_info.timers if timer.id == timer_id), None)
if ac_info.timers
else None
)
assert selected_timer is not None

selected_timer.enabled = enabled
future = myskoda.set_ac_timer(VIN, selected_timer)

topic = f"{USER_ID}/{VIN}/operation-request/air-conditioning/set-air-conditioning-timers"
await mqtt_client.publish(topic, create_completed_json("set-air-conditioning-timers"), QOS_2)

json_data = {"timers": [selected_timer.to_dict()]}

await future
responses.assert_called_with(
url=url,
method="POST",
headers={"authorization": f"Bearer {ACCESS_TOKEN}"},
json=json_data,
)
2 changes: 1 addition & 1 deletion tests/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ async def test_get_spin_status(

@pytest.fixture(name="departure_timers")
def load_departure_timers() -> list[str]:
"""Load charging fixture."""
"""Load departure timers fixture."""
departure_timers = []
for path in [
"other/departure-timers.json",
Expand Down

0 comments on commit 5759e93

Please sign in to comment.