Skip to content

Commit

Permalink
Implement input parameters for actions (#1663)
Browse files Browse the repository at this point in the history
This makes the action input parameters accessible through action
descriptors.
  • Loading branch information
rytilahti authored Jan 15, 2023
1 parent 674f3f5 commit c4dabeb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 11 deletions.
3 changes: 2 additions & 1 deletion miio/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
If needed, you can override the methods listed to add more descriptors to your integration.
"""
from enum import Enum, auto
from typing import Callable, Dict, Optional, Type
from typing import Any, Callable, Dict, List, Optional, Type

import attr

Expand All @@ -33,6 +33,7 @@ class ActionDescriptor:
name: str
method_name: Optional[str] = attr.ib(default=None, repr=False)
method: Optional[Callable] = attr.ib(default=None, repr=False)
inputs: Optional[List[Any]] = attr.ib(default=None, repr=True)
extras: Dict = attr.ib(factory=dict, repr=False)


Expand Down
59 changes: 58 additions & 1 deletion miio/devtools/simulators/miotsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,65 @@ def dump_properties(self, payload):

def action(self, payload):
"""Handle action method."""
params = payload["params"]
if (
"did" not in params
or "siid" not in params
or "aiid" not in params
or "in" not in params
):
raise ValueError("did, siid, or aiid missing")

siid = params["siid"]
aiid = params["aiid"]
inputs = params["in"]
service = self._model.get_service_by_siid(siid)

action = service.get_action_by_id(aiid)
action_inputs = action.inputs
if len(inputs) != len(action_inputs):
raise ValueError(
"Invalid parameter count, was expecting %s params, got %s"
% (len(inputs), len(action_inputs))
)

for idx, param in enumerate(inputs):
wanted_input = action_inputs[idx]

if wanted_input.choices:
if not isinstance(param, int):
raise TypeError(
"Param #%s: enum value expects an integer %s, got %s"
% (idx, wanted_input, param)
)
for choice in wanted_input.choices:
if param == choice.value:
break
else:
raise ValueError(
"Param #%s: invalid value '%s' for %s"
% (idx, param, wanted_input.choices)
)

elif wanted_input.range:
if not isinstance(param, int):
raise TypeError(
"Param #%s: ranged value expects an integer %s, got %s"
% (idx, wanted_input, param)
)

min, max, step = wanted_input.range
if param < min or param > max:
raise ValueError(
"Param #%s: value '%s' out of range [%s, %s]"
% (idx, param, min, max)
)

elif wanted_input.format == str and not isinstance(param, str):
raise TypeError(f"Param #{idx}: expected string but got {type(param)}")

_LOGGER.info("Got called %s", payload)
return {"result": 0}
return {"result": ["ok"]}


async def main(dev, model):
Expand Down
33 changes: 25 additions & 8 deletions miio/integrations/genericmiot/genericmiot.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,25 @@ def pretty_status(result: "GenericMiotStatus"):
def pretty_actions(result: Dict[str, ActionDescriptor]):
"""Pretty print actions."""
out = ""
service = None
for _, desc in result.items():
out += f"{desc.id}\t\t{desc.name}\n"
miot_prop: MiotProperty = desc.extras["miot_action"]
# service is marked as optional due pydantic backrefs..
serv = cast(MiotService, miot_prop.service)
if service is None or service.siid != serv.siid:
service = serv
out += f"[bold]{service.description} ({service.name})[/bold]\n"

out += f"\t{desc.id}\t\t{desc.name}"
if desc.inputs:
for idx, input_ in enumerate(desc.inputs, start=1):
param = input_.extras[
"miot_property"
] # TODO: hack until descriptors get support for descriptions
param_desc = f"\n\t\tParameter #{idx}: {param.name} ({param.description}) ({param.format}) {param.pretty_input_constraints}"
out += param_desc

out += "\n"

return out

Expand Down Expand Up @@ -213,13 +230,6 @@ def status(self) -> GenericMiotStatus:

def _create_action(self, act: MiotAction) -> Optional[ActionDescriptor]:
"""Create action descriptor for miot action."""
if act.inputs:
# TODO: need to figure out how to expose input parameters for downstreams
_LOGGER.warning(
"Got inputs for action, skipping as handling is unknown: %s", act
)
return None

call_action = partial(self.call_action_by, act.siid, act.aiid)

id_ = act.name
Expand All @@ -229,10 +239,17 @@ def _create_action(self, act: MiotAction) -> Optional[ActionDescriptor]:
extras["urn"] = act.urn
extras["siid"] = act.siid
extras["aiid"] = act.aiid
extras["miot_action"] = act

inputs = act.inputs
if inputs:
# TODO: this is just temporarily here, pending refactoring the descriptor creation into the model
inputs = [self._descriptor_for_property(prop) for prop in act.inputs]

return ActionDescriptor(
id=id_,
name=act.description,
inputs=inputs,
method=call_action,
extras=extras,
)
Expand Down
8 changes: 7 additions & 1 deletion miio/miot_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ class DeviceModel(BaseModel):
urn: URN = Field(alias="type")
services: List[MiotService] = Field(repr=False)

# internal mappings to simplify accesses to a specific (siid, piid)
# internal mappings to simplify accesses
_services_by_id: Dict[int, MiotService] = PrivateAttr(default_factory=dict)
_properties_by_id: Dict[int, Dict[int, MiotProperty]] = PrivateAttr(
default_factory=dict
)
Expand All @@ -302,6 +303,7 @@ def __init__(self, *args, **kwargs):
"""
super().__init__(*args, **kwargs)
for serv in self.services:
self._services_by_id[serv.siid] = serv
self._properties_by_name[serv.name] = dict()
self._properties_by_id[serv.siid] = dict()
for prop in serv.properties:
Expand All @@ -313,6 +315,10 @@ def device_type(self) -> str:
"""Return device type as string."""
return self.urn.type

def get_service_by_siid(self, siid: int) -> MiotService:
"""Return the service for given siid."""
return self._services_by_id[siid]

def get_property(self, service: str, prop_name: str) -> MiotProperty:
"""Return the property model for given service and property name."""
return self._properties_by_name[service][prop_name]
Expand Down

0 comments on commit c4dabeb

Please sign in to comment.