From 001738e04d1a32e1c4e8e126f7d0f8ff568d230d Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Wed, 6 Dec 2023 16:58:53 +0000 Subject: [PATCH 01/11] Add .devcontainer file --- .devcontainer/devcontainer.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..244f3e6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pip3 install -e ." + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From ea25caf3e5b4e0e120fd29684ca1e0a320b5ac88 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Wed, 6 Dec 2023 16:59:28 +0000 Subject: [PATCH 02/11] Add "async_timeout" to "install_requires" --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7ecc5be..300b234 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ "click", "aiohttp", "setuptools", + "async_timeout" ], packages=find_packages(), python_requires=">=3.9", From c4a78f84da762ee4c1eb45ece5fb9337ecbed885 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Wed, 6 Dec 2023 18:13:09 +0000 Subject: [PATCH 03/11] Move base class out of init and add inheritance --- pymystrom/__init__.py | 106 ------------------------------------------ pymystrom/bulb.py | 43 ++++++++--------- pymystrom/device.py | 106 ++++++++++++++++++++++++++++++++++++++++++ pymystrom/pir.py | 39 ++++++---------- pymystrom/switch.py | 37 +++++---------- 5 files changed, 150 insertions(+), 181 deletions(-) create mode 100644 pymystrom/device.py diff --git a/pymystrom/__init__.py b/pymystrom/__init__.py index 80cc054..fbae3f5 100644 --- a/pymystrom/__init__.py +++ b/pymystrom/__init__.py @@ -1,107 +1 @@ """Base details for the myStrom Python bindings.""" -import asyncio -import aiohttp -import async_timeout -from yarl import URL -from typing import Any, Mapping, Optional -import socket -from .exceptions import MyStromConnectionError - -import pkg_resources - -__version__ = pkg_resources.get_distribution("setuptools").version - -TIMEOUT = 10 -USER_AGENT = f"PythonMyStrom/{__version__}" - - -async def _request( - self, - uri: str, - method: str = "GET", - data: Optional[Any] = None, - json_data: Optional[dict] = None, - params: Optional[Mapping[str, str]] = None, -) -> Any: - """Handle a request to the myStrom device.""" - headers = { - "User-Agent": USER_AGENT, - "Accept": "application/json, text/plain, */*", - } - - if self._session is None: - self._session = aiohttp.ClientSession() - self._close_session = True - - try: - with async_timeout.timeout(TIMEOUT): - response = await self._session.request( - method, - uri, - data=data, - json=json_data, - params=params, - headers=headers, - ) - except asyncio.TimeoutError as exception: - raise MyStromConnectionError( - "Timeout occurred while connecting to myStrom device." - ) from exception - except (aiohttp.ClientError, socket.gaierror) as exception: - raise MyStromConnectionError( - "Error occurred while communicating with myStrom device." - ) from exception - - content_type = response.headers.get("Content-Type", "") - if (response.status // 100) in [4, 5]: - response.close() - - if "application/json" in content_type: - response_json = await response.json() - return response_json - - return response.text - - -class MyStromDevice: - """A class for a myStrom device.""" - - def __init__( - self, - host, - session: aiohttp.client.ClientSession = None, - ): - """Initialize the device.""" - self._close_session = False - self._host = host - self._session = session - self.uri = URL.build(scheme="http", host=self._host) - - async def get_device_info(self) -> dict: - """Get the device info of a myStrom device.""" - url = URL(self.uri).join(URL("api/v1/info")) - response = await _request(self, uri=url) - if not isinstance(response, dict): - # Fall back to the old API version if the device runs with old firmware - url = URL(self.uri).join(URL("info.json")) - response = await _request(self, uri=url) - return response - - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - - async def __aenter__(self) -> "MyStromDevice": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() - - -async def get_device_info(host: str) -> dict: - """Get the device info of a myStrom device.""" - async with MyStromDevice(host) as device: - return await device.get_device_info() diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index 93aab20..f668dc9 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -6,14 +6,15 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice _LOGGER = logging.getLogger(__name__) -URI_BULB = URL("api/v1/device") +API_PREFIX = URL("api/v1/device") -class MyStromBulb: +class MyStromBulb(MyStromDevice): """A class for a myStrom bulb.""" def __init__( @@ -23,10 +24,8 @@ def __init__( session: aiohttp.client.ClientSession = None, ): """Initialize the bulb.""" - self._close_session = False - self._host = host + super().__init__(host, session) self._mac = mac - self._session = session self.brightness = 0 self._color = None self._consumption = 0 @@ -36,7 +35,7 @@ def __init__( self._bulb_type = None self._state = None self._transition_time = 0 - self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac + # self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac async def get_state(self) -> object: """Get the state of the bulb.""" @@ -91,8 +90,9 @@ def state(self) -> Optional[str]: async def set_on(self): """Turn the bulb on with the previous settings.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"action": "on"} + self, url, method="POST", data={"action": "on"} ) return response @@ -104,11 +104,12 @@ async def set_color_hex(self, value): green: 0000FF00 blue: 000000FF """ + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = { "action": "on", "color": value, } - response = await request(self, uri=self.uri, method="POST", data=data) + response = await request(self, url, method="POST", data=data) return response async def set_color_hsv(self, hue, saturation, value): @@ -120,8 +121,9 @@ async def set_color_hsv(self, hue, saturation, value): # 'action': 'on', # 'color': f"{hue};{saturation};{value}", # } + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = "action=on&color={};{};{}".format(hue, saturation, value) - response = await request(self, uri=self.uri, method="POST", data=data) + response = await request(self, url, method="POST", data=data) return response async def set_white(self): @@ -139,11 +141,12 @@ async def set_sunrise(self, duration): The brightness is from 0 till 100. """ + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) max_brightness = 100 await self.set_transition_time((duration / max_brightness)) for i in range(0, duration): data = "action=on&color=3;{}".format(i) - await request(self, uri=self.uri, method="POST", data=data) + await request(self, url, method="POST", data=data) await asyncio.sleep(duration / max_brightness) async def set_flashing(self, duration, hsv1, hsv2): @@ -157,27 +160,19 @@ async def set_flashing(self, duration, hsv1, hsv2): async def set_transition_time(self, value): """Set the transition time in ms.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"ramp": int(round(value))} + self, url, method="POST", data={"ramp": int(round(value))} ) return response async def set_off(self): """Turn the bulb off.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"action": "off"} + self, url, method="POST", data={"action": "off"} ) return response - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - async def __aenter__(self) -> "MyStromBulb": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + super().__aenter__() diff --git a/pymystrom/device.py b/pymystrom/device.py new file mode 100644 index 0000000..99601e0 --- /dev/null +++ b/pymystrom/device.py @@ -0,0 +1,106 @@ +"""Base device class for all myStrom devices.""" +from typing import Any, Mapping, Optional +import asyncio +import aiohttp +import pkg_resources +import socket +import async_timeout +from yarl import URL +from .exceptions import MyStromConnectionError + +__version__ = pkg_resources.get_distribution("setuptools").version + +TIMEOUT = 10 +USER_AGENT = f"PythonMyStrom/{__version__}" + + +async def _request( + self, + uri: str, + method: str = "GET", + data: Optional[Any] = None, + json_data: Optional[dict] = None, + params: Optional[Mapping[str, str]] = None, +) -> Any: + """Handle a request to the myStrom device.""" + headers = { + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + } + + if self._session is None: + self._session = aiohttp.ClientSession() + self._close_session = True + + try: + with async_timeout.timeout(TIMEOUT): + response = await self._session.request( + method, + uri, + data=data, + json=json_data, + params=params, + headers=headers, + ) + except asyncio.TimeoutError as exception: + raise MyStromConnectionError( + "Timeout occurred while connecting to myStrom device." + ) from exception + except (aiohttp.ClientError, socket.gaierror) as exception: + raise MyStromConnectionError( + "Error occurred while communicating with myStrom device." + ) from exception + + content_type = response.headers.get("Content-Type", "") + if (response.status // 100) in [4, 5]: + response.close() + + if "application/json" in content_type: + response_json = await response.json() + return response_json + + return response.text + +class MyStromDevice: + """A class for a myStrom device.""" + + def __init__( + self, + host, + session: aiohttp.client.ClientSession = None, + ): + """Initialize the device.""" + self._close_session = False + self._host = host + self._session = session + self.uri = URL.build(scheme="http", host=self._host) + + async def get_device_info(self) -> dict: + """Get the device info of a myStrom device.""" + url = URL(self.uri).join(URL("api/v1/info")) + print(url) + response = await _request(self, uri=url) + if not isinstance(response, dict): + # Fall back to the old API version if the device runs with old firmware + url = URL(self.uri).join(URL("info.json")) + response = await _request(self, uri=url) + return response + + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + + async def __aenter__(self) -> "MyStromDevice": + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() + + +async def get_device_info(host: str) -> dict: + """Get the device info of a myStrom device.""" + async with MyStromDevice(host) as device: + return await device.get_device_info() diff --git a/pymystrom/pir.py b/pymystrom/pir.py index de66b29..9dcd858 100644 --- a/pymystrom/pir.py +++ b/pymystrom/pir.py @@ -3,19 +3,17 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice -URI_PIR = URL("api/v1/") +API_PREFIX = "api/v1" - -class MyStromPir: +class MyStromPir(MyStromDevice): """A class for a myStrom PIR.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: - """Initialize the switch.""" - self._close_session = False - self._host = host - self._session = session + """Initialize the PIR.""" + super().__init__(host, session) self._intensity = None self._day = None self._light_raw = None @@ -29,29 +27,28 @@ def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> N self._pir = None self._actions = None - self.uri = URL.build(scheme="http", host=self._host).join(URI_PIR) async def get_settings(self) -> None: """Get the current settings from the PIR.""" - url = URL(self.uri).join(URL("settings")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/settings")) response = await request(self, uri=url) self._settings = response async def get_actions(self) -> None: """Get the current action settings from the PIR.""" - url = URL(self.uri).join(URL("action")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/action")) response = await request(self, uri=url) self._actions = response async def get_pir(self) -> None: """Get the current PIR settings.""" - url = URL(self.uri).join(URL("settings/pir")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/settings/pir")) response = await request(self, uri=url) self._pir = response async def get_sensors_state(self) -> None: """Get the state of the sensors from the PIR.""" - url = URL(self.uri).join(URL("sensors")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/sensors")) response = await request(self, uri=url) # The return data has the be re-written as the temperature is not rounded self._sensors = { @@ -72,13 +69,13 @@ async def get_temperatures(self) -> None: async def get_motion(self) -> None: """Get the state of the motion sensor from the PIR.""" - url = URL(self.uri).join(URL("motion")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/motion")) response = await request(self, uri=url) self._motion = response["motion"] async def get_light(self) -> None: """Get the state of the light sensor from the PIR.""" - url = URL(self.uri).join(URL("light")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/light")) response = await request(self, uri=url) self._intensity = response["intensity"] self._day = response["day"] @@ -147,15 +144,5 @@ def light_raw(self) -> Optional[str]: "infrared": self._light_raw["adc1"], } - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - async def __aenter__(self) -> "MyStromPir": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + super().__aenter__() diff --git a/pymystrom/switch.py b/pymystrom/switch.py index a8d80db..14fc7f8 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -3,24 +3,21 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice - -class MyStromSwitch: +class MyStromSwitch(MyStromDevice): """A class for a myStrom switch/plug.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: """Initialize the switch.""" - self._close_session = False - self._host = host - self._session = session + super().__init__(host, session) self._consumption = 0 self._consumedWs = 0 self._state = None self._temperature = None self._firmware = None self._mac = None - self.uri = URL.build(scheme="http", host=self._host) async def turn_on(self) -> None: """Turn the relay on.""" @@ -71,6 +68,12 @@ async def get_state(self) -> None: self._firmware = response["version"] self._mac = response["mac"] + async def get_temperature_full(self) -> str: + """Get current temperature in celsius.""" + url = URL(self.uri).join(URL("temp")) + response = await request(self, uri=url) + return response + @property def relay(self) -> bool: """Return the relay state.""" @@ -110,21 +113,5 @@ def temperature(self) -> float: return self._temperature - async def get_temperature_full(self) -> str: - """Get current temperature in celsius.""" - url = URL(self.uri).join(URL("temp")) - response = await request(self, uri=url) - return response - - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - - async def __aenter__(self) -> "MyStromSwitch": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + async def __aenter__(self) -> "MyStromPir": + super().__aenter__() From 91aeec4291cde37f2a9fa98b1acd863d40647da4 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Wed, 6 Dec 2023 18:15:09 +0000 Subject: [PATCH 04/11] Revert "Move base class out of init and add inheritance" This reverts commit c4a78f84da762ee4c1eb45ece5fb9337ecbed885. --- pymystrom/__init__.py | 106 ++++++++++++++++++++++++++++++++++++++++++ pymystrom/bulb.py | 43 +++++++++-------- pymystrom/device.py | 106 ------------------------------------------ pymystrom/pir.py | 39 ++++++++++------ pymystrom/switch.py | 37 ++++++++++----- 5 files changed, 181 insertions(+), 150 deletions(-) delete mode 100644 pymystrom/device.py diff --git a/pymystrom/__init__.py b/pymystrom/__init__.py index fbae3f5..80cc054 100644 --- a/pymystrom/__init__.py +++ b/pymystrom/__init__.py @@ -1 +1,107 @@ """Base details for the myStrom Python bindings.""" +import asyncio +import aiohttp +import async_timeout +from yarl import URL +from typing import Any, Mapping, Optional +import socket +from .exceptions import MyStromConnectionError + +import pkg_resources + +__version__ = pkg_resources.get_distribution("setuptools").version + +TIMEOUT = 10 +USER_AGENT = f"PythonMyStrom/{__version__}" + + +async def _request( + self, + uri: str, + method: str = "GET", + data: Optional[Any] = None, + json_data: Optional[dict] = None, + params: Optional[Mapping[str, str]] = None, +) -> Any: + """Handle a request to the myStrom device.""" + headers = { + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + } + + if self._session is None: + self._session = aiohttp.ClientSession() + self._close_session = True + + try: + with async_timeout.timeout(TIMEOUT): + response = await self._session.request( + method, + uri, + data=data, + json=json_data, + params=params, + headers=headers, + ) + except asyncio.TimeoutError as exception: + raise MyStromConnectionError( + "Timeout occurred while connecting to myStrom device." + ) from exception + except (aiohttp.ClientError, socket.gaierror) as exception: + raise MyStromConnectionError( + "Error occurred while communicating with myStrom device." + ) from exception + + content_type = response.headers.get("Content-Type", "") + if (response.status // 100) in [4, 5]: + response.close() + + if "application/json" in content_type: + response_json = await response.json() + return response_json + + return response.text + + +class MyStromDevice: + """A class for a myStrom device.""" + + def __init__( + self, + host, + session: aiohttp.client.ClientSession = None, + ): + """Initialize the device.""" + self._close_session = False + self._host = host + self._session = session + self.uri = URL.build(scheme="http", host=self._host) + + async def get_device_info(self) -> dict: + """Get the device info of a myStrom device.""" + url = URL(self.uri).join(URL("api/v1/info")) + response = await _request(self, uri=url) + if not isinstance(response, dict): + # Fall back to the old API version if the device runs with old firmware + url = URL(self.uri).join(URL("info.json")) + response = await _request(self, uri=url) + return response + + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + + async def __aenter__(self) -> "MyStromDevice": + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() + + +async def get_device_info(host: str) -> dict: + """Get the device info of a myStrom device.""" + async with MyStromDevice(host) as device: + return await device.get_device_info() diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index f668dc9..93aab20 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -6,15 +6,14 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from .device import _request as request -from .device import MyStromDevice +from . import _request as request _LOGGER = logging.getLogger(__name__) -API_PREFIX = URL("api/v1/device") +URI_BULB = URL("api/v1/device") -class MyStromBulb(MyStromDevice): +class MyStromBulb: """A class for a myStrom bulb.""" def __init__( @@ -24,8 +23,10 @@ def __init__( session: aiohttp.client.ClientSession = None, ): """Initialize the bulb.""" - super().__init__(host, session) + self._close_session = False + self._host = host self._mac = mac + self._session = session self.brightness = 0 self._color = None self._consumption = 0 @@ -35,7 +36,7 @@ def __init__( self._bulb_type = None self._state = None self._transition_time = 0 - # self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac + self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac async def get_state(self) -> object: """Get the state of the bulb.""" @@ -90,9 +91,8 @@ def state(self) -> Optional[str]: async def set_on(self): """Turn the bulb on with the previous settings.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, url, method="POST", data={"action": "on"} + self, uri=self.uri, method="POST", data={"action": "on"} ) return response @@ -104,12 +104,11 @@ async def set_color_hex(self, value): green: 0000FF00 blue: 000000FF """ - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = { "action": "on", "color": value, } - response = await request(self, url, method="POST", data=data) + response = await request(self, uri=self.uri, method="POST", data=data) return response async def set_color_hsv(self, hue, saturation, value): @@ -121,9 +120,8 @@ async def set_color_hsv(self, hue, saturation, value): # 'action': 'on', # 'color': f"{hue};{saturation};{value}", # } - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = "action=on&color={};{};{}".format(hue, saturation, value) - response = await request(self, url, method="POST", data=data) + response = await request(self, uri=self.uri, method="POST", data=data) return response async def set_white(self): @@ -141,12 +139,11 @@ async def set_sunrise(self, duration): The brightness is from 0 till 100. """ - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) max_brightness = 100 await self.set_transition_time((duration / max_brightness)) for i in range(0, duration): data = "action=on&color=3;{}".format(i) - await request(self, url, method="POST", data=data) + await request(self, uri=self.uri, method="POST", data=data) await asyncio.sleep(duration / max_brightness) async def set_flashing(self, duration, hsv1, hsv2): @@ -160,19 +157,27 @@ async def set_flashing(self, duration, hsv1, hsv2): async def set_transition_time(self, value): """Set the transition time in ms.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, url, method="POST", data={"ramp": int(round(value))} + self, uri=self.uri, method="POST", data={"ramp": int(round(value))} ) return response async def set_off(self): """Turn the bulb off.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, url, method="POST", data={"action": "off"} + self, uri=self.uri, method="POST", data={"action": "off"} ) return response + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + async def __aenter__(self) -> "MyStromBulb": - super().__aenter__() + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() diff --git a/pymystrom/device.py b/pymystrom/device.py deleted file mode 100644 index 99601e0..0000000 --- a/pymystrom/device.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Base device class for all myStrom devices.""" -from typing import Any, Mapping, Optional -import asyncio -import aiohttp -import pkg_resources -import socket -import async_timeout -from yarl import URL -from .exceptions import MyStromConnectionError - -__version__ = pkg_resources.get_distribution("setuptools").version - -TIMEOUT = 10 -USER_AGENT = f"PythonMyStrom/{__version__}" - - -async def _request( - self, - uri: str, - method: str = "GET", - data: Optional[Any] = None, - json_data: Optional[dict] = None, - params: Optional[Mapping[str, str]] = None, -) -> Any: - """Handle a request to the myStrom device.""" - headers = { - "User-Agent": USER_AGENT, - "Accept": "application/json, text/plain, */*", - } - - if self._session is None: - self._session = aiohttp.ClientSession() - self._close_session = True - - try: - with async_timeout.timeout(TIMEOUT): - response = await self._session.request( - method, - uri, - data=data, - json=json_data, - params=params, - headers=headers, - ) - except asyncio.TimeoutError as exception: - raise MyStromConnectionError( - "Timeout occurred while connecting to myStrom device." - ) from exception - except (aiohttp.ClientError, socket.gaierror) as exception: - raise MyStromConnectionError( - "Error occurred while communicating with myStrom device." - ) from exception - - content_type = response.headers.get("Content-Type", "") - if (response.status // 100) in [4, 5]: - response.close() - - if "application/json" in content_type: - response_json = await response.json() - return response_json - - return response.text - -class MyStromDevice: - """A class for a myStrom device.""" - - def __init__( - self, - host, - session: aiohttp.client.ClientSession = None, - ): - """Initialize the device.""" - self._close_session = False - self._host = host - self._session = session - self.uri = URL.build(scheme="http", host=self._host) - - async def get_device_info(self) -> dict: - """Get the device info of a myStrom device.""" - url = URL(self.uri).join(URL("api/v1/info")) - print(url) - response = await _request(self, uri=url) - if not isinstance(response, dict): - # Fall back to the old API version if the device runs with old firmware - url = URL(self.uri).join(URL("info.json")) - response = await _request(self, uri=url) - return response - - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - - async def __aenter__(self) -> "MyStromDevice": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() - - -async def get_device_info(host: str) -> dict: - """Get the device info of a myStrom device.""" - async with MyStromDevice(host) as device: - return await device.get_device_info() diff --git a/pymystrom/pir.py b/pymystrom/pir.py index 9dcd858..de66b29 100644 --- a/pymystrom/pir.py +++ b/pymystrom/pir.py @@ -3,17 +3,19 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from .device import _request as request -from .device import MyStromDevice +from . import _request as request -API_PREFIX = "api/v1" +URI_PIR = URL("api/v1/") -class MyStromPir(MyStromDevice): + +class MyStromPir: """A class for a myStrom PIR.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: - """Initialize the PIR.""" - super().__init__(host, session) + """Initialize the switch.""" + self._close_session = False + self._host = host + self._session = session self._intensity = None self._day = None self._light_raw = None @@ -27,28 +29,29 @@ def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> N self._pir = None self._actions = None + self.uri = URL.build(scheme="http", host=self._host).join(URI_PIR) async def get_settings(self) -> None: """Get the current settings from the PIR.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/settings")) + url = URL(self.uri).join(URL("settings")) response = await request(self, uri=url) self._settings = response async def get_actions(self) -> None: """Get the current action settings from the PIR.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/action")) + url = URL(self.uri).join(URL("action")) response = await request(self, uri=url) self._actions = response async def get_pir(self) -> None: """Get the current PIR settings.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/settings/pir")) + url = URL(self.uri).join(URL("settings/pir")) response = await request(self, uri=url) self._pir = response async def get_sensors_state(self) -> None: """Get the state of the sensors from the PIR.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/sensors")) + url = URL(self.uri).join(URL("sensors")) response = await request(self, uri=url) # The return data has the be re-written as the temperature is not rounded self._sensors = { @@ -69,13 +72,13 @@ async def get_temperatures(self) -> None: async def get_motion(self) -> None: """Get the state of the motion sensor from the PIR.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/motion")) + url = URL(self.uri).join(URL("motion")) response = await request(self, uri=url) self._motion = response["motion"] async def get_light(self) -> None: """Get the state of the light sensor from the PIR.""" - url = URL(self.uri).join(URL(f"{API_PREFIX}/light")) + url = URL(self.uri).join(URL("light")) response = await request(self, uri=url) self._intensity = response["intensity"] self._day = response["day"] @@ -144,5 +147,15 @@ def light_raw(self) -> Optional[str]: "infrared": self._light_raw["adc1"], } + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + async def __aenter__(self) -> "MyStromPir": - super().__aenter__() + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() diff --git a/pymystrom/switch.py b/pymystrom/switch.py index 14fc7f8..a8d80db 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -3,21 +3,24 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from .device import _request as request -from .device import MyStromDevice +from . import _request as request -class MyStromSwitch(MyStromDevice): + +class MyStromSwitch: """A class for a myStrom switch/plug.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: """Initialize the switch.""" - super().__init__(host, session) + self._close_session = False + self._host = host + self._session = session self._consumption = 0 self._consumedWs = 0 self._state = None self._temperature = None self._firmware = None self._mac = None + self.uri = URL.build(scheme="http", host=self._host) async def turn_on(self) -> None: """Turn the relay on.""" @@ -68,12 +71,6 @@ async def get_state(self) -> None: self._firmware = response["version"] self._mac = response["mac"] - async def get_temperature_full(self) -> str: - """Get current temperature in celsius.""" - url = URL(self.uri).join(URL("temp")) - response = await request(self, uri=url) - return response - @property def relay(self) -> bool: """Return the relay state.""" @@ -113,5 +110,21 @@ def temperature(self) -> float: return self._temperature - async def __aenter__(self) -> "MyStromPir": - super().__aenter__() + async def get_temperature_full(self) -> str: + """Get current temperature in celsius.""" + url = URL(self.uri).join(URL("temp")) + response = await request(self, uri=url) + return response + + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + + async def __aenter__(self) -> "MyStromSwitch": + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() From b2a0ca6aa78b79e4ce9b7058076137935dcdbb2c Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Wed, 6 Dec 2023 18:18:49 +0000 Subject: [PATCH 05/11] Move base class and add inheritance --- pymystrom/__init__.py | 106 ------------------------------------------ pymystrom/bulb.py | 43 ++++++++--------- pymystrom/device.py | 106 ++++++++++++++++++++++++++++++++++++++++++ pymystrom/pir.py | 39 ++++++---------- pymystrom/switch.py | 37 +++++---------- 5 files changed, 150 insertions(+), 181 deletions(-) create mode 100644 pymystrom/device.py diff --git a/pymystrom/__init__.py b/pymystrom/__init__.py index 80cc054..fbae3f5 100644 --- a/pymystrom/__init__.py +++ b/pymystrom/__init__.py @@ -1,107 +1 @@ """Base details for the myStrom Python bindings.""" -import asyncio -import aiohttp -import async_timeout -from yarl import URL -from typing import Any, Mapping, Optional -import socket -from .exceptions import MyStromConnectionError - -import pkg_resources - -__version__ = pkg_resources.get_distribution("setuptools").version - -TIMEOUT = 10 -USER_AGENT = f"PythonMyStrom/{__version__}" - - -async def _request( - self, - uri: str, - method: str = "GET", - data: Optional[Any] = None, - json_data: Optional[dict] = None, - params: Optional[Mapping[str, str]] = None, -) -> Any: - """Handle a request to the myStrom device.""" - headers = { - "User-Agent": USER_AGENT, - "Accept": "application/json, text/plain, */*", - } - - if self._session is None: - self._session = aiohttp.ClientSession() - self._close_session = True - - try: - with async_timeout.timeout(TIMEOUT): - response = await self._session.request( - method, - uri, - data=data, - json=json_data, - params=params, - headers=headers, - ) - except asyncio.TimeoutError as exception: - raise MyStromConnectionError( - "Timeout occurred while connecting to myStrom device." - ) from exception - except (aiohttp.ClientError, socket.gaierror) as exception: - raise MyStromConnectionError( - "Error occurred while communicating with myStrom device." - ) from exception - - content_type = response.headers.get("Content-Type", "") - if (response.status // 100) in [4, 5]: - response.close() - - if "application/json" in content_type: - response_json = await response.json() - return response_json - - return response.text - - -class MyStromDevice: - """A class for a myStrom device.""" - - def __init__( - self, - host, - session: aiohttp.client.ClientSession = None, - ): - """Initialize the device.""" - self._close_session = False - self._host = host - self._session = session - self.uri = URL.build(scheme="http", host=self._host) - - async def get_device_info(self) -> dict: - """Get the device info of a myStrom device.""" - url = URL(self.uri).join(URL("api/v1/info")) - response = await _request(self, uri=url) - if not isinstance(response, dict): - # Fall back to the old API version if the device runs with old firmware - url = URL(self.uri).join(URL("info.json")) - response = await _request(self, uri=url) - return response - - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - - async def __aenter__(self) -> "MyStromDevice": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() - - -async def get_device_info(host: str) -> dict: - """Get the device info of a myStrom device.""" - async with MyStromDevice(host) as device: - return await device.get_device_info() diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index 93aab20..f668dc9 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -6,14 +6,15 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice _LOGGER = logging.getLogger(__name__) -URI_BULB = URL("api/v1/device") +API_PREFIX = URL("api/v1/device") -class MyStromBulb: +class MyStromBulb(MyStromDevice): """A class for a myStrom bulb.""" def __init__( @@ -23,10 +24,8 @@ def __init__( session: aiohttp.client.ClientSession = None, ): """Initialize the bulb.""" - self._close_session = False - self._host = host + super().__init__(host, session) self._mac = mac - self._session = session self.brightness = 0 self._color = None self._consumption = 0 @@ -36,7 +35,7 @@ def __init__( self._bulb_type = None self._state = None self._transition_time = 0 - self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac + # self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac async def get_state(self) -> object: """Get the state of the bulb.""" @@ -91,8 +90,9 @@ def state(self) -> Optional[str]: async def set_on(self): """Turn the bulb on with the previous settings.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"action": "on"} + self, url, method="POST", data={"action": "on"} ) return response @@ -104,11 +104,12 @@ async def set_color_hex(self, value): green: 0000FF00 blue: 000000FF """ + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = { "action": "on", "color": value, } - response = await request(self, uri=self.uri, method="POST", data=data) + response = await request(self, url, method="POST", data=data) return response async def set_color_hsv(self, hue, saturation, value): @@ -120,8 +121,9 @@ async def set_color_hsv(self, hue, saturation, value): # 'action': 'on', # 'color': f"{hue};{saturation};{value}", # } + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) data = "action=on&color={};{};{}".format(hue, saturation, value) - response = await request(self, uri=self.uri, method="POST", data=data) + response = await request(self, url, method="POST", data=data) return response async def set_white(self): @@ -139,11 +141,12 @@ async def set_sunrise(self, duration): The brightness is from 0 till 100. """ + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) max_brightness = 100 await self.set_transition_time((duration / max_brightness)) for i in range(0, duration): data = "action=on&color=3;{}".format(i) - await request(self, uri=self.uri, method="POST", data=data) + await request(self, url, method="POST", data=data) await asyncio.sleep(duration / max_brightness) async def set_flashing(self, duration, hsv1, hsv2): @@ -157,27 +160,19 @@ async def set_flashing(self, duration, hsv1, hsv2): async def set_transition_time(self, value): """Set the transition time in ms.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"ramp": int(round(value))} + self, url, method="POST", data={"ramp": int(round(value))} ) return response async def set_off(self): """Turn the bulb off.""" + url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) response = await request( - self, uri=self.uri, method="POST", data={"action": "off"} + self, url, method="POST", data={"action": "off"} ) return response - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - async def __aenter__(self) -> "MyStromBulb": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + super().__aenter__() diff --git a/pymystrom/device.py b/pymystrom/device.py new file mode 100644 index 0000000..99601e0 --- /dev/null +++ b/pymystrom/device.py @@ -0,0 +1,106 @@ +"""Base device class for all myStrom devices.""" +from typing import Any, Mapping, Optional +import asyncio +import aiohttp +import pkg_resources +import socket +import async_timeout +from yarl import URL +from .exceptions import MyStromConnectionError + +__version__ = pkg_resources.get_distribution("setuptools").version + +TIMEOUT = 10 +USER_AGENT = f"PythonMyStrom/{__version__}" + + +async def _request( + self, + uri: str, + method: str = "GET", + data: Optional[Any] = None, + json_data: Optional[dict] = None, + params: Optional[Mapping[str, str]] = None, +) -> Any: + """Handle a request to the myStrom device.""" + headers = { + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + } + + if self._session is None: + self._session = aiohttp.ClientSession() + self._close_session = True + + try: + with async_timeout.timeout(TIMEOUT): + response = await self._session.request( + method, + uri, + data=data, + json=json_data, + params=params, + headers=headers, + ) + except asyncio.TimeoutError as exception: + raise MyStromConnectionError( + "Timeout occurred while connecting to myStrom device." + ) from exception + except (aiohttp.ClientError, socket.gaierror) as exception: + raise MyStromConnectionError( + "Error occurred while communicating with myStrom device." + ) from exception + + content_type = response.headers.get("Content-Type", "") + if (response.status // 100) in [4, 5]: + response.close() + + if "application/json" in content_type: + response_json = await response.json() + return response_json + + return response.text + +class MyStromDevice: + """A class for a myStrom device.""" + + def __init__( + self, + host, + session: aiohttp.client.ClientSession = None, + ): + """Initialize the device.""" + self._close_session = False + self._host = host + self._session = session + self.uri = URL.build(scheme="http", host=self._host) + + async def get_device_info(self) -> dict: + """Get the device info of a myStrom device.""" + url = URL(self.uri).join(URL("api/v1/info")) + print(url) + response = await _request(self, uri=url) + if not isinstance(response, dict): + # Fall back to the old API version if the device runs with old firmware + url = URL(self.uri).join(URL("info.json")) + response = await _request(self, uri=url) + return response + + async def close(self) -> None: + """Close an open client session.""" + if self._session and self._close_session: + await self._session.close() + + async def __aenter__(self) -> "MyStromDevice": + """Async enter.""" + return self + + async def __aexit__(self, *exc_info) -> None: + """Async exit.""" + await self.close() + + +async def get_device_info(host: str) -> dict: + """Get the device info of a myStrom device.""" + async with MyStromDevice(host) as device: + return await device.get_device_info() diff --git a/pymystrom/pir.py b/pymystrom/pir.py index de66b29..9dcd858 100644 --- a/pymystrom/pir.py +++ b/pymystrom/pir.py @@ -3,19 +3,17 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice -URI_PIR = URL("api/v1/") +API_PREFIX = "api/v1" - -class MyStromPir: +class MyStromPir(MyStromDevice): """A class for a myStrom PIR.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: - """Initialize the switch.""" - self._close_session = False - self._host = host - self._session = session + """Initialize the PIR.""" + super().__init__(host, session) self._intensity = None self._day = None self._light_raw = None @@ -29,29 +27,28 @@ def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> N self._pir = None self._actions = None - self.uri = URL.build(scheme="http", host=self._host).join(URI_PIR) async def get_settings(self) -> None: """Get the current settings from the PIR.""" - url = URL(self.uri).join(URL("settings")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/settings")) response = await request(self, uri=url) self._settings = response async def get_actions(self) -> None: """Get the current action settings from the PIR.""" - url = URL(self.uri).join(URL("action")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/action")) response = await request(self, uri=url) self._actions = response async def get_pir(self) -> None: """Get the current PIR settings.""" - url = URL(self.uri).join(URL("settings/pir")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/settings/pir")) response = await request(self, uri=url) self._pir = response async def get_sensors_state(self) -> None: """Get the state of the sensors from the PIR.""" - url = URL(self.uri).join(URL("sensors")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/sensors")) response = await request(self, uri=url) # The return data has the be re-written as the temperature is not rounded self._sensors = { @@ -72,13 +69,13 @@ async def get_temperatures(self) -> None: async def get_motion(self) -> None: """Get the state of the motion sensor from the PIR.""" - url = URL(self.uri).join(URL("motion")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/motion")) response = await request(self, uri=url) self._motion = response["motion"] async def get_light(self) -> None: """Get the state of the light sensor from the PIR.""" - url = URL(self.uri).join(URL("light")) + url = URL(self.uri).join(URL(f"{API_PREFIX}/light")) response = await request(self, uri=url) self._intensity = response["intensity"] self._day = response["day"] @@ -147,15 +144,5 @@ def light_raw(self) -> Optional[str]: "infrared": self._light_raw["adc1"], } - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - async def __aenter__(self) -> "MyStromPir": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + super().__aenter__() diff --git a/pymystrom/switch.py b/pymystrom/switch.py index a8d80db..14fc7f8 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -3,24 +3,21 @@ from yarl import URL from typing import Any, Dict, Iterable, List, Optional, Union -from . import _request as request +from .device import _request as request +from .device import MyStromDevice - -class MyStromSwitch: +class MyStromSwitch(MyStromDevice): """A class for a myStrom switch/plug.""" def __init__(self, host: str, session: aiohttp.client.ClientSession = None) -> None: """Initialize the switch.""" - self._close_session = False - self._host = host - self._session = session + super().__init__(host, session) self._consumption = 0 self._consumedWs = 0 self._state = None self._temperature = None self._firmware = None self._mac = None - self.uri = URL.build(scheme="http", host=self._host) async def turn_on(self) -> None: """Turn the relay on.""" @@ -71,6 +68,12 @@ async def get_state(self) -> None: self._firmware = response["version"] self._mac = response["mac"] + async def get_temperature_full(self) -> str: + """Get current temperature in celsius.""" + url = URL(self.uri).join(URL("temp")) + response = await request(self, uri=url) + return response + @property def relay(self) -> bool: """Return the relay state.""" @@ -110,21 +113,5 @@ def temperature(self) -> float: return self._temperature - async def get_temperature_full(self) -> str: - """Get current temperature in celsius.""" - url = URL(self.uri).join(URL("temp")) - response = await request(self, uri=url) - return response - - async def close(self) -> None: - """Close an open client session.""" - if self._session and self._close_session: - await self._session.close() - - async def __aenter__(self) -> "MyStromSwitch": - """Async enter.""" - return self - - async def __aexit__(self, *exc_info) -> None: - """Async exit.""" - await self.close() + async def __aenter__(self) -> "MyStromPir": + super().__aenter__() From 8e3042a7c5f7977cb440e16ba4f3ab25f6e32e91 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 13 Jan 2024 12:59:24 +0000 Subject: [PATCH 06/11] Remove unused imports and fix switch __aenter__ return type --- pymystrom/bulb.py | 2 +- pymystrom/device.py | 1 + pymystrom/pir.py | 3 ++- pymystrom/switch.py | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index f668dc9..68294e3 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -4,7 +4,7 @@ import aiohttp from yarl import URL -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Optional from .device import _request as request from .device import MyStromDevice diff --git a/pymystrom/device.py b/pymystrom/device.py index 99601e0..b57cdaa 100644 --- a/pymystrom/device.py +++ b/pymystrom/device.py @@ -61,6 +61,7 @@ async def _request( return response.text + class MyStromDevice: """A class for a myStrom device.""" diff --git a/pymystrom/pir.py b/pymystrom/pir.py index 9dcd858..bf6da56 100644 --- a/pymystrom/pir.py +++ b/pymystrom/pir.py @@ -1,13 +1,14 @@ """Support for communicating with myStrom PIRs.""" import aiohttp from yarl import URL -from typing import Any, Dict, Iterable, List, Optional, Union +from typing import Optional from .device import _request as request from .device import MyStromDevice API_PREFIX = "api/v1" + class MyStromPir(MyStromDevice): """A class for a myStrom PIR.""" diff --git a/pymystrom/switch.py b/pymystrom/switch.py index 14fc7f8..59f254a 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -1,11 +1,11 @@ """Support for communicating with myStrom plugs/switches.""" import aiohttp from yarl import URL -from typing import Any, Dict, Iterable, List, Optional, Union from .device import _request as request from .device import MyStromDevice + class MyStromSwitch(MyStromDevice): """A class for a myStrom switch/plug.""" @@ -113,5 +113,5 @@ def temperature(self) -> float: return self._temperature - async def __aenter__(self) -> "MyStromPir": + async def __aenter__(self) -> "MyStromSwitch": super().__aenter__() From e05b72bcce9c79766029835952ca74281850a925 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 13 Jan 2024 13:05:24 +0000 Subject: [PATCH 07/11] Add Black formatter extension to .vscode settings --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a0e76ff --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true + } +} \ No newline at end of file From 4578fac91b23230d334974f807536018e9b9590f Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 13 Jan 2024 13:05:39 +0000 Subject: [PATCH 08/11] Run formatter on all files --- pymystrom/bulb.py | 8 ++------ pymystrom/switch.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index 68294e3..6189e7a 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -91,9 +91,7 @@ def state(self) -> Optional[str]: async def set_on(self): """Turn the bulb on with the previous settings.""" url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) - response = await request( - self, url, method="POST", data={"action": "on"} - ) + response = await request(self, url, method="POST", data={"action": "on"}) return response async def set_color_hex(self, value): @@ -169,9 +167,7 @@ async def set_transition_time(self, value): async def set_off(self): """Turn the bulb off.""" url = URL(self.uri).join(URL(f"{API_PREFIX}/{self.mac}")) - response = await request( - self, url, method="POST", data={"action": "off"} - ) + response = await request(self, url, method="POST", data={"action": "off"}) return response async def __aenter__(self) -> "MyStromBulb": diff --git a/pymystrom/switch.py b/pymystrom/switch.py index 59f254a..ccbd3ae 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -73,7 +73,7 @@ async def get_temperature_full(self) -> str: url = URL(self.uri).join(URL("temp")) response = await request(self, uri=url) return response - + @property def relay(self) -> bool: """Return the relay state.""" From 92d143681c70b0f0e3eb8e38003ac729bd4da6b9 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 13 Jan 2024 14:11:40 +0100 Subject: [PATCH 09/11] Add extensions to .devcontainer --- .devcontainer/devcontainer.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 244f3e6..b40bcfb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,19 +4,22 @@ "name": "Python 3", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", - // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "pip3 install -e ." - - // Configure tool-specific properties. - // "customizations": {}, - + "postCreateCommand": "pip3 install -e .", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.vscode-pylance", + "ms-python.python" + ] + } + } // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" -} +} \ No newline at end of file From a4004178943a2a08b3e9d78c51666bd98e2eaeb4 Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 13 Jan 2024 13:30:09 +0000 Subject: [PATCH 10/11] Add .flake8 file and add configs recommended for working with black code formatter https://github.com/psf/black/blob/06ccb88bf2bd35a4dc5d591bb296b5b299d07323/docs/guides/using_black_with_other_tools.md#flake8 --- .flake8 | 3 +++ pymystrom/bulb.py | 4 +++- setup.py | 9 +-------- 3 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e0ea542 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 \ No newline at end of file diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index 6189e7a..0ea8583 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -35,7 +35,9 @@ def __init__( self._bulb_type = None self._state = None self._transition_time = 0 - # self.uri = URL.build(scheme="http", host=self._host).join(URI_BULB) / self._mac + # self.uri = URL.build( + # scheme="http", host=self._host + # ).join(URI_BULB) / self._mac async def get_state(self) -> object: """Get the state of the bulb.""" diff --git a/setup.py b/setup.py index 300b234..a6136c8 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ """Set up the Python API for myStrom devices.""" import os -import sys from setuptools import setup, find_packages @@ -19,13 +18,7 @@ author="Fabian Affolter", author_email="fabian@affolter-engineering.ch", license="MIT", - install_requires=[ - "requests", - "click", - "aiohttp", - "setuptools", - "async_timeout" - ], + install_requires=["requests", "click", "aiohttp", "setuptools", "async_timeout"], packages=find_packages(), python_requires=">=3.9", zip_safe=True, From ff94b20808497a4e9c22e6fa08ec775fc4b2503c Mon Sep 17 00:00:00 2001 From: Benedikt Schniepp Date: Sat, 27 Jan 2024 08:54:44 +0000 Subject: [PATCH 11/11] Only return self on __aenter__ --- pymystrom/bulb.py | 2 +- pymystrom/pir.py | 2 +- pymystrom/switch.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pymystrom/bulb.py b/pymystrom/bulb.py index 0ea8583..28d357c 100644 --- a/pymystrom/bulb.py +++ b/pymystrom/bulb.py @@ -173,4 +173,4 @@ async def set_off(self): return response async def __aenter__(self) -> "MyStromBulb": - super().__aenter__() + return self diff --git a/pymystrom/pir.py b/pymystrom/pir.py index bf6da56..84c3723 100644 --- a/pymystrom/pir.py +++ b/pymystrom/pir.py @@ -146,4 +146,4 @@ def light_raw(self) -> Optional[str]: } async def __aenter__(self) -> "MyStromPir": - super().__aenter__() + return self diff --git a/pymystrom/switch.py b/pymystrom/switch.py index ccbd3ae..31edc2d 100644 --- a/pymystrom/switch.py +++ b/pymystrom/switch.py @@ -114,4 +114,4 @@ def temperature(self) -> float: return self._temperature async def __aenter__(self) -> "MyStromSwitch": - super().__aenter__() + return self