From babc7a477f159d1312329121fe33e7ae899f1637 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 1 Dec 2023 11:51:16 +0100 Subject: [PATCH 1/8] Fix gen property --- aioshelly/rpc_device/device.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aioshelly/rpc_device/device.py b/aioshelly/rpc_device/device.py index e3f70594..5d9073fe 100644 --- a/aioshelly/rpc_device/device.py +++ b/aioshelly/rpc_device/device.py @@ -373,8 +373,11 @@ def shelly(self) -> dict[str, Any]: @property def gen(self) -> int: - """Device generation: GEN2 - RPC.""" - return 2 + """Device generation: GEN2/3 - RPC.""" + if self._shelly is None: + raise NotInitialized + + return cast(int, self._shelly["gen"]) @property def firmware_version(self) -> str: From 700ea15bfe792862000a44c20b62e1173b3c4d09 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 1 Dec 2023 11:54:10 +0100 Subject: [PATCH 2/8] Add Plus 1 Mini --- aioshelly/const.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/aioshelly/const.py b/aioshelly/const.py index 56460d86..4f9677c2 100644 --- a/aioshelly/const.py +++ b/aioshelly/const.py @@ -97,6 +97,8 @@ MODEL_PRO_EM3 = "SPEM-003CEBEU" MODEL_PRO_EM3_400 = "SPEM-003CEBEU400" MODEL_WALL_DISPLAY = "SAWD-0A1XX10EU1" +# Gen3 RPC based models +MODEL_PLUS_1_MINI_G3 = "S3SW-001X8EU" MODEL_NAMES = { # Gen1 CoAP based models @@ -175,7 +177,6 @@ MODEL_PRO_2_V3: "Shelly Pro 2", MODEL_PRO_2PM: "Shelly Pro 2PM", MODEL_PRO_2PM_V2: "Shelly Pro 2PM", - MODEL_PRO_2PM_V2: "Shelly Pro 2PM", MODEL_PRO_3: "Shelly Pro 3", MODEL_PRO_4PM: "Shelly Pro 4PM", MODEL_PRO_4PM_V2: "Shelly Pro 4PM", @@ -184,6 +185,8 @@ MODEL_PRO_EM3: "Shelly Pro 3EM", MODEL_PRO_EM3_400: "Shelly Pro 3EM-400", MODEL_WALL_DISPLAY: "Shelly Wall Display", + # Gen3 RPC based models + MODEL_PLUS_1_MINI_G3: "Shelly Plus 1 Mini", } # Timeout used for Device IO From da3f8dcea13ebcbf8339f571dbbea4ef8e5a6481 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 1 Dec 2023 12:04:00 +0100 Subject: [PATCH 3/8] Update example.py file --- example.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/example.py b/example.py index 2baeb3bc..ceabbed6 100644 --- a/example.py +++ b/example.py @@ -39,7 +39,7 @@ async def create_device( init: bool, gen: int | None, ) -> Any: - """Create a Gen1/Gen2 device.""" + """Create a Gen1/Gen2/Gen3 device.""" if gen is None: if info := await aioshelly.common.get_info(aiohttp_session, options.ip_address): gen = info.get("gen", 1) @@ -49,7 +49,7 @@ async def create_device( if gen == 1: return await BlockDevice.create(aiohttp_session, coap_context, options, init) - if gen == 2: + if gen in (2, 3): return await RpcDevice.create(aiohttp_session, ws_context, options, init) raise ShellyError("Unknown Gen") @@ -192,7 +192,7 @@ def print_block_device(device: BlockDevice) -> None: def print_rpc_device(device: RpcDevice) -> None: - """Print RPC (GEN2) device data.""" + """Print RPC (GEN2/3) device data.""" print(f"Status: {device.status}") print(f"Event: {device.event}") print(f"Connected: {device.connected}") @@ -243,6 +243,9 @@ def get_arguments() -> tuple[argparse.ArgumentParser, argparse.Namespace]: parser.add_argument( "--gen2", "-g2", action="store_true", help="Force Gen 2 (RPC) device" ) + parser.add_argument( + "--gen3", "-g3", action="store_true", help="Force Gen 3 (RPC) device" + ) parser.add_argument( "--debug", "-deb", action="store_true", help="Enable debug level for logging" ) @@ -254,7 +257,7 @@ def get_arguments() -> tuple[argparse.ArgumentParser, argparse.Namespace]: "--update_ws", "-uw", type=str, - help="Update outbound WebSocket (Gen2) and exit", + help="Update outbound WebSocket (Gen2/3) and exit", ) arguments = parser.parse_args() @@ -265,7 +268,7 @@ def get_arguments() -> tuple[argparse.ArgumentParser, argparse.Namespace]: async def update_outbound_ws( options: ConnectionOptions, init: bool, ws_url: str ) -> None: - """Update outbound WebSocket URL (Gen2).""" + """Update outbound WebSocket URL (Gen2/3).""" async with aiohttp.ClientSession() as aiohttp_session: device: RpcDevice = await create_device(aiohttp_session, options, init, 2) print(f"Updating outbound weboskcet URL to {ws_url}") @@ -281,12 +284,18 @@ async def main() -> None: if args.gen1 and args.gen2: parser.error("--gen1 and --gen2 can't be used together") + elif args.gen1 and args.gen3: + parser.error("--gen1 and --gen3 can't be used together") + elif args.gen2 and args.gen3: + parser.error("--gen2 and --gen3 can't be used together") gen = None if args.gen1: gen = 1 elif args.gen2: gen = 2 + elif args.gen3: + gen = 3 if args.debug: logging.basicConfig(level="DEBUG", force=True) From 1e5e1578a19f2e8db39c7f2ed7dca3b493e09dbc Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 1 Dec 2023 15:16:04 +0100 Subject: [PATCH 4/8] Add another models --- aioshelly/const.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aioshelly/const.py b/aioshelly/const.py index 4f9677c2..c92b9d95 100644 --- a/aioshelly/const.py +++ b/aioshelly/const.py @@ -99,6 +99,8 @@ MODEL_WALL_DISPLAY = "SAWD-0A1XX10EU1" # Gen3 RPC based models MODEL_PLUS_1_MINI_G3 = "S3SW-001X8EU" +MODEL_PLUS_1PM_MINI_G3 = "S3SW-001P8EU" +MODEL_PLUS_PM_MINI_G3 = "S3PM-001PCEU16" MODEL_NAMES = { # Gen1 CoAP based models @@ -187,6 +189,8 @@ MODEL_WALL_DISPLAY: "Shelly Wall Display", # Gen3 RPC based models MODEL_PLUS_1_MINI_G3: "Shelly Plus 1 Mini", + MODEL_PLUS_1PM_MINI_G3: "Shelly Plus 1PM Mini", + MODEL_PLUS_PM_MINI_G3: "Shelly Plus PM Mini", } # Timeout used for Device IO From 7c2780622c5793a24470bf4fdf7bc35982b79913 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 1 Dec 2023 15:25:41 +0100 Subject: [PATCH 5/8] Add const GEN3_MIN_FIRMWARE_DATE and minimum firmware check --- aioshelly/common.py | 7 +++++-- aioshelly/const.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/aioshelly/common.py b/aioshelly/common.py index 916a4dee..1d1bc58b 100644 --- a/aioshelly/common.py +++ b/aioshelly/common.py @@ -8,15 +8,16 @@ from dataclasses import dataclass from socket import gethostbyname from typing import Any, Union -from yarl import URL import aiohttp +from yarl import URL from .const import ( CONNECT_ERRORS, DEVICE_IO_TIMEOUT, GEN1_MIN_FIRMWARE_DATE, GEN2_MIN_FIRMWARE_DATE, + GEN3_MIN_FIRMWARE_DATE, ) from .exceptions import ( DeviceConnectionError, @@ -119,7 +120,9 @@ def shelly_supported_firmware(result: dict[str, Any]) -> bool: fw_ver = GEN1_MIN_FIRMWARE_DATE else: fw_str = result["fw_id"] - fw_ver = GEN2_MIN_FIRMWARE_DATE + fw_ver = ( + GEN2_MIN_FIRMWARE_DATE if result["gen"] == 2 else GEN3_MIN_FIRMWARE_DATE + ) match = FIRMWARE_PATTERN.search(fw_str) diff --git a/aioshelly/const.py b/aioshelly/const.py index c92b9d95..5dc5115c 100644 --- a/aioshelly/const.py +++ b/aioshelly/const.py @@ -205,6 +205,9 @@ # Firmware 0.8.1 release date GEN2_MIN_FIRMWARE_DATE = 20210921 +# Firmware 1.0.99 release date +GEN3_MIN_FIRMWARE_DATE = 20231102 + WS_HEARTBEAT = 55 # Default Gen2 outbound websocket API URL From e5c904769c2f8bccf2c7ed85607db5332df4598c Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sat, 2 Dec 2023 11:36:16 +0100 Subject: [PATCH 6/8] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6835f94a..f2b8e696 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ if __name__ == "__main__": asyncio.run(test_block_device()) ``` -### Gen2 (RPC/WebSocket) device example: +### Gen2 and Gen3 (RPC/WebSocket) device example: ```python import asyncio @@ -85,7 +85,7 @@ from aioshelly.rpc_device import RpcDevice, WsServer async def test_rpc_device(): - """Test Gen2 RPC (WebSocket) based device.""" + """Test Gen2/Gen3 RPC (WebSocket) based device.""" options = ConnectionOptions("192.168.1.188", "username", "password") ws_context = WsServer() await ws_context.initialize(8123) From a928f408ecaf6f5ed1b5c3199d89ff2e8de6bb0a Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 3 Dec 2023 14:17:27 +0100 Subject: [PATCH 7/8] Add generations constants --- aioshelly/const.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aioshelly/const.py b/aioshelly/const.py index 5dc5115c..8c950f58 100644 --- a/aioshelly/const.py +++ b/aioshelly/const.py @@ -215,3 +215,6 @@ # Notification sent by RPC device in case of WebSocket close NOTIFY_WS_CLOSED = "NotifyWebSocketClosed" + +BLOCK_GENERATIONS = (1,) +RPC_GENERATIONS = (2, 3) From e11c726f5f64890a7dc7d53e8ce6702ea6b2438f Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Sun, 3 Dec 2023 14:33:19 +0100 Subject: [PATCH 8/8] Use constants in example.py --- example.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example.py b/example.py index ceabbed6..a4ba0bb0 100644 --- a/example.py +++ b/example.py @@ -18,7 +18,7 @@ import aioshelly from aioshelly.block_device import BLOCK_VALUE_UNIT, COAP, BlockDevice, BlockUpdateType from aioshelly.common import ConnectionOptions -from aioshelly.const import MODEL_NAMES, WS_API_URL +from aioshelly.const import BLOCK_GENERATIONS, MODEL_NAMES, RPC_GENERATIONS, WS_API_URL from aioshelly.exceptions import ( DeviceConnectionError, FirmwareUnsupported, @@ -46,10 +46,10 @@ async def create_device( else: raise ShellyError("Unknown Gen") - if gen == 1: + if gen in BLOCK_GENERATIONS: return await BlockDevice.create(aiohttp_session, coap_context, options, init) - if gen in (2, 3): + if gen in RPC_GENERATIONS: return await RpcDevice.create(aiohttp_session, ws_context, options, init) raise ShellyError("Unknown Gen") @@ -164,9 +164,9 @@ def print_device(device: BlockDevice | RpcDevice) -> None: print(f"** {device.name} - {model_name} @ {device.ip_address} **") print() - if device.gen == 1: + if device.gen in BLOCK_GENERATIONS: print_block_device(cast(BlockDevice, device)) - elif device.gen == 2: + elif device.gen == RPC_GENERATIONS: print_rpc_device(cast(RpcDevice, device))