Skip to content

Commit

Permalink
Allow custom methods for miio simulator (#1538)
Browse files Browse the repository at this point in the history
  • Loading branch information
rytilahti authored Sep 26, 2022
1 parent 6d841e0 commit f4abd59
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 7 deletions.
41 changes: 38 additions & 3 deletions docs/simulator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The name is not required, but recommended.
This information is currently used to set the model information for the simulated
device when not overridden using the ``--model`` option.

The description file needs to define a list of properties the device supports.
The description file can define a list of *properties* the device supports for ``get_prop`` queries.
You need to define several mappings for each property:

* ``name`` defines the name used for fetching using the ``get_prop`` request
Expand All @@ -126,10 +126,30 @@ You need to define several mappings for each property:
definition of new files using pure YAML without a need for Python implementation.
Refer :ref:`example_desc` for a complete, working example.

Alternatively, you can define *methods* with their responses by defining ``methods``, which is necessary to simulate
devices that use other ways to obtain the status information (e.g., on Roborock vacuums).
You can either use ``result`` or ``result_json`` to define the response for the given method:

.. code-block:: yaml
methods:
- name: get_status
result:
- some_variable: 1
another_variable: "foo"
- name: get_timezone
result_json: '["UTC"]'
Calling method ``get_status`` will return ``[{"some_variable": 1, "another_variable": "foo"}]``,
the ``result_json`` will be parsed and serialized to ``["UTC"]`` when sent to the client.
A full working example can be found in :ref:`example_desc_methods`.


Minimal Working Example
~~~~~~~~~~~~~~~~~~~~~~~

The following defining a very minimal device description having a single model with two properties:
The following YAML file defines a very minimal device having a single model with two properties,
and exposing also a custom method (``reboot``):

.. code-block:: yaml
Expand All @@ -144,6 +164,9 @@ The following defining a very minimal device description having a single model w
- name: is_on
type: bool
value: false
methods:
- name: reboot
result_json: '["ok"]'
In this case, the ``get_prop`` method call with parameters ``['speed', 'is_on']`` will return ``[33, 0]``.
The ``speed`` property can be changed by calling the ``set_speed`` method.
Expand All @@ -154,11 +177,23 @@ See :ref:`example_desc` for a more complete example.
Example Description File
~~~~~~~~~~~~~~~~~~~~~~~~

The following description file shows a complete, concrete example of a description file (``zhimi_fan.yaml``):
The following description file shows a complete,
concrete example for a device using ``get_prop`` for accessing the properties (``zhimi_fan.yaml``):

.. literalinclude:: ../miio/integrations/fan/zhimi/zhimi_fan.yaml
:language: yaml

.. _example_desc_methods:

Example Description File Using Methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following description file (``simulated_roborock.yaml``) shows a complete,
concrete example for a device using custom method names for obtaining the status.

.. literalinclude:: ../miio/integrations/vacuum/roborock/simulated_roborock.yaml
:language: yaml


MiOT Simulator
--------------
Expand Down
29 changes: 25 additions & 4 deletions miio/devtools/simulators/miiosimulator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Implementation of miio simulator."""
import asyncio
import json
import logging
from typing import List, Optional, Union

Expand Down Expand Up @@ -51,6 +52,14 @@ class MiioAction(BaseModel):
"""Simulated miio action."""


class MiioMethod(BaseModel):
"""Simulated method."""

name: str
result: Optional[List] = None
result_json: Optional[str] = None


class MiioModel(BaseModel):
"""Model information."""

Expand All @@ -61,11 +70,12 @@ class MiioModel(BaseModel):
class SimulatedMiio(BaseModel):
"""Simulated device model for miio devices."""

name: Optional[str] = Field(default="Unnamed integration")
models: List[MiioModel]
type: str
properties: List[MiioProperty]
name: Optional[str] = Field(default="Unnamed integration")
actions: Optional[List[MiioAction]] = Field(default=[])
properties: List[MiioProperty] = Field(default=[])
actions: List[MiioAction] = Field(default=[])
methods: List[MiioMethod] = Field(default=[])
_model: Optional[str] = PrivateAttr(default=None)

class Config:
Expand All @@ -84,7 +94,9 @@ def __init__(self, dev: SimulatedMiio, server: PushServer):
if self._dev._model is None:
self._dev._model = next(iter(self._dev.models)).model

server.add_method("get_prop", self.get_prop)
# Add get_prop if device has properties defined
if self._dev.properties:
server.add_method("get_prop", self.get_prop)
# initialize setters
for prop in self._dev.properties:
if prop.models and self._dev._model not in prop.models:
Expand All @@ -93,6 +105,15 @@ def __init__(self, dev: SimulatedMiio, server: PushServer):
self._setters[prop.setter] = prop
server.add_method(prop.setter, self.handle_set)

# Add static methods
for method in self._dev.methods:
if method.result_json:
server.add_method(
method.name, {"result": json.loads(method.result_json)}
)
else:
server.add_method(method.name, {"result": method.result})

def get_prop(self, payload):
"""Handle get_prop."""
params = payload["params"]
Expand Down
65 changes: 65 additions & 0 deletions miio/integrations/vacuum/roborock/simulated_roborock.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
models:
- model: roborock.vacuum.a15
type: vacuum
methods:
- name: get_status
result:
- _model: roborock.vacuum.a15 # internal note where this status came from
adbumper_status:
- 0
- 0
- 0
auto_dust_collection: 1
battery: 87
clean_area: 35545000
clean_time: 2311
debug_mode: 0
dnd_enabled: 0
dock_type: 0
dust_collection_status: 0
error_code: 0
fan_power: 102
in_cleaning: 0
in_fresh_state: 1
in_returning: 0
is_locating: 0
lab_status: 3
lock_status: 0
map_present: 1
map_status: 3
mop_forbidden_enable: 0
mop_mode: 300
msg_seq: 1839
msg_ver: 2
state: 8
water_box_carriage_status: 0
water_box_mode: 202
water_box_status: 1
water_shortage_status: 0
- name: get_consumable
result:
- filter_work_time: 32454
sensor_dirty_time: 3798
side_brush_work_time: 32454
main_brush_work_time: 32454
- name: get_clean_summary
result_json: '[ 174145, 2410150000, 82, [ 1488240000, 1488153600, 1488067200, 1487980800, 1487894400, 1487808000, 1487548800 ] ]'
- name: get_timer
result_json: '[["1488667794112", "off", ["49 22 * * 6", ["start_clean", ""]], ["1488667777661", "off", ["49 21 * * 3,4,5,6", ["start_clean", ""]]]]]'
- name: get_timezone
result_json: '["UTC"]'
- name: get_dnd_timer
result:
- enabled: 1
start_minute: 0
end_minute: 0
start_hour: 22
end_hour: 8
- name: get_clean_record
result:
- begin: 1488347071
end: 1488347123
duration: 16
area: 0
error: 0
complete: 0

0 comments on commit f4abd59

Please sign in to comment.