Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/saic_ismart_client_ng/api/vehicle/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ async def control_heated_seats(
) -> VehicleControlResp:
rcv_params = [
RvcParams(
RvcParamsId.HEATED_SEAT_DRIVER, left_side_level.to_bytes(1, "big")
RvcParamsId.HEATED_SEAT_FRONT_LEFT, left_side_level.to_bytes(1, "big")
),
RvcParams(
RvcParamsId.HEATED_SEAT_PASSENGER, right_side_level.to_bytes(1, "big")
RvcParamsId.HEATED_SEAT_FRONT_RIGHT, right_side_level.to_bytes(1, "big")
),
RvcParams(RvcParamsId.PARAMS_MAX, b"\x00\x00\x00\x00"),
]
Expand Down
11 changes: 8 additions & 3 deletions src/saic_ismart_client_ng/api/vehicle/climate/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@


class HeatedSeat(Enum):
"""A single heated seat, by the parameter the iSmart app uses for it."""
"""A single heated seat, by the parameter the iSmart app uses for it.

FRONT_LEFT = RvcParamsId.HEATED_SEAT_DRIVER
FRONT_RIGHT = RvcParamsId.HEATED_SEAT_PASSENGER
Left and right are the physical sides of the car, the same on left- and
right-hand drive cars. They match ``frontLeftSeatHeatLevel`` /
``frontRightSeatHeatLevel`` in the vehicle status.
"""

FRONT_LEFT = RvcParamsId.HEATED_SEAT_FRONT_LEFT
FRONT_RIGHT = RvcParamsId.HEATED_SEAT_FRONT_RIGHT
REAR_LEFT = RvcParamsId.HEATED_SEAT_REAR_LEFT
REAR_RIGHT = RvcParamsId.HEATED_SEAT_REAR_RIGHT

Expand Down
11 changes: 9 additions & 2 deletions src/saic_ismart_client_ng/api/vehicle/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,15 @@ class RvcParamsId(Enum):
WINDOW_3 = 11
WINDOW_4 = 12
WINDOW_OPEN_CLOSE = 13
HEATED_SEAT_DRIVER = 17
HEATED_SEAT_PASSENGER = 18
# Heated seats are addressed by PHYSICAL side, not driver/passenger:
# 17 is the front-left seat and 18 the front-right on both left- and
# right-hand drive cars (confirmed on a right-hand drive MG S6 EV, where
# the app's front-left seat sends 17). The DRIVER/PASSENGER names are the
# original ones, kept as aliases; they're only right for left-hand drive.
HEATED_SEAT_FRONT_LEFT = 17
HEATED_SEAT_FRONT_RIGHT = 18
HEATED_SEAT_DRIVER = 17 # noqa: PIE796 - alias of HEATED_SEAT_FRONT_LEFT
HEATED_SEAT_PASSENGER = 18 # noqa: PIE796 - alias of HEATED_SEAT_FRONT_RIGHT
FAN_SPEED = 19
TEMPERATURE = 20
AC_ON_OFF = 22
Expand Down
53 changes: 53 additions & 0 deletions tests/test_heated_seat_sides.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Front heated seats are named by physical side, not driver/passenger.

Parameters 17 and 18 are the front-left and front-right seats on both left-
and right-hand drive cars: on a right-hand drive MG S6 EV the iSmart app's
front-left (passenger) seat sends 17. HEATED_SEAT_FRONT_LEFT/RIGHT are the
new names; HEATED_SEAT_DRIVER/PASSENGER are kept as aliases, and the bytes
sent are unchanged.
"""

from __future__ import annotations

import base64
from typing import Any
import unittest

from saic_ismart_client_ng import SaicApi
from saic_ismart_client_ng.api.vehicle import RvcParamsId
from saic_ismart_client_ng.api.vehicle.climate import HeatedSeat
from saic_ismart_client_ng.model import SaicApiConfiguration


class TestHeatedSeatSides(unittest.IsolatedAsyncioTestCase):
def test_front_seats_are_physical_sides(self) -> None:
assert RvcParamsId.HEATED_SEAT_FRONT_LEFT.value == 17
assert RvcParamsId.HEATED_SEAT_FRONT_RIGHT.value == 18
assert HeatedSeat.FRONT_LEFT.value is RvcParamsId.HEATED_SEAT_FRONT_LEFT
assert HeatedSeat.FRONT_RIGHT.value is RvcParamsId.HEATED_SEAT_FRONT_RIGHT

def test_old_names_still_work(self) -> None:
# Looked up by name: mypy doesn't know enum aliases share a member.
assert RvcParamsId["HEATED_SEAT_DRIVER"] is RvcParamsId.HEATED_SEAT_FRONT_LEFT
assert (
RvcParamsId["HEATED_SEAT_PASSENGER"] is RvcParamsId.HEATED_SEAT_FRONT_RIGHT
)

async def test_both_front_seats_command_unchanged(self) -> None:
api = SaicApi(SaicApiConfiguration(username="user", password="pass")) # noqa: S106
captured: dict[str, Any] = {}

async def capture(body: Any, vin: str) -> None:
captured["body"] = body
captured["vin"] = vin

api.send_vehicle_control_command = capture # type: ignore[method-assign,assignment]
await api.control_heated_seats(
"OFFLINE0000000000", left_side_level=1, right_side_level=3
)
await api.close()
params = [
(p.paramId, base64.b64decode(p.paramValue))
for p in captured["body"].rvcParams
]
assert params == [(17, b"\x01"), (18, b"\x03"), (255, b"\x00\x00\x00\x00")]
Loading