mijiaAPI 1.3.0__tar.gz → 1.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mijiaAPI
3
- Version: 1.3.0
3
+ Version: 1.3.2
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,3 +1,3 @@
1
1
  from .login import mijiaLogin
2
2
  from .apis import mijiaAPI
3
- from .devices import mijiaDevices
3
+ from .devices import mijiaDevices, get_device_info
@@ -1,141 +1,209 @@
1
- from typing import Union, Optional
2
- from time import sleep
3
- from .apis import mijiaAPI
4
-
5
- class DevProp(object):
6
- def __init__(self, prop_dict: dict):
7
- self.name = prop_dict['name']
8
- self.desc = prop_dict['description']
9
- self.type = prop_dict['type']
10
- if self.type not in ['bool', 'int', 'uint', 'float', 'string']:
11
- raise ValueError(f'Unsupported type: {self.type}, available types: bool, int, uint, float, string')
12
- self.rw = prop_dict['rw']
13
- self.unit = prop_dict['unit']
14
- self.range = prop_dict['range']
15
- self.method = prop_dict['method']
16
-
17
- def __str__(self):
18
- return f' {self.name}: {self.desc}\n' \
19
- f' valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}'
20
-
21
- class DevAction(object):
22
- def __init__(self, act_dict: dict):
23
- self.name = act_dict['name']
24
- self.desc = act_dict['description']
25
- self.method = act_dict['method']
26
-
27
- def __str__(self):
28
- return f' {self.name}: {self.desc}'
29
-
30
- class mijiaDevices(object):
31
- def __init__(self, api: mijiaAPI, dev_info: dict,
32
- did: Optional[str] = None,
33
- sleep_time: Optional[Union[int, float]] = 0.5):
34
- self.api = api
35
- self.name = dev_info['name']
36
- self.model = dev_info['model']
37
- self.prop_list = {prop['name']: DevProp(prop) for prop in dev_info['properties']}
38
- self.prop_list.update({prop['name'].replace('-', '_'): DevProp(prop) for prop in dev_info['properties'] if '-' in prop['name']})
39
- if 'actions' in dev_info:
40
- self.action_list = {act['name']: DevAction(act) for act in dev_info['actions']}
41
- else:
42
- self.action_list = {}
43
- self.did = did
44
- self.sleep_time = sleep_time
45
-
46
- def __str__(self):
47
- prop_list = [str(v) for k, v in self.prop_list.items() if '_' not in k]
48
- action_list = [str(v) for v in self.action_list.values()]
49
- return f'{self.name} ({self.model})\n' \
50
- 'Properties:\n' + '\n'.join(prop_list) + '\n' \
51
- 'Actions:\n' + '\n'.join(action_list)
52
-
53
- def set(self, name: str, did: str, value: Union[bool, int]) -> Union[bool, int]:
54
- if name not in self.prop_list:
55
- raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
56
- prop = self.prop_list[name]
57
- if 'w' not in prop.rw:
58
- raise ValueError(f'Property {name} is read-only')
59
- if prop.type == 'bool':
60
- if not isinstance(value, bool):
61
- raise ValueError(f'Invalid value for bool: {value}, should be True or False')
62
- elif prop.type in ['int', 'uint']:
63
- try:
64
- value = int(value)
65
- if prop.range:
66
- if value < prop.range[0] or value > prop.range[1]:
67
- raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
68
- except ValueError:
69
- raise ValueError(f'Invalid value for int: {value}, should be an integer')
70
- elif prop.type == 'float':
71
- try:
72
- value = float(value)
73
- if prop.range:
74
- if value < prop.range[0] or value > prop.range[1]:
75
- raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
76
- except ValueError:
77
- raise ValueError(f'Invalid value for float: {value}, should be a float')
78
- elif prop.type == 'string':
79
- if not isinstance(value, str):
80
- raise ValueError(f'Invalid value for string: {value}, should be a string')
81
- else:
82
- raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
83
- method = prop.method.copy()
84
- method['did'] = did
85
- method['value'] = value
86
- ret = self.api.set_devices_prop([method])[0]['code'] == 0
87
- sleep(self.sleep_time)
88
- return ret
89
-
90
- def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) -> Union[bool, int]:
91
- if did is not None:
92
- return self.set(name, did, value)
93
- elif self.did is not None:
94
- return self.set(name, self.did, value)
95
- else:
96
- raise ValueError('Please specify the did')
97
-
98
- def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
99
- if did is None:
100
- did = self.did
101
- if did is None:
102
- raise ValueError('Please specify the did')
103
- if name not in self.prop_list:
104
- raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
105
- prop = self.prop_list[name]
106
- if 'r' not in prop.rw:
107
- raise ValueError(f'Property {name} is write-only')
108
- method = prop.method.copy()
109
- method['did'] = did
110
- ret = self.api.get_devices_prop([method])[0]['value']
111
- sleep(self.sleep_time)
112
- return ret
113
-
114
- def __setattr__(self, name: str, value: Union[bool, int]) -> None:
115
- if 'prop_list' in self.__dict__ and name in self.prop_list:
116
- if not self.set_v2(name, value):
117
- raise RuntimeError(f'Failed to set property: {name}')
118
- else:
119
- super().__setattr__(name, value)
120
-
121
- def __getattr__(self, name: str) -> Union[bool, int]:
122
- if 'prop_list' in self.__dict__ and name in self.prop_list:
123
- return self.get(name)
124
- else:
125
- return super().__getattr__(name)
126
-
127
- def run_action(self, name: str, did: Optional[str] = None, value: Optional[Union[list, tuple]] = None) -> bool:
128
- if did is None:
129
- did = self.did
130
- if did is None:
131
- raise ValueError('Please specify the did')
132
- if name not in self.action_list:
133
- raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
134
- act = self.action_list[name]
135
- method = act.method.copy()
136
- method['did'] = did
137
- if value is not None:
138
- method['value'] = value
139
- ret = self.api.run_action(method)['code'] == 0
140
- sleep(self.sleep_time)
141
- return ret
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.value_list = prop_dict.get('value-list', None)
20
+ self.method = prop_dict['method']
21
+
22
+ def __str__(self):
23
+ return f' {self.name}: {self.desc}\n' \
24
+ f' valuetype: {self.type}, rw: {self.rw}, unit: {self.unit}, range: {self.range}' + \
25
+ (('\n ' + ', '.join([f'{item["value"]}: {item["description"]}' for item in self.value_list])) if self.value_list else '')
26
+
27
+ class DevAction(object):
28
+ def __init__(self, act_dict: dict):
29
+ self.name = act_dict['name']
30
+ self.desc = act_dict['description']
31
+ self.method = act_dict['method']
32
+
33
+ def __str__(self):
34
+ return f' {self.name}: {self.desc}'
35
+
36
+ class mijiaDevices(object):
37
+ def __init__(self, api: mijiaAPI, dev_info: dict,
38
+ did: Optional[str] = None,
39
+ sleep_time: Optional[Union[int, float]] = 0.5):
40
+ self.api = api
41
+ self.name = dev_info['name']
42
+ self.model = dev_info['model']
43
+ self.prop_list = {prop['name']: DevProp(prop) for prop in dev_info['properties']}
44
+ self.prop_list.update({prop['name'].replace('-', '_'): DevProp(prop) for prop in dev_info['properties'] if '-' in prop['name']})
45
+ if 'actions' in dev_info:
46
+ self.action_list = {act['name']: DevAction(act) for act in dev_info['actions']}
47
+ else:
48
+ self.action_list = {}
49
+ self.did = did
50
+ self.sleep_time = sleep_time
51
+
52
+ def __str__(self):
53
+ prop_list = [str(v) for k, v in self.prop_list.items() if '_' not in k]
54
+ action_list = [str(v) for v in self.action_list.values()]
55
+ return f'{self.name} ({self.model})\n' \
56
+ 'Properties:\n' + '\n'.join(prop_list) + '\n' \
57
+ 'Actions:\n' + '\n'.join(action_list)
58
+
59
+ def set(self, name: str, did: str, value: Union[bool, int]) -> Union[bool, int]:
60
+ if name not in self.prop_list:
61
+ raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
62
+ prop = self.prop_list[name]
63
+ if 'w' not in prop.rw:
64
+ raise ValueError(f'Property {name} is read-only')
65
+ if prop.value_list:
66
+ if value not in [item['value'] for item in prop.value_list]:
67
+ raise ValueError(f'Invalid value: {value}, should be in {prop.value_list}')
68
+ if prop.type == 'bool':
69
+ if not isinstance(value, bool):
70
+ raise ValueError(f'Invalid value for bool: {value}, should be True or False')
71
+ elif prop.type in ['int', 'uint']:
72
+ try:
73
+ value = int(value)
74
+ if prop.range:
75
+ if value < prop.range[0] or value > prop.range[1]:
76
+ raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
77
+ except ValueError:
78
+ raise ValueError(f'Invalid value for int: {value}, should be an integer')
79
+ elif prop.type == 'float':
80
+ try:
81
+ value = float(value)
82
+ if prop.range:
83
+ if value < prop.range[0] or value > prop.range[1]:
84
+ raise ValueError(f'Value out of range: {value}, should be in range {prop.range}')
85
+ except ValueError:
86
+ raise ValueError(f'Invalid value for float: {value}, should be a float')
87
+ elif prop.type == 'string':
88
+ if not isinstance(value, str):
89
+ raise ValueError(f'Invalid value for string: {value}, should be a string')
90
+ else:
91
+ raise ValueError(f'Unsupported type: {prop.type}, available types: bool, int, uint, float, string')
92
+ method = prop.method.copy()
93
+ method['did'] = did
94
+ method['value'] = value
95
+ ret = self.api.set_devices_prop([method])[0]['code'] == 0
96
+ sleep(self.sleep_time)
97
+ return ret
98
+
99
+ def set_v2(self, name: str, value: Union[bool, int], did: Optional[str] = None) -> Union[bool, int]:
100
+ if did is not None:
101
+ return self.set(name, did, value)
102
+ elif self.did is not None:
103
+ return self.set(name, self.did, value)
104
+ else:
105
+ raise ValueError('Please specify the did')
106
+
107
+ def get(self, name: str, did: Optional[str] = None) -> Union[bool, int]:
108
+ if did is None:
109
+ did = self.did
110
+ if did is None:
111
+ raise ValueError('Please specify the did')
112
+ if name not in self.prop_list:
113
+ raise ValueError(f'Unsupported property: {name}, available properties: {list(self.prop_list.keys())}')
114
+ prop = self.prop_list[name]
115
+ if 'r' not in prop.rw:
116
+ raise ValueError(f'Property {name} is write-only')
117
+ method = prop.method.copy()
118
+ method['did'] = did
119
+ ret = self.api.get_devices_prop([method])[0]['value']
120
+ sleep(self.sleep_time)
121
+ return ret
122
+
123
+ def __setattr__(self, name: str, value: Union[bool, int]) -> None:
124
+ if 'prop_list' in self.__dict__ and name in self.prop_list:
125
+ if not self.set_v2(name, value):
126
+ raise RuntimeError(f'Failed to set property: {name}')
127
+ else:
128
+ super().__setattr__(name, value)
129
+
130
+ def __getattr__(self, name: str) -> Union[bool, int]:
131
+ if 'prop_list' in self.__dict__ and name in self.prop_list:
132
+ return self.get(name)
133
+ else:
134
+ return super().__getattr__(name)
135
+
136
+ def run_action(self, name: str, did: Optional[str] = None, value: Optional[Union[list, tuple]] = None) -> bool:
137
+ if did is None:
138
+ did = self.did
139
+ if did is None:
140
+ raise ValueError('Please specify the did')
141
+ if name not in self.action_list:
142
+ raise ValueError(f'Unsupported action: {name}, available actions: {list(self.action_list.keys())}')
143
+ act = self.action_list[name]
144
+ method = act.method.copy()
145
+ method['did'] = did
146
+ if value is not None:
147
+ method['value'] = value
148
+ ret = self.api.run_action(method)['code'] == 0
149
+ sleep(self.sleep_time)
150
+ return ret
151
+
152
+ def get_device_info(device_model: str) -> dict:
153
+ response = requests.get(deviceURL + device_model)
154
+ if response.status_code != 200:
155
+ raise RuntimeError(f'Failed to get device info')
156
+ content = re.search(r'data-page="(.*?)">', response.text)
157
+ if content is None:
158
+ raise RuntimeError(f'Failed to get device info')
159
+ content = content.group(1)
160
+ content = json.loads(content.replace('&quot;', '"'))
161
+
162
+ result = {}
163
+ result['name'] = content['props']['product']['name']
164
+ result['model'] = content['props']['product']['model']
165
+ result['properties'] = []
166
+ result['actions'] = []
167
+ services = content['props']['spec']['services']
168
+
169
+ for siid in services:
170
+ if 'properties' in services[siid]:
171
+ for piid in services[siid]['properties']:
172
+ prop = services[siid]['properties'][piid]
173
+ if prop['format'].startswith('int'):
174
+ prop_type = 'int'
175
+ elif prop['format'].startswith('uint'):
176
+ prop_type = 'uint'
177
+ else:
178
+ prop_type = prop['format']
179
+ item ={
180
+ 'name': prop['name'],
181
+ 'description': prop['description'],
182
+ 'type': prop_type,
183
+ 'rw': ''.join([
184
+ 'r' if 'read' in prop['access'] else '',
185
+ 'w' if 'write' in prop['access'] else ''
186
+ ]),
187
+ 'unit': prop.get('unit', None),
188
+ 'range': prop.get('value-range', None),
189
+ 'value-list': prop.get('value-list', None),
190
+ 'method': {
191
+ 'siid': int(siid),
192
+ 'piid': int(piid)
193
+ }
194
+ }
195
+ if item['range'] is not None:
196
+ item['range'] = item['range'][:2]
197
+ result['properties'].append({k: None if v == 'none' else v for k, v in item.items()})
198
+ if 'actions' in services[siid]:
199
+ for aiid in services[siid]['actions']:
200
+ act = services[siid]['actions'][aiid]
201
+ result['actions'].append({
202
+ 'name': act['name'],
203
+ 'description': act['description'],
204
+ 'method': {
205
+ 'siid': int(siid),
206
+ 'aiid': int(aiid)
207
+ }
208
+ })
209
+ 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/'
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mijiaAPI"
3
- version = "1.3.0"
3
+ version = "1.3.2"
4
4
  description = "A Python API for Xiaomi Mijia"
5
5
  authors = ["Do1e <dpj.email@qq.com>"]
6
6
  license = "GPLv3"
File without changes
File without changes
File without changes
File without changes