From 5769b85528eca2fe97202ee3beab18007de04f1f Mon Sep 17 00:00:00 2001 From: xiaodong Date: Wed, 7 Aug 2024 01:28:35 +0800 Subject: [PATCH] add `telnet_password` for `ShellE1` and `ShellMGW2` --- README.md | 1 + .../xiaomi_gateway3/config_flow.py | 3 +++ .../xiaomi_gateway3/core/core_utils.py | 17 ++++++++--------- .../xiaomi_gateway3/core/gate/openmiio.py | 2 +- .../xiaomi_gateway3/core/gate/silabs.py | 4 ++-- .../xiaomi_gateway3/core/gateway.py | 6 +++--- .../xiaomi_gateway3/core/shell/base.py | 3 ++- .../xiaomi_gateway3/core/shell/session.py | 9 +++++---- .../xiaomi_gateway3/core/shell/shell_e1.py | 4 +++- .../xiaomi_gateway3/translations/en.json | 3 ++- .../xiaomi_gateway3/translations/zh-Hans.json | 3 ++- .../xiaomi_gateway3/translations/zh-Hant.json | 3 ++- 12 files changed, 34 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index abfee81b..2e263e63 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,7 @@ Only for Xiaomi Multimode Gateway 1: - **Host** - gateway IP-address, should be fixed on your Wi-Fi router - **Token** - gateway Mi Home token, changed only when you add gateway to Mi Home app - **Key** - gateway secret key, [read more](https://github.com/AlexxIT/Blog/issues/13) +- **Telnet Password** - gateway(`Aqara_Hub_E1` or `Mijia_Hub_V2`) telnet password. Password will setup while adding integration(keep field empty if you don't want setup password), or you need change both integration config and password in telnet(`passwd` or `chpasswd`) later. - **Add statistic sensors** - [read more](#statistics-table) - **Debug logs** - enable different levels of logging ([read more](#debug-mode)) diff --git a/custom_components/xiaomi_gateway3/config_flow.py b/custom_components/xiaomi_gateway3/config_flow.py index 4780eea7..456801fe 100644 --- a/custom_components/xiaomi_gateway3/config_flow.py +++ b/custom_components/xiaomi_gateway3/config_flow.py @@ -51,6 +51,7 @@ async def async_step_user(self, user_input: dict = None): vol.Required("host", default=device["localip"]): str, vol.Required("token", default=device["token"]): str, vol.Optional("key"): str, + vol.Optional("telnet_password"): str, } ), ) @@ -121,6 +122,7 @@ async def async_step_token(self, user_input: dict = None): vol.Required("host"): str, vol.Required("token"): str, vol.Optional("key"): str, + vol.Optional("telnet_password"): str, }, user_input, ) @@ -189,6 +191,7 @@ async def async_step_user(self, user_input: dict = None): vol.Required("host"): str, vol.Required("token"): str, vol.Optional("key"): str, + vol.Optional("telnet_password"): str, vol.Optional("stats"): vol.In( { False: "Disabled", # for backward compatibility diff --git a/custom_components/xiaomi_gateway3/core/core_utils.py b/custom_components/xiaomi_gateway3/core/core_utils.py index eadf93f3..9f756e91 100644 --- a/custom_components/xiaomi_gateway3/core/core_utils.py +++ b/custom_components/xiaomi_gateway3/core/core_utils.py @@ -23,14 +23,14 @@ async def check_port(host: str, port: int) -> bool: s.close() -async def gateway_info(host: str, token: str = None, key: str = None) -> dict | None: +async def gateway_info(host: str, token: str = None, key: str = None, telnet_password: str = None) -> dict | None: # Strategy: # 1. Check open telnet and return host, did, token, key # 2. Try to enable telnet using host, token and (optionaly) key # 3. Check open telnet again # 4. Return error try: - async with Session(host) as sh: + async with Session(host, telnet_password = telnet_password) as sh: info = await sh.get_miio_info() info["host"] = host return info @@ -41,13 +41,13 @@ async def gateway_info(host: str, token: str = None, key: str = None) -> dict | return None # try to enable telnet and return miio info - result = await enable_telnet(host, token, key) + result = await enable_telnet(host, token, key, telnet_password) # waiting for telnet to start await asyncio.sleep(1) # call with empty token so only telnet will check - if info := await gateway_info(host): + if info := await gateway_info(host, telnet_password = telnet_password): return info # result ok, but telnet can't be opened @@ -55,10 +55,9 @@ async def gateway_info(host: str, token: str = None, key: str = None) -> dict | # universal command for open telnet on all models -TELNET_CMD = "passwd -d $USER; riu_w 101e 53 3012 || echo enable > /sys/class/tty/tty/enable; telnetd" +TELNET_CMD = 'echo "$USER:%s" | chpasswd; riu_w 101e 53 3012 || echo enable > /sys/class/tty/tty/enable; telnetd;' - -async def enable_telnet(host: str, token: str, key: str = None) -> str: +async def enable_telnet(host: str, token: str, key: str = None, telnet_password: str = None) -> str: # Strategy: # 1. Get miio info miio = AsyncMiIO(host, token) @@ -92,11 +91,11 @@ async def enable_telnet(host: str, token: str, key: str = None) -> str: if method == "enable_telnet_service": params = None elif method == "set_ip_info": - params = {"ssid": '""', "pswd": "1; " + TELNET_CMD} + params = {"ssid": '""', "pswd": "1; " + TELNET_CMD % (telnet_password or "")} elif method == "system_command": params = { "password": miio_password(miio.device_id, miio_info["mac"], key), - "command": TELNET_CMD, + "command": TELNET_CMD % (telnet_password or ""), } else: raise NotImplementedError(method) diff --git a/custom_components/xiaomi_gateway3/core/gate/openmiio.py b/custom_components/xiaomi_gateway3/core/gate/openmiio.py index 7cb4c506..bd160155 100644 --- a/custom_components/xiaomi_gateway3/core/gate/openmiio.py +++ b/custom_components/xiaomi_gateway3/core/gate/openmiio.py @@ -83,7 +83,7 @@ def openmiio_on_timer(self, ts: float): async def openmiio_restart(self): try: - async with Session(self.host) as sh: + async with Session(self.host, telnet_password = self.options["telnet_password"]) as sh: if await sh.only_one(): await self.openmiio_prepare_gateway(sh) except Exception as e: diff --git a/custom_components/xiaomi_gateway3/core/gate/silabs.py b/custom_components/xiaomi_gateway3/core/gate/silabs.py index cf659eeb..b059ab7d 100644 --- a/custom_components/xiaomi_gateway3/core/gate/silabs.py +++ b/custom_components/xiaomi_gateway3/core/gate/silabs.py @@ -161,7 +161,7 @@ async def silabs_process_unknown(self, uid: str): async def silabs_process_join(self, data: dict): self.debug("silabs_process_join", data=data) try: - async with Session(self.host) as sh: + async with Session(self.host, telnet_password = self.options["telnet_password"]) as sh: # check if model should be prevented from unpairing if self.force_pair or not data["model"].startswith(("lumi.", "ikea.")): self.force_pair = False @@ -287,7 +287,7 @@ async def silabs_process_neighbors(self, device: XDevice, data: dict): async def silabs_restart(self): try: - async with Session(self.host) as sh: + async with Session(self.host, telnet_password = self.options["telnet_password"]) as sh: # names for all supported gateway models await sh.exec("killall Lumi_Z3GatewayHost_MQTT mZ3GatewayHost_MQTT") except Exception as e: diff --git a/custom_components/xiaomi_gateway3/core/gateway.py b/custom_components/xiaomi_gateway3/core/gateway.py index 1d642075..5a1ac614 100644 --- a/custom_components/xiaomi_gateway3/core/gateway.py +++ b/custom_components/xiaomi_gateway3/core/gateway.py @@ -63,7 +63,7 @@ async def enable_telnet(self) -> bool: return False try: resp = await core_utils.enable_telnet( - self.host, token, self.options.get("key") + self.host, token, self.options.get("key"), self.options.get("telnet_password") ) self.debug("enable_telnet", data=resp) return resp == "ok" @@ -73,7 +73,7 @@ async def enable_telnet(self) -> bool: async def prepare_gateway(self) -> bool: try: - async with Session(self.host) as sh: + async with Session(self.host, telnet_password = self.options["telnet_password"]) as sh: if not await sh.only_one(): self.debug("Connection from a second Hass detected") return False @@ -167,7 +167,7 @@ async def send(self, device: XDevice, data: dict): async def telnet_command(self, cmd: str) -> bool | None: self.debug("telnet_command", data=cmd) try: - async with Session(self.host) as sh: + async with Session(self.host, telnet_password = self.options["telnet_password"]) as sh: if cmd == "run_ftp": await sh.run_ftp() return True diff --git a/custom_components/xiaomi_gateway3/core/shell/base.py b/custom_components/xiaomi_gateway3/core/shell/base.py index cfa9cdda..eb499305 100644 --- a/custom_components/xiaomi_gateway3/core/shell/base.py +++ b/custom_components/xiaomi_gateway3/core/shell/base.py @@ -5,9 +5,10 @@ class ShellBase: - def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, telnet_password: str = None): self.reader = reader self.writer = writer + self.telnet_password = telnet_password async def close(self): if not self.writer: diff --git a/custom_components/xiaomi_gateway3/core/shell/session.py b/custom_components/xiaomi_gateway3/core/shell/session.py index 3fbd142c..a9870e5b 100644 --- a/custom_components/xiaomi_gateway3/core/shell/session.py +++ b/custom_components/xiaomi_gateway3/core/shell/session.py @@ -8,8 +8,10 @@ class Session: reader: asyncio.StreamReader writer: asyncio.StreamWriter + telnet_password: str - def __init__(self, host: str, port=23): + def __init__(self, host: str, port=23, telnet_password: str = None): + self.telnet_password = telnet_password self.coro = asyncio.open_connection(host, port, limit=1_000_000) async def __aenter__(self): @@ -29,13 +31,12 @@ async def close(self): async def login(self) -> ShellMGW | ShellE1 | ShellMGW2: coro = self.reader.readuntil(b"login: ") resp: bytes = await asyncio.wait_for(coro, 3) - if b"rlxlinux" in resp: shell = ShellMGW(self.reader, self.writer) elif b"Aqara-Hub-E1" in resp or b"Aqara_Hub_E1" in resp: - shell = ShellE1(self.reader, self.writer) + shell = ShellE1(self.reader, self.writer, telnet_password = self.telnet_password) elif b"Mijia_Hub_V2" in resp: - shell = ShellMGW2(self.reader, self.writer) + shell = ShellMGW2(self.reader, self.writer, telnet_password = self.telnet_password) else: raise Exception(f"Unknown response: {resp}") diff --git a/custom_components/xiaomi_gateway3/core/shell/shell_e1.py b/custom_components/xiaomi_gateway3/core/shell/shell_e1.py index e3e0e026..d8aa408b 100644 --- a/custom_components/xiaomi_gateway3/core/shell/shell_e1.py +++ b/custom_components/xiaomi_gateway3/core/shell/shell_e1.py @@ -9,7 +9,9 @@ class ShellE1(ShellBase): async def login(self): self.writer.write(b"root\n") await asyncio.sleep(0.1) - self.writer.write(b"\n") # empty password + if self.telnet_password: + self.writer.write(str.encode(self.telnet_password)) + self.writer.write(b"\n") coro = self.reader.readuntil(b" # ") await asyncio.wait_for(coro, timeout=3) diff --git a/custom_components/xiaomi_gateway3/translations/en.json b/custom_components/xiaomi_gateway3/translations/en.json index 61d4d5e2..2e0c761b 100644 --- a/custom_components/xiaomi_gateway3/translations/en.json +++ b/custom_components/xiaomi_gateway3/translations/en.json @@ -31,7 +31,8 @@ "data": { "host": "Host", "token": "Token", - "key": "Key" + "key": "Key", + "telnet_password": "Telnet Password" } } } diff --git a/custom_components/xiaomi_gateway3/translations/zh-Hans.json b/custom_components/xiaomi_gateway3/translations/zh-Hans.json index 31da69e3..f52a7685 100644 --- a/custom_components/xiaomi_gateway3/translations/zh-Hans.json +++ b/custom_components/xiaomi_gateway3/translations/zh-Hans.json @@ -30,7 +30,8 @@ "data": { "host": "网关IP", "token": "Token", - "key": "Key" + "key": "Key", + "telnet_password": "Telnet密码" } } } diff --git a/custom_components/xiaomi_gateway3/translations/zh-Hant.json b/custom_components/xiaomi_gateway3/translations/zh-Hant.json index b4741add..d9239626 100644 --- a/custom_components/xiaomi_gateway3/translations/zh-Hant.json +++ b/custom_components/xiaomi_gateway3/translations/zh-Hant.json @@ -31,7 +31,8 @@ "data": { "host": "網關IP", "token": "Token", - "key": "Key" + "key": "Key", + "telnet_password": "Telnet密碼" } } }