Skip to content

Commit 27e74d2

Browse files
authored
Add generic miot support (#1581)
This adds a new integration, `genericmiot`, that uses the miotspec files to provide support for all miot devices that can be controlled over local network.
1 parent 0d0e891 commit 27e74d2

File tree

11 files changed

+708
-104
lines changed

11 files changed

+708
-104
lines changed

README.rst

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ This library (and its accompanying cli tool) can be used to interface with devic
99
Getting started
1010
---------------
1111

12-
If you already have a token for your device and the device type, you can directly start using `miiocli` tool.
13-
If you don't have a token for your device, refer to `Getting started <https://python-miio.readthedocs.io/en/latest/discovery.html>`__ section of `the manual <https://python-miio.readthedocs.io>`__ for instructions how to obtain it.
12+
The ``miiocli`` command allows controlling supported devices from the command line,
13+
given that you know their IP addresses and tokens.
14+
You can use ``miiocli cloud`` command to obtain this information from the cloud.
15+
Refer to `Getting started <https://python-miio.readthedocs.io/en/latest/discovery.html>`__ section of `the manual <https://python-miio.readthedocs.io>`__ for more detailed instructions.
1416

15-
The `miiocli` is the main way to execute commands from command line.
16-
You can always use `--help` to get more information about the available commands.
17-
For example, executing it without any extra arguments will print out options and available commands::
17+
You can always use ``--help`` to get more information about available commands, subcommands, and their options.
18+
For example, to print out options and available commands::
1819

1920
$ miiocli --help
2021
Usage: miiocli [OPTIONS] COMMAND [ARGS]...
@@ -28,7 +29,7 @@ For example, executing it without any extra arguments will print out options and
2829
airconditioningcompanion
2930
..
3031

31-
You can get some information from any miIO/MIoT device, including its device model, using the `info` command::
32+
You can get some information from any miIO/MIoT device, including its device model, using the ``info`` command::
3233

3334
miiocli device --ip <ip> --token <token> info
3435

@@ -38,8 +39,28 @@ You can get some information from any miIO/MIoT device, including its device mod
3839
Network: {'localIp': '<ip>', 'mask': '255.255.255.0', 'gw': '<ip>'}
3940
AP: {'rssi': -73, 'ssid': '<nnetwork>', 'primary': 11, 'bssid': '<bssid>'}
4041

41-
Different devices are supported by their corresponding modules (e.g., `roborockvacuum` or `fan`).
42-
You can get the list of available commands for any given module by passing `--help` argument to it::
42+
43+
Controlling MIoT devices
44+
^^^^^^^^^^^^^^^^^^^^^^^^
45+
46+
MiOT devices are supported by the ``genericmiot`` integration which provides basic support for all MiOT devices.
47+
Internally, it downloads ``miot-spec`` files to find out about supported features.
48+
All features of supported devices are available using these common commands::
49+
50+
- ``miiocli genericmiot status`` to print the device status information, including settings (prefixed with ``[S]``).
51+
- ``miiocli genericmiot set`` to change settings.
52+
- ``miiocli genericmiot actions`` to list available actions.
53+
- ``miiocli genericmiot call`` to execute actions.
54+
55+
Use ``miiocli genericmiot --help`` for more available commands.
56+
57+
58+
Controlling other devices
59+
^^^^^^^^^^^^^^^^^^^^^^^^^
60+
61+
Older devices are mainly supported by their corresponding modules (e.g., ``roborockvacuum`` or ``fan``).
62+
63+
You can get the list of available commands for any given module by passing ``--help`` argument to it::
4364

4465
$ miiocli roborockvacuum --help
4566

@@ -63,25 +84,30 @@ You can avoid this by specifying the model manually::
6384

6485
API usage
6586
---------
66-
All functionality is accessible through the `miio` module::
87+
All functionalities of this library are accessible through the ``miio`` module.
88+
While you can initialize individual integration classes manually,
89+
the simplest way to obtain a device instance is to use ``DeviceFactory``::
6790

68-
from miio import RoborockVacuum
91+
from miio import DeviceFactory
6992

70-
vac = RoborockVacuum("<ip address>", "<token>")
71-
vac.start()
93+
dev = DeviceFactory.create("<ip address>", "<token>")
94+
dev.info()
7295

73-
Each separate device type inherits from `miio.Device`
74-
(and in case of MIoT devices, `miio.MiotDevice`) which provides a common API.
7596

76-
Each command invocation will automatically detect (and cache) the device model necessary for some actions
77-
by querying the device.
78-
You can avoid this by specifying the model manually::
97+
This will perform an ``info`` query to the device to detect its model information,
98+
which is crucial especially for MiOT devices.
7999

80-
from miio import RoborockVacuum
100+
Introspecting supported features
101+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
81102

82-
vac = RoborockVacuum("<ip address>", "<token>", model="roborock.vacuum.s5")
103+
You can introspect device classes using the following methods::
83104

84-
Please refer to `API documentation <https://python-miio.readthedocs.io/en/latest/api/miio.html>`__ for more information.
105+
- ``actions()`` to return information about available device actions.
106+
- ``settings()`` to obtain information about available settings that can be changed.
107+
- ``sensors()`` to obtain information about sensors.
108+
109+
Each of these return `device descriptor objects <https://python-miio.readthedocs.io/en/latest/api/miio.descriptors.html>`__,
110+
which contain the necessary metadata about the available features to allow constructing generic interfaces.
85111

86112

87113
Troubleshooting
@@ -90,6 +116,7 @@ You can find some solutions for the most common problems can be found in `Troubl
90116

91117
If you have any questions, or simply want to join up for a chat, check `our Matrix room <https://matrix.to/#/#python-miio-chat:matrix.org>`__.
92118

119+
93120
Contributing
94121
------------
95122

@@ -100,11 +127,14 @@ To ease the process of setting up a development environment we have prepared `a
100127
Supported devices
101128
-----------------
102129

130+
While all MIoT devices are supported through the ``genericmiot`` integration,
131+
this library supports also the following devices::
132+
103133
- Xiaomi Mi Robot Vacuum V1, S4, S4 MAX, S5, S5 MAX, S6 Pure, M1S, S7
104134
- Xiaomi Mi Home Air Conditioner Companion
105135
- Xiaomi Mi Smart Air Conditioner A (xiaomi.aircondition.mc1, mc2, mc4, mc5)
106136
- Xiaomi Mi Air Purifier 2, 3H, 3C, 4, Pro, Pro H, 4 Pro (zhimi.airpurifier.m2, mb3, mb4, mb5, v7, vb2, va2), 4 Lite
107-
- Xiaomi Mi Air (Purifier) Dog X3, X5, X7SM (airdog.airpurifier.x3, airdog.airpurifier.x5, airdog.airpurifier.x7sm)
137+
- Xiaomi Mi Air (Purifier) Dog X3, X5, X7SM (airdog.airpurifier.x3, x5, x7sm)
108138
- Xiaomi Mi Air Humidifier
109139
- Smartmi Air Purifier
110140
- Xiaomi Aqara Camera
@@ -139,9 +169,8 @@ Supported devices
139169
- Xiaomi Smart WiFi Speaker
140170
- Xiaomi Mi WiFi Repeater 2
141171
- Xiaomi Mi Smart Rice Cooker
142-
- Xiaomi Smartmi Fresh Air System VA2 (zhimi.airfresh.va2), VA4 (zhimi.airfresh.va4),
143-
A1 (dmaker.airfresh.a1), T2017 (dmaker.airfresh.t2017)
144-
- Yeelight lights (basic support, we recommend using `python-yeelight <https://gitlab.com/stavros/python-yeelight/>`__)
172+
- Xiaomi Smartmi Fresh Air System VA2 (zhimi.airfresh.va2), VA4 (va4), T2017 (t2017), A1 (dmaker.airfresh.a1)
173+
- Yeelight lights (see also `python-yeelight <https://gitlab.com/stavros/python-yeelight/>`__)
145174
- Xiaomi Mi Air Dehumidifier
146175
- Xiaomi Tinymu Smart Toilet Cover
147176
- Xiaomi 16 Relays Module

miio/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
AirPurifierMiot,
4848
)
4949
from miio.integrations.fan import Fan, Fan1C, FanLeshow, FanMiot, FanP5, FanZA5
50+
from miio.integrations.genericmiot import GenericMiot
5051
from miio.integrations.humidifier import (
5152
AirHumidifier,
5253
AirHumidifierJsq,

miio/device.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def _fetch_info(self) -> DeviceInfo:
145145
self._info = devinfo
146146
_LOGGER.debug("Detected model %s", devinfo.model)
147147
cls = self.__class__.__name__
148-
bases = ["Device", "MiotDevice"]
148+
# Ignore bases and generic classes
149+
bases = ["Device", "MiotDevice", "GenericMiot"]
149150
if devinfo.model not in self.supported_models and cls not in bases:
150151
_LOGGER.warning(
151152
"Found an unsupported model '%s' for class '%s'. If this is working for you, please open an issue at https://github.com/rytilahti/python-miio/",

miio/devicefactory.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ def register(cls, integration_cls: Type[Device]):
3333
for model in integration_cls.supported_models: # type: ignore
3434
if model in cls._supported_models:
3535
_LOGGER.debug(
36-
"Got duplicate of %s for %s, previously registered by %s",
36+
"Ignoring duplicate of %s for %s, previously registered by %s",
3737
model,
3838
integration_cls,
3939
cls._supported_models[model],
4040
)
41+
continue
4142

4243
_LOGGER.debug(" * %s => %s", model, integration_cls)
4344
cls._supported_models[model] = integration_cls
@@ -62,7 +63,11 @@ def class_for_model(cls, model: str):
6263
wildcard_models = {
6364
m: impl for m, impl in cls._supported_models.items() if m.endswith("*")
6465
}
65-
for wildcard_model, impl in wildcard_models.items():
66+
# We sort here to return the implementation with most specific prefix
67+
sorted_by_longest_prefix = sorted(
68+
wildcard_models.items(), key=lambda item: len(item[0]), reverse=True
69+
)
70+
for wildcard_model, impl in sorted_by_longest_prefix:
6671
m = wildcard_model.rstrip("*")
6772
if model.startswith(m):
6873
_LOGGER.debug(
@@ -76,13 +81,27 @@ def class_for_model(cls, model: str):
7681
raise DeviceException("No implementation found for model %s" % model)
7782

7883
@classmethod
79-
def create(self, host: str, token: str, model: Optional[str] = None) -> Device:
84+
def create(
85+
self,
86+
host: str,
87+
token: str,
88+
model: Optional[str] = None,
89+
*,
90+
force_generic_miot=False,
91+
) -> Device:
8092
"""Return instance for the given host and token, with optional model override.
8193
8294
The optional model parameter can be used to override the model detection.
8395
"""
96+
dev: Device
97+
if force_generic_miot: # TODO: find a better way to handle this.
98+
from .integrations.genericmiot import GenericMiot
99+
100+
dev = GenericMiot(host, token, model=model)
101+
dev.info()
102+
return dev
84103
if model is None:
85-
dev: Device = Device(host, token)
104+
dev = Device(host, token)
86105
info = dev.info()
87106
model = info.model
88107

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .genericmiot import GenericMiot
2+
3+
__all__ = ["GenericMiot"]

0 commit comments

Comments
 (0)