python-library-xiaomi-miot 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_library_xiaomi_miot-0.1.0.dist-info/METADATA +6 -0
- python_library_xiaomi_miot-0.1.0.dist-info/RECORD +10 -0
- python_library_xiaomi_miot-0.1.0.dist-info/WHEEL +4 -0
- xiaomi_miot/__init__.py +17 -0
- xiaomi_miot/_api.py +54 -0
- xiaomi_miot/device.py +48 -0
- xiaomi_miot/extensions/__init__.py +7 -0
- xiaomi_miot/extensions/_registry.py +44 -0
- xiaomi_miot/extensions/switch.py +31 -0
- xiaomi_miot/models.py +22 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
xiaomi_miot/__init__.py,sha256=xMnX3Icdofj0fRNQC5_NZGyLq9G-6dqAX8l019TJ0dQ,432
|
|
2
|
+
xiaomi_miot/_api.py,sha256=4dN17bEFnbkMFQkyyXK7FSyAngG0jcVnKNArIeJYZdQ,1838
|
|
3
|
+
xiaomi_miot/device.py,sha256=YA8UVq0uIEoBLmvzMhUyIFKC_fQewpMHbTkl-otzjPI,1693
|
|
4
|
+
xiaomi_miot/models.py,sha256=7X5L1ClsPYxJdDQrAEbqRl-PK49CLwGPB5ptbSaPZnE,739
|
|
5
|
+
xiaomi_miot/extensions/__init__.py,sha256=CgSFv8opc31WFVXFga785p5lV3e2oPSNhucRgIvvZmE,146
|
|
6
|
+
xiaomi_miot/extensions/_registry.py,sha256=vdbS7XAEqc-HyuGs22iBDiA4Gt2rW87QpyfDzKC7x-s,1064
|
|
7
|
+
xiaomi_miot/extensions/switch.py,sha256=iV3xfu1mmTq9KxoWfMWnkTlKZOogIuCvS137ZP_D6uk,1300
|
|
8
|
+
python_library_xiaomi_miot-0.1.0.dist-info/METADATA,sha256=-7TzQTGoWZMGvRtqMiowqWOpSo1JyGFPaEk2U1qABLo,145
|
|
9
|
+
python_library_xiaomi_miot-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
10
|
+
python_library_xiaomi_miot-0.1.0.dist-info/RECORD,,
|
xiaomi_miot/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
warnings.filterwarnings("ignore", category=FutureWarning, module=r"miio\.miot_device")
|
|
3
|
+
|
|
4
|
+
from .device import MiotDevice
|
|
5
|
+
from .models import DeviceParams, GetParams, SetParams
|
|
6
|
+
from .extensions import get_extension, list_extensions
|
|
7
|
+
from ._api import execute
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"MiotDevice",
|
|
11
|
+
"DeviceParams",
|
|
12
|
+
"GetParams",
|
|
13
|
+
"SetParams",
|
|
14
|
+
"get_extension",
|
|
15
|
+
"list_extensions",
|
|
16
|
+
"execute",
|
|
17
|
+
]
|
xiaomi_miot/_api.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from .device import MiotDevice
|
|
2
|
+
from .models import DeviceParams, GetParams, SetParams
|
|
3
|
+
from .extensions import get_extension, list_extensions
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def execute(params: dict) -> dict:
|
|
7
|
+
"""统一入口,用户输入永远是 dict
|
|
8
|
+
|
|
9
|
+
通用模式(无 type):
|
|
10
|
+
{"ip": "...", "token": "...", "siid": 2, "piid": 1} → 读属性
|
|
11
|
+
{"ip": "...", "token": "...", "siid": 2, "piid": 1, "value": 1} → 写属性
|
|
12
|
+
{"ip": "...", "token": "...", "action": "info"} → 设备信息
|
|
13
|
+
|
|
14
|
+
扩展模式(有 type):
|
|
15
|
+
{"type": "switch", "ip": "...", "token": "...", "on": True} → 走开关扩展
|
|
16
|
+
"""
|
|
17
|
+
device_type = params.get("type")
|
|
18
|
+
|
|
19
|
+
if device_type:
|
|
20
|
+
return _execute_extension(device_type, params)
|
|
21
|
+
return _execute_generic(params)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _execute_extension(device_type: str, params: dict) -> dict:
|
|
25
|
+
ext_cls = get_extension(device_type)
|
|
26
|
+
if ext_cls is None:
|
|
27
|
+
return {
|
|
28
|
+
"ok": False,
|
|
29
|
+
"error": f"未知设备类型: {device_type}",
|
|
30
|
+
"available": list_extensions(),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ext = ext_cls()
|
|
34
|
+
validated = ext.Params(**params)
|
|
35
|
+
device = MiotDevice(validated.ip, validated.token)
|
|
36
|
+
return ext.execute(device, validated)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _execute_generic(params: dict) -> dict:
|
|
40
|
+
action = params.get("action", "prop")
|
|
41
|
+
|
|
42
|
+
if action == "info":
|
|
43
|
+
dp = DeviceParams(**params)
|
|
44
|
+
device = MiotDevice(dp.ip, dp.token)
|
|
45
|
+
return {"ok": True, **device.info()}
|
|
46
|
+
|
|
47
|
+
if "value" in params:
|
|
48
|
+
sp = SetParams(**params)
|
|
49
|
+
device = MiotDevice(sp.ip, sp.token)
|
|
50
|
+
return device.set_prop(sp.did, sp.siid, sp.piid, sp.value)
|
|
51
|
+
|
|
52
|
+
gp = GetParams(**params)
|
|
53
|
+
device = MiotDevice(gp.ip, gp.token)
|
|
54
|
+
return device.get_prop(gp.did, gp.siid, gp.piid)
|
xiaomi_miot/device.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from miio import Device
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MiotDevice:
|
|
5
|
+
"""纯局域网 MIoT 设备操作,不依赖云端"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, ip: str, token: str):
|
|
8
|
+
self._dev = Device(ip, token)
|
|
9
|
+
|
|
10
|
+
def info(self) -> dict:
|
|
11
|
+
"""获取设备基本信息(model, firmware, hardware 等)"""
|
|
12
|
+
raw = self._dev.info()
|
|
13
|
+
return {
|
|
14
|
+
"model": raw.model,
|
|
15
|
+
"mac": raw.mac_address,
|
|
16
|
+
"firmware": raw.firmware_version,
|
|
17
|
+
"hardware": raw.hardware_version,
|
|
18
|
+
"raw": str(raw),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def get_prop(self, did: str, siid: int, piid: int) -> dict:
|
|
22
|
+
results = self._dev.send("get_properties", [
|
|
23
|
+
{"did": did, "siid": siid, "piid": piid}
|
|
24
|
+
])
|
|
25
|
+
r = results[0]
|
|
26
|
+
if r.get("code") == 0:
|
|
27
|
+
return {"ok": True, "value": r["value"]}
|
|
28
|
+
return {"ok": False, "error": r}
|
|
29
|
+
|
|
30
|
+
def get_props(self, props: list[dict]) -> list[dict]:
|
|
31
|
+
return self._dev.send("get_properties", props)
|
|
32
|
+
|
|
33
|
+
def set_prop(self, did: str, siid: int, piid: int, value) -> dict:
|
|
34
|
+
results = self._dev.send("set_properties", [
|
|
35
|
+
{"did": did, "siid": siid, "piid": piid, "value": value}
|
|
36
|
+
])
|
|
37
|
+
r = results[0]
|
|
38
|
+
if r.get("code") == 0:
|
|
39
|
+
return {"ok": True}
|
|
40
|
+
return {"ok": False, "error": r}
|
|
41
|
+
|
|
42
|
+
def set_props(self, props: list[dict]) -> list[dict]:
|
|
43
|
+
return self._dev.send("set_properties", props)
|
|
44
|
+
|
|
45
|
+
def call_action(self, did: str, siid: int, aiid: int, params: list | None = None) -> dict:
|
|
46
|
+
return self._dev.send("action", {
|
|
47
|
+
"did": did, "siid": siid, "aiid": aiid, "in": params or []
|
|
48
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import pkgutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
_extensions: dict[str, type] = {}
|
|
6
|
+
_discovered = False
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extension(name: str):
|
|
10
|
+
"""装饰器:注册设备扩展
|
|
11
|
+
|
|
12
|
+
@extension("switch")
|
|
13
|
+
class SwitchExtension:
|
|
14
|
+
class Params(DeviceParams): ...
|
|
15
|
+
def execute(self, device, params): ...
|
|
16
|
+
"""
|
|
17
|
+
def decorator(cls):
|
|
18
|
+
_extensions[name] = cls
|
|
19
|
+
return cls
|
|
20
|
+
return decorator
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_extension(name: str):
|
|
24
|
+
discover()
|
|
25
|
+
return _extensions.get(name)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_extensions() -> list[str]:
|
|
29
|
+
discover()
|
|
30
|
+
return list(_extensions.keys())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def discover():
|
|
34
|
+
"""动态扫描 extensions/ 目录下所有模块,导入以触发 @extension 注册"""
|
|
35
|
+
global _discovered
|
|
36
|
+
if _discovered:
|
|
37
|
+
return
|
|
38
|
+
_discovered = True
|
|
39
|
+
|
|
40
|
+
ext_dir = Path(__file__).parent
|
|
41
|
+
for finder, module_name, is_pkg in pkgutil.iter_modules([str(ext_dir)]):
|
|
42
|
+
if module_name.startswith("_"):
|
|
43
|
+
continue
|
|
44
|
+
importlib.import_module(f"{__package__}.{module_name}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from xiaomi_miot.device import MiotDevice
|
|
3
|
+
from xiaomi_miot.extensions import extension
|
|
4
|
+
from xiaomi_miot.models import DeviceParams
|
|
5
|
+
|
|
6
|
+
class SwitchParams(DeviceParams):
|
|
7
|
+
type: str = "switch"
|
|
8
|
+
on: bool | None = Field(default=None,description="None=查询状态, True=开, False=关")
|
|
9
|
+
siid: int = Field(default=2,description="默认 service:Switch")
|
|
10
|
+
piid: int = Field(default=1,description="默认 property:On")
|
|
11
|
+
|
|
12
|
+
@extension("switch")
|
|
13
|
+
class SwitchExtension:
|
|
14
|
+
Params = SwitchParams
|
|
15
|
+
|
|
16
|
+
def execute(self, device: MiotDevice, params: SwitchParams) -> dict:
|
|
17
|
+
if params.on is None:
|
|
18
|
+
return self._get_state(device, params)
|
|
19
|
+
return self._set_state(device, params)
|
|
20
|
+
|
|
21
|
+
def _get_state(self, device: MiotDevice, params: SwitchParams) -> dict:
|
|
22
|
+
result = device.get_prop("switch", params.siid, params.piid)
|
|
23
|
+
if result["ok"]:
|
|
24
|
+
result["state"] = "开启" if result["value"] else "关闭"
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
def _set_state(self, device: MiotDevice, params: SwitchParams) -> dict:
|
|
28
|
+
result = device.set_prop("switch", params.siid, params.piid, params.on)
|
|
29
|
+
if result["ok"]:
|
|
30
|
+
result["state"] = "开启" if params.on else "关闭"
|
|
31
|
+
return result
|
xiaomi_miot/models.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeviceParams(BaseModel):
|
|
6
|
+
"""最基础的设备参数:局域网连接所需"""
|
|
7
|
+
model_config = ConfigDict(extra="ignore")
|
|
8
|
+
|
|
9
|
+
ip: str = Field(description="设备 IP 地址")
|
|
10
|
+
token: str = Field(description="设备 Token")
|
|
11
|
+
type: str | None = Field(default=None,description="设备类型")
|
|
12
|
+
|
|
13
|
+
class GetParams(DeviceParams):
|
|
14
|
+
"""通用属性读取"""
|
|
15
|
+
did: str = Field(default="prop",description="默认 did:prop")
|
|
16
|
+
siid: int = Field(description="service ID")
|
|
17
|
+
piid: int = Field(description="property ID")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SetParams(GetParams):
|
|
21
|
+
"""通用属性设置"""
|
|
22
|
+
value: Any = Field(description="属性值")
|