Device Simulators

This section describes how to use and develop device simulators that can be useful when developing either this library and its CLI tool, as well as when developing applications communicating with MiIO/MiOT devices, like the Home Assistant integration, even when you have no access to real devices.

MiIO Simulator

The miiocli devtools miio-simulator command can be used to simulate devices. You can command the simulated devices using the miiocli tool or any other implementation that talks the MiIO proocol, like Home Assistant.

Behind the scenes, the simulator uses the push server to handle the low-level protocol handling. To make it easy to simulate devices, it uses YAML-based device description files to describe information like models and exposed properties to simulate.

Note

The simulator currently supports only devices whose properties are queried using get_prop method, and whose properties are set using a single setter method (e.g., set_fan_speed) accepting the new value.

Usage

You start the simulator like this:

miiocli devtools miio-simulator --file miio/integrations/zhimi/fan/zhimi_fan.yaml

The mandatory --file option takes a path to a device description file file that defines information about the device to be simulated.

Note

You can define --model to define which model string you want to expose to the clients. The MAC address of the device is generated from the model string, making them unique for downstream use cases, e.g., to make them distinguishable to Home Assistant.

After the simulator has started, you can communicate with it using the miiocli:

$ export MIIO_FAN_TOKEN=00000000000000000000000000000000

$ miiocli fan --host 127.0.0.1 info

Model: zhimi.fan.sa1
Hardware version: MW300
Firmware version: 1.2.4_16

$ miiocli fan --ip 127.0.0.1 status

Power: on
Battery: None %
AC power: True
Temperature: None °C
Humidity: None %
LED: None
LED brightness: LedBrightness.Bright
Buzzer: False
Child lock: False
Speed: 277
Natural speed: 2
Direct speed: 1
Oscillate: False
Power-off time: 12
Angle: 120

Note

The default token is hardcoded to full of zeros (00000000000000000000000000000000). We defined MIIO_FAN_TOKEN to avoid repeating --token for each command.

Note

Although Home Assistant uses MAC address as a unique ID to identify the device, the model information is stored in the configuration entry which is used to initialize the integration.

Therefore, if you are testing multiple simulated devices in Home Assistant, you want to disable other simulated integrations inside Home Assistant to avoid them being updated against a wrong simulated device.

Device Descriptions

The simulator uses YAML files that describe information about the device, including supported models and the available properties.

Required Information

The file begins with a definition of models supported by the file:

models:
  - name: Name of the device, if known
    model: model.string.v2
  - model: model.string.v3

You need to have model for each model the description file wants to support. 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 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

  • type defines the type of the property, e.g., bool, int, or str

  • value is the value which is returned for get_prop requests

  • setter defines the method that allows changing the value

  • models list, if the property is only available on some of the supported models

Note

The schema might change in the future to accommodate other potential uses, e.g., allowing definition of new files using pure YAML without a need for Python implementation. Refer Example Description File 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:

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 Example Description File Using Methods.

Minimal Working Example

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

models:
  - name: Some Fan
    model: some.fan.model
properties:
  - name: speed
    type: int
    value: 33
    setter: set_speed
  - 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. See Example Description File for a more complete example.

Example Description File

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

models:
  - model: zhimi.fan.sa1
  - model: zhimi.fan.za1
  - model: zhimi.fan.za3
  - model: zhimi.fan.za4
  - model: zhimi.fan.v3
  - model: zhimi.fan.v2
type: fan
properties:
  - name: angle
    value: 120
    type: int
    min: 0
    max: 120
    setter: set_angle
  - name: speed
    value: 277
    type: int
    setter: set_speed_level
    min: 0
    max: 100
  - name: poweroff_time
    value: 12
    type: int
    setter: set_poweroff_time
  - name: power
    value: 'on'
    type: str_bool
    setter: set_power
  - name: ac_power
    value: 'on'
    type: str_bool
  - name: angle_enable
    value: 'off'
    setter: set_angle_enable
    type: str_bool
  - name: speed_level
    value: 1
    type: int
    min: 0
    max: 100
    setter: set_speed_level
  - name: natural_level
    value: 2
    type: int
    setter: set_natural_level
  - name: child_lock
    value: 'off'
    type: str_bool
    setter: set_child_lock
  - name: buzzer
    value: 0
    type: int
    setter: set_buzzer
  - name: led_b
    value: 0
    type: int
    setter: set_led_b
  - name: use_time
    value: 2318
    type: int
  # V2 & V3 only
  - name: temp_dec
    value: 232
    type: float
    models:
      - zhimi.fan.v3
      - zhimi.fan.v2
  - name: humidity
    value: 46
    type: int
    models:
      - zhimi.fan.v3
      - zhimi.fan.v2
  - name: battery
    type: int
    value: 98
    models:
      - zhimi.fan.v3
      - zhimi.fan.v2
  - name: bat_charge
    value: "complete"
    type: str
    models:
      - zhimi.fan.v3
      - zhimi.fan.v2
  - name: button_pressed
    type: str
    value: speed
    models:
      - zhimi.fan.v3
      - zhimi.fan.v2
  # V2 only properties
  - name: led
    type: str
    value: null
    models:
      - zhimi.fan.v2
  - name: bat_state
    type: str
    value: "unknown state"
    models:
      - zhimi.fan.v2

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.

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

MiOT Simulator

The miiocli devtools miot-simulator command can be used to simulate MiOT devices for a given description file. You can command the simulated devices using the miiocli tool or any other implementation that supports the device.

Behind the scenes, the simulator uses the push server to handle the low-level protocol handling.

The simulator implements the following methods:

  • miIO.info returns the device information

  • get_properties returns randomized (leveraging the schema limits) values for the given siid and piid

  • set_properties allows setting the property for the given siid and piid combination

  • action to call actions that simply respond that the action succeeded

Furthermore, two custom methods are implemented help with development:

Usage

You start the simulator like this:

miiocli devtools miot-simulator --file some.vacuum.model.json --model some.vacuum.model

The mandatory --file option takes a path to a MiOT description file, while --model defines the model the simulator should report in its miIO.info response.

Note

The default token is hardcoded to full of zeros (00000000000000000000000000000000).

Dump Service Information

dump_services method that returns a JSON dictionary keyed with the siid containing the simulated services:

$ miiocli device --ip 127.0.0.1 --token 00000000000000000000000000000000 raw_command dump_services
Running command raw_command
{'services': {'1': {'siid': 1, 'description': 'Device Information'}, '2': {'siid': 2, 'description': 'Heater'}, '3': {'siid': 3, 'description': 'Countdown'}, '4': {'siid': 4, 'description': 'Environment'}, '5': {'siid': 5, 'description': 'Physical Control Locked'}, '6': {'siid': 6, 'description': 'Alarm'}, '7': {'siid': 7, 'description': 'Indicator Light'}, '8': {'siid': 8, 'description': '私有服务'}}, 'id': 2}

Dump Service Properties

dump_properties method can be used to return the current state of the device on service-basis:

$ miiocli device --ip 127.0.0.1 --token 00000000000000000000000000000000 raw_command dump_properties '{"siid": 2}'
Running command raw_command
[{'siid': 2, 'piid': 1, 'prop': 'Switch Status', 'value': False}, {'siid': 2, 'piid': 2, 'prop': 'Device Fault', 'value': 167}, {'siid': 2, 'piid': 5, 'prop': 'Target Temperature', 'value': 28}]