Skip to content

Commit

Permalink
feat: implement command parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
alryaz committed Mar 12, 2024
1 parent 9197b5e commit 1269deb
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/pandora_cas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "0.0.1"
__version__ = "0.0.2"
__author__ = "Alexander Ryazanov <[email protected]>"
__all__ = ("account", "data", "device", "enums", "errors")
13 changes: 11 additions & 2 deletions src/pandora_cas/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,19 +405,28 @@ async def async_refresh_devices(self) -> None:
device_object.attributes = device_attributes

async def async_remote_command(
self, device_id: int, command_id: int | CommandID
self,
device_id: int,
command_id: int | CommandID,
params: Mapping[str, Any] = None,
) -> None:
"""
Execute remote command on target device.
:param device_id: Device ID to execute command on.
:param command_id: Identifier of the command to execute.
:param params: additional parameters to send with the command.
:raises PandoraOnlineException: Failed command execution with response.
"""
self.logger.info(f"Sending command {command_id} to device {device_id}")

data = {"id": device_id, "command": int(command_id)}

if params:
data["comm_params"] = dict(params)

async with self._session.post(
self.BASE_URL + "/api/devices/command",
data={"id": device_id, "command": int(command_id)},
data=data,
params={"access_token": self.access_token},
) as response:
data = await self._handle_dict_response(response)
Expand Down
65 changes: 33 additions & 32 deletions src/pandora_cas/data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__all__ = (
"BalanceState",
"Balance",
"FuelTank",
"CurrentState",
"TrackingEvent",
Expand All @@ -17,10 +17,11 @@
SupportsFloat,
SupportsInt,
MutableMapping,
Collection,
Final,
Callable,
Type,
Sequence,
SupportsRound,
)

import attr
Expand Down Expand Up @@ -64,6 +65,15 @@ def value_or_none(x: _T) -> _T | None:
return x or None


def field_list(
field_name: _TFieldName, converter: Callable[[Any], Any] | None = None, **kwargs
):
kwargs.setdefault("default", ())
if converter is not None:
kwargs["converter"] = lambda x: tuple(map(converter, x))
return field(field_name, **kwargs)


def field_emp(
field_name: _TFieldName, converter: Callable[[Any], Any] | None = None, **kwargs
):
Expand Down Expand Up @@ -150,42 +160,32 @@ def from_dict(cls, data: Mapping[str, Any], **kwargs):
def conv(cls, x: Any):
return x if isinstance(x, cls) else cls.from_dict(x)

@classmethod
def conv_list(cls, x: Any):
return tuple(map(cls.conv, x))


@attr.s(frozen=True, slots=True)
class BalanceState(_BaseGetDictArgs):
value: float = field_float("value", default=0.0)
currency: str | None = field_emp("cur")
@attr.s(kw_only=True, frozen=True, slots=True)
class _FloatValue(_BaseGetDictArgs, SupportsInt, SupportsFloat, SupportsRound):
value: float | None = field_float("value")

def __float__(self) -> float:
return self.value

def __int__(self) -> int:
return int(self.value)

def __round__(self, n=None):
return round(self.value, n)
def __round__(self, __ndigits: int | None = None):
return round(self.value, __ndigits)


@attr.s(frozen=True, slots=True)
class Balance(_FloatValue):
currency: str | None = field_emp("cur")


@attr.s(kw_only=True, frozen=True, slots=True)
class FuelTank:
id: int = attr.ib(metadata={_S: "id"})
value: float = attr.ib(metadata={_S: "value"})
class FuelTank(_FloatValue):
id: int = field("id", int, default=0)
ras: float | None = field_float("")
ras_t: float | None = field_float("")

def __float__(self) -> float:
return self.value

def __int__(self) -> int:
return int(self.value)

def __round__(self, n=None):
return round(self.value, n)


def _degrees_to_direction(degrees: float):
sides = (
Expand Down Expand Up @@ -217,7 +217,7 @@ def from_dict_wrap(cls: Type[_BaseGetDictArgs]):
class SimCard(_BaseGetDictArgs):
phone: str = field("phoneNumber")
is_active: bool = field("isActive", bool)
balance: BalanceState | None = field_emp("balance", from_dict_wrap(BalanceState))
balance: Balance | None = field_emp("balance", from_dict_wrap(Balance))


def lock_lat_lng_conv(x: Any):
Expand All @@ -229,8 +229,8 @@ class CurrentState(_BaseGetDictArgs):
identifier: int = field(("dev_id", "id"), int)

active_sim: int | None = field_int("active_sim")
balance: BalanceState | None = field_emp("balance", BalanceState.conv)
balance_other: BalanceState | None = field_emp("balance1", BalanceState.conv)
balance: Balance | None = field_emp("balance", Balance.conv)
balance_other: Balance | None = field_emp("balance1", Balance.conv)
bit_state: BitStatus | None = field_opt("bit_state_1", lambda x: BitStatus(int(x)))
can_mileage: float | None = field_float("mileage_CAN")
engine_rpm: int | None = field_int("engine_rpm")
Expand Down Expand Up @@ -282,11 +282,12 @@ class CurrentState(_BaseGetDictArgs):
can_climate_defroster: bool | None = field_bool("CAN_climate_defroster")
can_climate_evb_heat: bool | None = field_bool("CAN_climate_evb_heat")
can_climate_glass_heat: bool | None = field_bool("CAN_climate_glass_heat")
can_climate_seat_heat_lvl: int | None = field_int("CAN_climate_seat_heat_lvl")
can_climate_seat_vent_lvl: int | None = field_int("CAN_climate_seat_vent_lvl")
can_climate_seat_heat_level: int | None = field_int("CAN_climate_seat_heat_lvl")
can_climate_seat_vent_level: int | None = field_int("CAN_climate_seat_vent_lvl")
can_climate_steering_heat: bool | None = field_bool("CAN_climate_steering_heat")
can_climate_temp: int | None = field_int("CAN_climate_temp")
can_climate_temperature: int | None = field_int("CAN_climate_temp")

heater_errors: Sequence[int] = field_list("heater_errors", int)
heater_flame: bool | None = field_bool("heater_flame")
heater_power: bool | None = field_bool("heater_power")
heater_temperature: float | None = field_float("heater_temperature")
Expand Down Expand Up @@ -318,8 +319,8 @@ class CurrentState(_BaseGetDictArgs):
# land: int | None = field_int("land")
bunker: int | None = field_int("bunker")
ex_status: int | None = field_int("ex_status")
fuel_tanks: Collection[FuelTank] = attr.ib(default=())
sims: Collection[SimCard] = field_emp("sims", SimCard.conv_list, default=())
fuel_tanks: Sequence[FuelTank] = attr.ib(default=())
sims: Sequence[SimCard] = field_list("sims", SimCard.conv)

state_timestamp: int | None = field_int("state")
state_timestamp_utc: int | None = field_int("state_utc")
Expand Down
10 changes: 8 additions & 2 deletions src/pandora_cas/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ async def async_fetch_events(

# Remote command execution section
async def async_remote_command(
self, command_id: int | CommandID, ensure_complete: bool = True
self,
command_id: int | CommandID,
ensure_complete: bool = True,
params: Mapping[str, Any] | None = None,
):
"""Proxy method to execute commands on corresponding vehicle object"""
if self._current_state is None:
Expand All @@ -172,7 +175,7 @@ async def async_remote_command(
if ensure_complete:
self._control_future = asyncio.Future()

await self._account.async_remote_command(self.device_id, command_id)
await self._account.async_remote_command(self.device_id, command_id, params)

if ensure_complete:
self.logger.debug(
Expand Down Expand Up @@ -266,6 +269,9 @@ async def async_remote_trigger_light(self, ensure_complete: bool = True):
async def async_remote_trigger_trunk(self, ensure_complete: bool = True):
return await self.async_remote_command(CommandID.TRIGGER_TRUNK, ensure_complete)

# Climate commands


@property
def control_busy(self) -> bool:
"""Returns whether device is currently busy executing command."""
Expand Down
26 changes: 26 additions & 0 deletions src/pandora_cas/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"PandoraDeviceTypes",
"WSMessageType",
"CommandID",
"CommandParams",
"EventType",
"AlertType",
"BitStatus",
Expand Down Expand Up @@ -89,6 +90,27 @@ class CommandID(IntEnum):
NAV12_ENABLE_STATUS_OUTPUT = 57372
NAV12_DISABLE_STATUS_OUTPUT = 57371

# Climate-related commands
CLIMATE_SET_TEMPERATURE = 58624 # Установить температуру
CLIMATE_SEAT_HEAT_TURN_ON = 58625 # Вкл. подогрев сидений
CLIMATE_SEAT_HEAT_TURN_OFF = 58626 # Выкл. подогрев сидений
CLIMATE_SEAT_VENT_TURN_ON = 58627 # Вкл. вентиляцию сидений
CLIMATE_SEAT_VENT_TURN_OFF = 58628 # Выкл. подогрев сидений
CLIMATE_WINDOW_HEAT_TURN_ON = 58629 # Вкл. подогрев окон и зеркал
CLIMATE_WINDOW_HEAT_TURN_OFF = 58630 # Выкл. подогрев окон и зеркал
CLIMATE_STEERING_HEAT_TURN_ON = 58631 # Вкл. подогрев руля
CLIMATE_STEERING_HEAT_TURN_OFF = 58632 # Выкл. подогрев руля
CLIMATE_AC_TURN_ON = 58633 # Вкл. кондиционер
CLIMATE_AC_TURN_OFF = 58634 # Выкл. кондиционер
CLIMATE_SYS_TURN_ON = 58635 # Вкл. климатическую систему
CLIMATE_SYS_TURN_OFF = 58636 # Выкл. климатическую систему
CLIMATE_DEFROSTER_TURN_ON = 58637 # Вкл. Defroster
CLIMATE_DEFROSTER_TURN_OFF = 58638 # Выкл. Defroster
CLIMATE_MODE_COMFORT = 58639 # Режим комфорт
CLIMATE_MODE_VENT = 58640 # Режим проветривания салона
CLIMATE_BATTERY_HEAT_TURN_ON = 58647 # Вкл. подогрев батареи
CLIMATE_BATTERY_HEAT_TURN_OFF = 58648 # Выкл. подогрев батареи

# Unknown (untested and incorrectly named) commands
STAY_HOME_PROPION = 42
LOW_POWER_MODE = 50
Expand Down Expand Up @@ -280,3 +302,7 @@ class PrimaryEventID(IntEnum):
@classmethod
def _missing_(cls, value: object) -> Any:
return cls.UNKNOWN


class CommandParams(StrEnum):
CLIMATE_TEMP = "climate_temp"

0 comments on commit 1269deb

Please sign in to comment.