From 1dfbdec29701a1fb7bdbcfb150fa96353604e8b0 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Mon, 26 Sep 2022 09:53:50 +0800 Subject: [PATCH 01/15] Renew version to 2.0.1 --- micli.py | 2 ++ setup.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/micli.py b/micli.py index 39c50f0..2864e6d 100755 --- a/micli.py +++ b/micli.py @@ -9,8 +9,10 @@ from miservice import MiAccount, MiNAService, MiIOService, miio_command, miio_command_help +MISERVICE_VERSION = '2.0.1' def usage(): + print("MiService %s - XiaoMi Cloud Service\n" % MISERVICE_VERSION) print("Usage: The following variables must be set:") print(" export MI_USER=") print(" export MI_PASS=") diff --git a/setup.py b/setup.py index 18fd7e1..85d4c61 100755 --- a/setup.py +++ b/setup.py @@ -14,10 +14,12 @@ from pathlib import Path from setuptools import setup +from micli import MISERVICE_VERSION + setup( name='miservice', description='XiaoMi Cloud Service', - version=time.strftime("%Y.%m.%d"), + version=MISERVICE_VERSION, license='MIT', author='Yonsm', author_email='Yonsm@qq.com', From 895406f83c2f4ec4e5ad35281544acc328004e47 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Wed, 26 Jun 2024 22:23:25 +0800 Subject: [PATCH 02/15] fix bug on get_props --- miservice/miiocommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miservice/miiocommand.py b/miservice/miiocommand.py index 46d7129..c470228 100755 --- a/miservice/miiocommand.py +++ b/miservice/miiocommand.py @@ -100,4 +100,4 @@ async def miio_command(service: MiIOService, did, text, prefix='?'): return await service.miot_action(did, props[0], args) do_props = ((service.home_get_props, service.miot_get_props), (service.home_set_props, service.miot_set_props))[setp][miot] - return await do_props(did, props) + return await do_props(did, props if miot or setp else [p[0] for p in props]) From f6971b7d03193097caca5f09b42510c9ebc20810 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 08:48:16 +0800 Subject: [PATCH 03/15] Set default account to None --- miservice/miioservice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/miservice/miioservice.py b/miservice/miioservice.py index bb6b0e0..12ec166 100755 --- a/miservice/miioservice.py +++ b/miservice/miioservice.py @@ -4,14 +4,13 @@ import hashlib import hmac import json -from .miaccount import MiAccount # REGIONS = ['cn', 'de', 'i2', 'ru', 'sg', 'us'] class MiIOService: - def __init__(self, account: MiAccount, region=None): + def __init__(self, account=None, region=None): self.account = account self.server = 'https://' + ('' if region is None or region == 'cn' else region + '.') + 'api.io.mi.com/app' From 017c9ee983d5ebc2faf1533585a256f026766db0 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 09:16:33 +0800 Subject: [PATCH 04/15] Refine argument format! --- miservice/miiocommand.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/miservice/miiocommand.py b/miservice/miiocommand.py index c470228..8e596f3 100755 --- a/miservice/miiocommand.py +++ b/miservice/miiocommand.py @@ -9,19 +9,17 @@ def twins_split(string, sep, default=None): def string_to_value(string): - if string == 'null' or string == 'none': + if string[0] == '#': + return string[1:] + if string == 'null': return None elif string == 'false': return False elif string == 'true': return True - else: + elif string.isdigit(): return int(string) - - -def string_or_value(string): - return string_to_value(string[1:]) if string[0] == '#' else string - + return string def miio_command_help(did=None, prefix='?'): quote = '' if prefix == '?' else "'" @@ -29,9 +27,9 @@ def miio_command_help(did=None, prefix='?'): Get Props: {prefix}[,...]\n\ {prefix}1,1-2,1-3,1-4,2-1,2-2,3\n\ Set Props: {prefix}[,...]\n\ - {prefix}2=#60,2-2=#false,3=test\n\ -Do Action: {prefix} [...] \n\ - {prefix}2 #NA\n\ + {prefix}2=60,2-1=#60,2-2=false,2-3=#false,3=test\n\ +Do Action: {prefix} [...] \n\ + {prefix}2 []\n\ {prefix}5 Hello\n\ {prefix}5-4 Hello #1\n\n\ Call MIoT: {prefix} \n\ @@ -92,11 +90,11 @@ async def miio_command(service: MiIOService, did, text, prefix='?'): if value is None: setp = False elif setp: - prop.append(string_or_value(value)) + prop.append(string_to_value(value)) props.append(prop) if miot and argc > 0: - args = [string_or_value(a) for a in argv] if arg != '#NA' else [] + args = [] if arg == '[]' else [string_to_value(a) for a in argv] return await service.miot_action(did, props[0], args) do_props = ((service.home_get_props, service.miot_get_props), (service.home_set_props, service.miot_set_props))[setp][miot] From 19b79e982093a05e8b1c9d04e3f8db039c6144ef Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 10:02:41 +0800 Subject: [PATCH 05/15] Support string with quote --- miservice/miiocommand.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/miservice/miiocommand.py b/miservice/miiocommand.py index 8e596f3..44934a7 100755 --- a/miservice/miiocommand.py +++ b/miservice/miiocommand.py @@ -9,9 +9,9 @@ def twins_split(string, sep, default=None): def string_to_value(string): - if string[0] == '#': - return string[1:] - if string == 'null': + if string[0] in '"\'#': + return string[1:-1] if string[-1] in '"\'#' else string[1:] + elif string == 'null': return None elif string == 'false': return False @@ -27,11 +27,11 @@ def miio_command_help(did=None, prefix='?'): Get Props: {prefix}[,...]\n\ {prefix}1,1-2,1-3,1-4,2-1,2-2,3\n\ Set Props: {prefix}[,...]\n\ - {prefix}2=60,2-1=#60,2-2=false,2-3=#false,3=test\n\ + {prefix}2=60,2-1=#60,2-2=false,2-3="null",3=test\n\ Do Action: {prefix} [...] \n\ {prefix}2 []\n\ {prefix}5 Hello\n\ - {prefix}5-4 Hello #1\n\n\ + {prefix}5-4 Hello 1\n\n\ Call MIoT: {prefix} \n\ {prefix}action {quote}{{"did":"{did or "267090026"}","siid":5,"aiid":1,"in":["Hello"]}}{quote}\n\n\ Call MiIO: {prefix}/ \n\ From 4232f2a974cc897f0571e3f44cda73aa14c49c5a Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 10:55:47 +0800 Subject: [PATCH 06/15] Refine version --- micli.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/micli.py b/micli.py index 2864e6d..4fafc0f 100755 --- a/micli.py +++ b/micli.py @@ -9,7 +9,7 @@ from miservice import MiAccount, MiNAService, MiIOService, miio_command, miio_command_help -MISERVICE_VERSION = '2.0.1' +MISERVICE_VERSION = '2.1.1' def usage(): print("MiService %s - XiaoMi Cloud Service\n" % MISERVICE_VERSION) @@ -22,9 +22,10 @@ def usage(): async def main(args): try: + env_get = os.environ.get + store = os.path.join(str(Path.home()), '.mi.token') async with ClientSession() as session: - env = os.environ - account = MiAccount(session, env.get('MI_USER'), env.get('MI_PASS'), os.path.join(str(Path.home()), '.mi.token')) + account = MiAccount(session, env_get('MI_USER'), env_get('MI_PASS'), store) if args.startswith('mina'): service = MiNAService(account) result = await service.device_list() @@ -32,7 +33,7 @@ async def main(args): await service.send_message(result, -1, args[4:]) else: service = MiIOService(account) - result = await miio_command(service, env.get('MI_DID'), args, sys.argv[0] + ' ') + result = await miio_command(service, env_get('MI_DID'), args, sys.argv[0] + ' ') if not isinstance(result, str): result = json.dumps(result, indent=2, ensure_ascii=False) except Exception as e: From 1338a66ead71c08f6bc16136699c7248fd585e51 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 12:44:23 +0800 Subject: [PATCH 07/15] support float --- miservice/miiocommand.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/miservice/miiocommand.py b/miservice/miiocommand.py index 44934a7..f4cac2a 100755 --- a/miservice/miiocommand.py +++ b/miservice/miiocommand.py @@ -19,7 +19,10 @@ def string_to_value(string): return True elif string.isdigit(): return int(string) - return string + try: + return float(string) + except: + return string def miio_command_help(did=None, prefix='?'): quote = '' if prefix == '?' else "'" From 059016ecf9f8fe9ebbba66d033a8f99b318ac971 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Thu, 27 Jun 2024 17:03:51 +0800 Subject: [PATCH 08/15] Update README --- README.md | 57 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 96220b4..16656db 100644 --- a/README.md +++ b/README.md @@ -25,36 +25,38 @@ MiService:XiaoMi Cloud Service ## Command Line ``` +MiService 2.1.1 - XiaoMi Cloud Service + Usage: The following variables must be set: export MI_USER= export MI_PASS= export MI_DID= -Get Props: /usr/local/bin/micli.py [,...] - /usr/local/bin/micli.py 1,1-2,1-3,1-4,2-1,2-2,3 -Set Props: /usr/local/bin/micli.py [,...] - /usr/local/bin/micli.py 2=#60,2-2=#false,3=test -Do Action: /usr/local/bin/micli.py [...] - /usr/local/bin/micli.py 2 #NA - /usr/local/bin/micli.py 5 Hello - /usr/local/bin/micli.py 5-4 Hello #1 +Get Props: ./micli.py [,...] + ./micli.py 1,1-2,1-3,1-4,2-1,2-2,3 +Set Props: ./micli.py [,...] + ./micli.py 2=60,2-1=#60,2-2=false,2-3="null",3=test +Do Action: ./micli.py [...] + ./micli.py 2 [] + ./micli.py 5 Hello + ./micli.py 5-4 Hello 1 -Call MIoT: /usr/local/bin/micli.py - /usr/local/bin/micli.py action '{"did":"267090026","siid":5,"aiid":1,"in":["Hello"]}' +Call MIoT: ./micli.py + ./micli.py action '{"did":"267090026","siid":5,"aiid":1,"in":["Hello"]}' -Call MiIO: /usr/local/bin/micli.py / - /usr/local/bin/micli.py /home/device_list '{"getVirtualModel":false,"getHuamiDevices":1}' +Call MiIO: ./micli.py / + ./micli.py /home/device_list '{"getVirtualModel":false,"getHuamiDevices":1}' -Devs List: /usr/local/bin/micli.py list [name=full|name_keyword] [getVirtualModel=false|true] [getHuamiDevices=0|1] - /usr/local/bin/micli.py list Light true 0 +Devs List: ./micli.py list [name=full|name_keyword] [getVirtualModel=false|true] [getHuamiDevices=0|1] + ./micli.py list Light true 0 -MIoT Spec: /usr/local/bin/micli.py spec [model_keyword|type_urn] [format=text|python|json] - /usr/local/bin/micli.py spec - /usr/local/bin/micli.py spec speaker - /usr/local/bin/micli.py spec xiaomi.wifispeaker.lx04 - /usr/local/bin/micli.py spec urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx04:1 +MIoT Spec: ./micli.py spec [model_keyword|type_urn] [format=text|python|json] + ./micli.py spec + ./micli.py spec speaker + ./micli.py spec xiaomi.wifispeaker.lx04 + ./micli.py spec urn:miot-spec-v2:device:speaker:0000A015:xiaomi-lx04:1 -MIoT Decode: /usr/local/bin/micli.py decode [gzip] +MIoT Decode: ./micli.py decode [gzip] ``` ## 套路,例子: @@ -103,7 +105,10 @@ micli.py 2-1 ``` micli.py 2=#60 ``` -`siid` 和 `piid` 规则同属性查询命令。注意 `#` 号的意思是整数类型,如果不带则默认是文本字符串类型,要根据接口描述文档来确定类型。 + +参数类型要根据接口描述文档来确定: +- `#`是强制文本类型,还可以用单引号`'`和双引号`"`来强制文本类型`'`(可单个引号,也可以两个); +- 如果不强制文本类型,默认将检测类型;可能的检测结果是 JSON 的 `null`、`false`、`true`、`整数`、`浮点数`或者`文本`。 ### 7. 动作调用:TTS 播报和执行文本 @@ -111,18 +116,20 @@ micli.py 2=#60 ``` micli.py 5 您好 ``` -其中,5 为 `siid`,此处省略了 `1` 的 `aiid`。 +其中,5 为 `siid`,此处省略了 `aiid`(默认为`1`)。 以下命令执行后相当于直接对对音箱说“小爱同学,查询天气”是一个效果: ``` -micli.py 5-4 查询天气 #1 +micli.py 5-4 查询天气 1 ``` -其中 `#1` 表示设备语音回应,如果要执行默默关灯(不要音箱回应),可以如下: +其中 `1` 表示设备语音回应,如果要执行默默关灯(不要音箱回应),可以如下: ``` -micli.py 5-4 关灯 #0 +micli.py 5-4 关灯 0 ``` +如果没有参数,请传入`[]`保留占位。 + ### 8. 其它应用 在扩展插件中使用,比如,参考 [ZhiMsg 小爱同学 TTS 播报/执行插件](https://github.com/Yonsm/ZhiMsg) From 3818a32ccd5d72f2295ccb14e7b3c7072c789916 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Sat, 29 Jun 2024 01:41:11 +0800 Subject: [PATCH 09/15] info to debug --- miservice/miaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miservice/miaccount.py b/miservice/miaccount.py index ecf5fcf..9781c01 100755 --- a/miservice/miaccount.py +++ b/miservice/miaccount.py @@ -111,7 +111,7 @@ async def mi_request(self, sid, url, data, headers, relogin=True): cookies = {'userId': self.token['userId'], 'serviceToken': self.token[sid][1]} content = data(self.token, cookies) if callable(data) else data method = 'GET' if data is None else 'POST' - _LOGGER.info("%s %s", url, content) + _LOGGER.debug("%s %s", url, content) async with self.session.request(method, url, data=content, cookies=cookies, headers=headers) as r: status = r.status if status == 200: From ad6be860a1a39da3fbf2fcfe1e8e629e2e963f3b Mon Sep 17 00:00:00 2001 From: Yonsm Date: Sat, 6 Jul 2024 20:57:41 +0800 Subject: [PATCH 10/15] Async token store --- miservice/miaccount.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) mode change 100755 => 100644 miservice/miaccount.py diff --git a/miservice/miaccount.py b/miservice/miaccount.py old mode 100755 new mode 100644 index 9781c01..167e513 --- a/miservice/miaccount.py +++ b/miservice/miaccount.py @@ -7,6 +7,7 @@ import string from urllib import parse from aiohttp import ClientSession +from aiofiles import open as async_open _LOGGER = logging.getLogger(__package__) @@ -20,34 +21,34 @@ class MiTokenStore: def __init__(self, token_path): self.token_path = token_path - def load_token(self): + async def load_token(self): if os.path.isfile(self.token_path): try: - with open(self.token_path) as f: - return json.load(f) - except Exception: - _LOGGER.exception("Exception on load token from %s", self.token_path) + async with async_open(self.token_path) as f: + return json.loads(await f.read()) + except Exception as e: + _LOGGER.exception("Exception on load token from %s: %s", self.token_path, e) return None - def save_token(self, token=None): + async def save_token(self, token=None): if token: try: - with open(self.token_path, 'w') as f: - json.dump(token, f, indent=2) - except Exception: - _LOGGER.exception("Exception on save token to %s", self.token_path) + async with async_open(self.token_path, 'w') as f: + await f.write(json.dumps(token, indent=2)) + except Exception as e: + _LOGGER.exception("Exception on save token to %s: %s", self.token_path, e) elif os.path.isfile(self.token_path): os.remove(self.token_path) class MiAccount: - def __init__(self, session: ClientSession, username, password, token_store=None): + def __init__(self, session: ClientSession, username, password, token_store='.mi.token'): self.session = session self.username = username self.password = password self.token_store = MiTokenStore(token_store) if isinstance(token_store, str) else token_store - self.token = token_store is not None and self.token_store.load_token() + self.token = None async def login(self, sid): if not self.token: @@ -74,13 +75,13 @@ async def login(self, sid): serviceToken = await self._securityTokenService(resp['location'], resp['nonce'], resp['ssecurity']) self.token[sid] = (resp['ssecurity'], serviceToken) if self.token_store: - self.token_store.save_token(self.token) + await self.token_store.save_token(self.token) return True except Exception as e: self.token = None if self.token_store: - self.token_store.save_token() + await self.token_store.save_token() _LOGGER.exception("Exception on login %s: %s", self.username, e) return False @@ -107,11 +108,13 @@ async def _securityTokenService(self, location, nonce, ssecurity): return serviceToken async def mi_request(self, sid, url, data, headers, relogin=True): + if self.token is None and self.token_store is not None: + self.token = await self.token_store.load_token() if (self.token and sid in self.token) or await self.login(sid): # Ensure login cookies = {'userId': self.token['userId'], 'serviceToken': self.token[sid][1]} content = data(self.token, cookies) if callable(data) else data method = 'GET' if data is None else 'POST' - _LOGGER.debug("%s %s", url, content) + _LOGGER.info("%s %s", url, content) async with self.session.request(method, url, data=content, cookies=cookies, headers=headers) as r: status = r.status if status == 200: From d97623f2448c536d6fbdf4abb7dcc66dd83beee3 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Sat, 6 Jul 2024 21:20:08 +0800 Subject: [PATCH 11/15] info to debug --- miservice/miaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miservice/miaccount.py b/miservice/miaccount.py index 167e513..3284b5a 100644 --- a/miservice/miaccount.py +++ b/miservice/miaccount.py @@ -114,7 +114,7 @@ async def mi_request(self, sid, url, data, headers, relogin=True): cookies = {'userId': self.token['userId'], 'serviceToken': self.token[sid][1]} content = data(self.token, cookies) if callable(data) else data method = 'GET' if data is None else 'POST' - _LOGGER.info("%s %s", url, content) + _LOGGER.debug("%s %s", url, content) async with self.session.request(method, url, data=content, cookies=cookies, headers=headers) as r: status = r.status if status == 200: From 909e73c980922c520b4903cf40036db04600a263 Mon Sep 17 00:00:00 2001 From: Yonsm Date: Sat, 6 Jul 2024 21:21:08 +0800 Subject: [PATCH 12/15] Update to 2.1.2 --- README.md | 4 ++-- micli.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 16656db..5e15d64 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ XiaoMi Cloud Service for mi.com ## Install ``` -pip3 install miservice +pip3 install aiohttp aiofiles miservice ``` ## Library @@ -25,7 +25,7 @@ MiService:XiaoMi Cloud Service ## Command Line ``` -MiService 2.1.1 - XiaoMi Cloud Service +MiService 2.1.2 - XiaoMi Cloud Service Usage: The following variables must be set: export MI_USER= diff --git a/micli.py b/micli.py index 4fafc0f..a44300a 100755 --- a/micli.py +++ b/micli.py @@ -9,7 +9,7 @@ from miservice import MiAccount, MiNAService, MiIOService, miio_command, miio_command_help -MISERVICE_VERSION = '2.1.1' +MISERVICE_VERSION = '2.1.2' def usage(): print("MiService %s - XiaoMi Cloud Service\n" % MISERVICE_VERSION) From 7d8627ba959c208856eb91ee22a766e14c7e9623 Mon Sep 17 00:00:00 2001 From: wanglei Date: Tue, 17 Sep 2024 13:02:29 +0800 Subject: [PATCH 13/15] =?UTF-8?q?=E4=BF=AE=E6=94=B9windows=E6=89=A7?= =?UTF-8?q?=E8=A1=8Cpip=20install=20miservice=E9=81=87=E5=88=B0=E7=9A=84?= =?UTF-8?q?=E7=BC=96=E7=A0=81bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 85d4c61..3f1ca94 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ author='Yonsm', author_email='Yonsm@qq.com', url='https://github.com/Yonsm/MiService', - long_description=Path('README.md').read_text(), + long_description=Path('README.md').read_text(encoding="utf-8"), long_description_content_type='text/markdown', packages=['miservice'], scripts=['micli.py'], From 105251fb3994c8edc5909df188e8a06f141844ae Mon Sep 17 00:00:00 2001 From: wanglei Date: Tue, 17 Sep 2024 13:03:34 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E9=80=82=E5=BA=94windows=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E4=B8=8B=E8=BF=90=E8=A1=8Cmicli.py=E9=81=87=E5=88=B0?= =?UTF-8?q?=E7=9A=84=E6=8A=A5=E9=94=99=EF=BC=8C=E5=8E=9F=E5=9B=A0=E6=98=AF?= =?UTF-8?q?windows=E4=B8=8B=E4=BA=8B=E4=BB=B6=E5=BE=AA=E7=8E=AF=E7=9A=84?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E5=AE=9E=E7=8E=B0=E5=AF=BC=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- micli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/micli.py b/micli.py index a44300a..3a7bbe9 100755 --- a/micli.py +++ b/micli.py @@ -41,6 +41,8 @@ async def main(args): print(result) if __name__ == '__main__': + if sys.platform.startswith('win'): + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) argv = sys.argv argc = len(argv) if argc > 1 and argv[1].startswith('-v'): From ef0d79173d38f1852a6b7f11f591dd817f5fae2f Mon Sep 17 00:00:00 2001 From: wanglei Date: Tue, 17 Sep 2024 13:09:25 +0800 Subject: [PATCH 15/15] Install separately to avoid errors --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e15d64..012f241 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ XiaoMi Cloud Service for mi.com ## Install ``` -pip3 install aiohttp aiofiles miservice +pip3 install aiohttp aiofiles +pip3 install miservice ``` ## Library