mijiaAPI 1.3.0__tar.gz → 1.3.1__tar.gz
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.
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/PKG-INFO +2 -1
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/README.md +1 -0
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/__init__.py +1 -1
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/devices.py +203 -141
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/urls.py +6 -5
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/pyproject.toml +1 -1
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/LICENSE +0 -0
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/apis.py +0 -0
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/login.py +0 -0
- {mijiaapi-1.3.0 → mijiaapi-1.3.1}/mijiaAPI/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mijiaAPI
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: A Python API for Xiaomi Mijia
|
|
5
5
|
Home-page: https://github.com/Do1e/mijia-api
|
|
6
6
|
License: GPLv3
|
|
@@ -61,6 +61,7 @@ pip install mijiaAPI
|
|
|
61
61
|
* `get(name: str, did: str) -> Union[bool, int]`:获取设备属性
|
|
62
62
|
* v1.2.0 新增直接通过名称设置/获取属性,需要在初始化时传入`did`,详见[demos/test_devices_v2_light.py](demos/test_devices_v2_light.py)。名称中包含`-`的属性需要替换为`_`。
|
|
63
63
|
* **欢迎大家把自己编写的设备属性字典分享到Issues中,方便大家使用**
|
|
64
|
+
* 也可以调用`get_device_info(device_model: str) -> dict`函数从[米家设备列表](https://home.miot-spec.com/)在线获取设备属性字典,详见[demos/test_get_device_info.py](demos/test_get_device_info.py)
|
|
64
65
|
|
|
65
66
|
## 致谢
|
|
66
67
|
* [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
|
|
@@ -41,6 +41,7 @@ pip install mijiaAPI
|
|
|
41
41
|
* `get(name: str, did: str) -> Union[bool, int]`:获取设备属性
|
|
42
42
|
* v1.2.0 新增直接通过名称设置/获取属性,需要在初始化时传入`did`,详见[demos/test_devices_v2_light.py](demos/test_devices_v2_light.py)。名称中包含`-`的属性需要替换为`_`。
|
|
43
43
|
* **欢迎大家把自己编写的设备属性字典分享到Issues中,方便大家使用**
|
|
44
|
+
* 也可以调用`get_device_info(device_model: str) -> dict`函数从[米家设备列表](https://home.miot-spec.com/)在线获取设备属性字典,详见[demos/test_get_device_info.py](demos/test_get_device_info.py)
|
|
44
45
|
|
|
45
46
|
## 致谢
|
|
46
47
|
* [janzlan/mijia-api](https://gitee.com/janzlan/mijia-api/tree/master)
|
|
@@ -1,141 +1,203 @@
|
|
|
1
|
-
from typing import Union, Optional
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
self.
|
|
13
|
-
self.
|
|
14
|
-
self.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
1
|
+
from typing import Union, Optional
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import requests
|
|
5
|
+
from time import sleep
|
|
6
|
+
from .apis import mijiaAPI
|
|
7
|
+
from .urls import deviceURL
|
|
8
|
+
|
|
9
|
+
class DevProp(object):
|
|
10
|
+
def __init__(self, prop_dict: dict):
|
|
11
|
+
self.name = prop_dict['name']
|
|
12
|
+
self.desc = prop_dict['description']
|
|
13
|
+
self.type = prop_dict['type']
|
|
14
|
+
if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
|
|
15
|
+
raise ValueError(f'Unsupported type: {self.type}, available types: bool, int, uint, float, string')
|
|
16
|
+
self.rw = prop_dict['rw']
|
|
17
|
+
self.unit = prop_dict['unit']
|
|
18
|
+
self.range = prop_dict['range']
|
|
19
|
+
self.method = prop_dict['method']
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return f' {self.name}: {self.desc}\n' \
|
|
23
|
+
f' valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}'
|
|
24
|
+
|
|
25
|
+
class DevAction(object):
|
|
26
|
+
def __init__(self, act_dict: dict):
|
|
27
|
+
self.name = act_dict['name']
|
|
28
|
+
self.desc = act_dict['description']
|
|
29
|
+
self.method = act_dict['method']
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f' {self.name}: {self.desc}'
|
|
33
|
+
|
|
34
|
+
class mijiaDevices(object):
|
|
35
|
+
def __init__(self, api: mijiaAPI, dev_info: dict,
|
|
36
|
+
did: Optional[str] = None,
|
|
37
|
+
sleep_time: Optional[Union[int, float]] = 0.5):
|
|
38
|
+
self.api = api
|
|
39
|
+
self.name = dev_info['name']
|
|
40
|
+
self.model = dev_info['model']
|
|
41
|
+
self.prop_list = {prop['name']: DevProp(prop) for prop in dev_info['properties']}
|
|
42
|
+
self.prop_list.update({prop['name'].replace('-', '_'): DevProp(prop) for prop in dev_info['properties'] if '-' in prop['name']})
|
|
43
|
+
if 'actions' in dev_info:
|
|
44
|
+
self.action_list = {act['name']: DevAction(act) for act in dev_info['actions']}
|
|
45
|
+
else:
|
|
46
|
+
self.action_list = {}
|
|
47
|
+
self.did = did
|
|
48
|
+
self.sleep_time = sleep_time
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
prop_list = [str(v) for k, v in self.prop_list.items() if '_' not in k]
|
|
52
|
+
action_list = [str(v) for v in self.action_list.values()]
|
|
53
|
+
return f'{self.name} ({self.model})\n' \
|
|
54
|
+
'Properties:\n' + '\n'.join(prop_list) + '\n' \
|
|
55
|
+
'Actions:\n' + '\n'.join(action_list)
|
|
56
|
+
|
|
57
|
+
def set(self, name: str, did: str, value: Union[bool, int]) -> Union[bool, int]:
|
|
58
|
+
if name not in self.prop_list:
|
|
59
|
+
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
60
|
+
prop = self.prop_list[name]
|
|
61
|
+
if 'w' not in prop.rw:
|
|
62
|
+
raise ValueError(f'Property {name} is read-only')
|
|
63
|
+
if prop.type == 'bool':
|
|
64
|
+
if not isinstance(value, bool):
|
|
65
|
+
raise ValueError(f'Invalid value for bool: {value}, should be True or False')
|
|
66
|
+
elif prop.type in ['int', 'uint']:
|
|
67
|
+
try:
|
|
68
|
+
value = int(value)
|
|
69
|
+
if prop.range:
|
|
70
|
+
if value < prop.range[0] or value > prop.range[1]:
|
|
71
|
+
raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
|
|
72
|
+
except ValueError:
|
|
73
|
+
raise ValueError(f'Invalid value for int: {value}, should be an integer')
|
|
74
|
+
elif prop.type == 'float':
|
|
75
|
+
try:
|
|
76
|
+
value = float(value)
|
|
77
|
+
if prop.range:
|
|
78
|
+
if value < prop.range[0] or value > prop.range[1]:
|
|
79
|
+
raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
|
|
80
|
+
except ValueError:
|
|
81
|
+
raise ValueError(f'Invalid value for float: {value}, should be a float')
|
|
82
|
+
elif prop.type == 'string':
|
|
83
|
+
if not isinstance(value, str):
|
|
84
|
+
raise ValueError(f'Invalid value for string: {value}, should be a string')
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
|
|
87
|
+
method = prop.method.copy()
|
|
88
|
+
method['did'] = did
|
|
89
|
+
method['value'] = value
|
|
90
|
+
ret = self.api.set_devices_prop([method])[0]['code'] == 0
|
|
91
|
+
sleep(self.sleep_time)
|
|
92
|
+
return ret
|
|
93
|
+
|
|
94
|
+
def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) -> Union[bool, int]:
|
|
95
|
+
if did is not None:
|
|
96
|
+
return self.set(name, did, value)
|
|
97
|
+
elif self.did is not None:
|
|
98
|
+
return self.set(name, self.did, value)
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError('Please specify the did')
|
|
101
|
+
|
|
102
|
+
def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
|
|
103
|
+
if did is None:
|
|
104
|
+
did = self.did
|
|
105
|
+
if did is None:
|
|
106
|
+
raise ValueError('Please specify the did')
|
|
107
|
+
if name not in self.prop_list:
|
|
108
|
+
raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
|
|
109
|
+
prop = self.prop_list[name]
|
|
110
|
+
if 'r' not in prop.rw:
|
|
111
|
+
raise ValueError(f'Property {name} is write-only')
|
|
112
|
+
method = prop.method.copy()
|
|
113
|
+
method['did'] = did
|
|
114
|
+
ret = self.api.get_devices_prop([method])[0]['value']
|
|
115
|
+
sleep(self.sleep_time)
|
|
116
|
+
return ret
|
|
117
|
+
|
|
118
|
+
def __setattr__(self, name: str, value: Union[bool, int]) -> None:
|
|
119
|
+
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
120
|
+
if not self.set_v2(name, value):
|
|
121
|
+
raise RuntimeError(f'Failed to set property: {name}')
|
|
122
|
+
else:
|
|
123
|
+
super().__setattr__(name, value)
|
|
124
|
+
|
|
125
|
+
def __getattr__(self, name: str) -> Union[bool, int]:
|
|
126
|
+
if 'prop_list' in self.__dict__ and name in self.prop_list:
|
|
127
|
+
return self.get(name)
|
|
128
|
+
else:
|
|
129
|
+
return super().__getattr__(name)
|
|
130
|
+
|
|
131
|
+
def run_action(self, name: str, did: Optional[str] = None, value: Optional[Union[list, tuple]] = None) -> bool:
|
|
132
|
+
if did is None:
|
|
133
|
+
did = self.did
|
|
134
|
+
if did is None:
|
|
135
|
+
raise ValueError('Please specify the did')
|
|
136
|
+
if name not in self.action_list:
|
|
137
|
+
raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
|
|
138
|
+
act = self.action_list[name]
|
|
139
|
+
method = act.method.copy()
|
|
140
|
+
method['did'] = did
|
|
141
|
+
if value is not None:
|
|
142
|
+
method['value'] = value
|
|
143
|
+
ret = self.api.run_action(method)['code'] == 0
|
|
144
|
+
sleep(self.sleep_time)
|
|
145
|
+
return ret
|
|
146
|
+
|
|
147
|
+
def get_device_info(device_model: str) -> dict:
|
|
148
|
+
response = requests.get(deviceURL + device_model)
|
|
149
|
+
if response.status_code != 200:
|
|
150
|
+
raise RuntimeError(f'Failed to get device info')
|
|
151
|
+
content = re.search(r'data-page="(.*?)">', response.text)
|
|
152
|
+
if content is None:
|
|
153
|
+
raise RuntimeError(f'Failed to get device info')
|
|
154
|
+
content = content.group(1)
|
|
155
|
+
content = json.loads(content.replace('"', '"'))
|
|
156
|
+
|
|
157
|
+
result = {}
|
|
158
|
+
result['name'] = content['props']['product']['name']
|
|
159
|
+
result['model'] = content['props']['product']['model']
|
|
160
|
+
result['properties'] = []
|
|
161
|
+
result['actions'] = []
|
|
162
|
+
services = content['props']['spec']['services']
|
|
163
|
+
|
|
164
|
+
for siid in services:
|
|
165
|
+
if 'properties' in services[siid]:
|
|
166
|
+
for piid in services[siid]['properties']:
|
|
167
|
+
prop = services[siid]['properties'][piid]
|
|
168
|
+
if prop['format'].startswith('int'):
|
|
169
|
+
prop_type = 'int'
|
|
170
|
+
elif prop['format'].startswith('uint'):
|
|
171
|
+
prop_type = 'uint'
|
|
172
|
+
else:
|
|
173
|
+
prop_type = prop['format']
|
|
174
|
+
item ={
|
|
175
|
+
'name': prop['name'],
|
|
176
|
+
'description': prop['description'],
|
|
177
|
+
'type': prop_type,
|
|
178
|
+
'rw': ''.join([
|
|
179
|
+
'r' if 'read' in prop['access'] else '',
|
|
180
|
+
'w' if 'write' in prop['access'] else ''
|
|
181
|
+
]),
|
|
182
|
+
'unit': prop.get('unit', None),
|
|
183
|
+
'range': prop.get('value-range', None),
|
|
184
|
+
'method': {
|
|
185
|
+
'siid': int(siid),
|
|
186
|
+
'piid': int(piid)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if item['range'] is not None:
|
|
190
|
+
item['range'] = item['range'][:2]
|
|
191
|
+
result['properties'].append({k: None if v == 'none' else v for k, v in item.items()})
|
|
192
|
+
if 'actions' in services[siid]:
|
|
193
|
+
for aiid in services[siid]['actions']:
|
|
194
|
+
act = services[siid]['actions'][aiid]
|
|
195
|
+
result['actions'].append({
|
|
196
|
+
'name': act['name'],
|
|
197
|
+
'description': act['description'],
|
|
198
|
+
'method': {
|
|
199
|
+
'siid': int(siid),
|
|
200
|
+
'aiid': int(aiid)
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
return result
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
sid = 'xiaomiio'
|
|
2
|
-
msgURL = 'https://account.xiaomi.com/pass/serviceLogin?sid=%s&_json=true' % sid
|
|
3
|
-
loginURL = 'https://account.xiaomi.com/pass/serviceLoginAuth2'
|
|
4
|
-
qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
|
|
5
|
-
apiURL = 'https://api.io.mi.com/app'
|
|
1
|
+
sid = 'xiaomiio'
|
|
2
|
+
msgURL = 'https://account.xiaomi.com/pass/serviceLogin?sid=%s&_json=true' % sid
|
|
3
|
+
loginURL = 'https://account.xiaomi.com/pass/serviceLoginAuth2'
|
|
4
|
+
qrURL = 'https://account.xiaomi.com/longPolling/loginUrl'
|
|
5
|
+
apiURL = 'https://api.io.mi.com/app'
|
|
6
|
+
deviceURL = 'https://home.miot-spec.com/spec/'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|