Merge pull request #1 from IoTLabs-pl/Remove-awsiotsdk-dependency

Remove awsiotsdk dependency
This commit is contained in:
Kuba Sawulski 2024-08-09 15:44:41 +02:00 committed by GitHub
commit 7d65cda8c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 61 additions and 93 deletions

View file

@ -1,6 +1,6 @@
[MESSAGES CONTROL]
disable=missing-docstring
disable=missing-docstring, contextmanager-generator-missing-cleanup
[FORMAT]

View file

@ -6,7 +6,7 @@ import urllib
from contextlib import suppress
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Dict, Optional, Any, List
from typing import Any, Dict, List, Optional
from urllib import parse
from urllib.parse import quote
@ -120,18 +120,25 @@ class HonAuth:
async with self._request.get(url) as response:
text = await response.text()
self._expires = datetime.utcnow()
login_url: List[str] = re.findall("url = '(.+?)'", text)
login_url: List[str] = re.findall("(?:url|href) ?= ?'(.+?)'", text)
if not login_url:
if "oauth/done#access_token=" in text:
self._parse_token_data(text)
raise exceptions.HonNoAuthenticationNeeded()
await self._error_logger(response)
# As of July 2024 the login page has changed,
# and we started getting a /NewhOnLogin based relative URL in JS to parse
if login_url[0].startswith("/NewhOnLogin"):
# Force use of the old login page to avoid having
# to make the new one work..
login_url[0] = f"{const.AUTH_API}/s/login{login_url[0]}"
return login_url[0]
async def _manual_redirect(self, url: str) -> str:
async with self._request.get(url, allow_redirects=False) as response:
if not (new_location := response.headers.get("Location", "")):
await self._error_logger(response)
new_location = response.headers.get("Location", "")
if not new_location:
return url
return new_location
async def _handle_redirects(self, login_url: str) -> str:

View file

@ -1,33 +1,32 @@
import asyncio
import json
import logging
import secrets
from typing import TYPE_CHECKING
from urllib.parse import urlencode
from awscrt import mqtt5
from awsiot import mqtt5_client_builder # type: ignore[import-untyped]
from paho.mqtt.client import Client, MQTTv5
from pyhon import const
from pyhon.appliance import HonAppliance
if TYPE_CHECKING:
from paho.mqtt.client import MQTTMessage, _UserData
from pyhon import Hon
from pyhon.appliance import HonAppliance
_LOGGER = logging.getLogger(__name__)
class MQTTClient:
def __init__(self, hon: "Hon", mobile_id: str) -> None:
self._client: mqtt5.Client | None = None
self._client: Client | None = None
self._hon = hon
self._mobile_id = mobile_id or const.MOBILE_ID
self._api = hon.api
self._appliances = hon.appliances
self._connection = False
self._watchdog_task: asyncio.Task[None] | None = None
@property
def client(self) -> mqtt5.Client:
def client(self) -> Client:
if self._client is not None:
return self._client
raise AttributeError("Client is not set")
@ -35,112 +34,74 @@ class MQTTClient:
async def create(self) -> "MQTTClient":
await self._start()
self._subscribe_appliances()
await self.start_watchdog()
return self
def _on_lifecycle_stopped(
self, lifecycle_stopped_data: mqtt5.LifecycleStoppedData
) -> None:
_LOGGER.info("Lifecycle Stopped: %s", str(lifecycle_stopped_data))
def _on_lifecycle_connection_success(
def _on_message(
self,
lifecycle_connect_success_data: mqtt5.LifecycleConnectSuccessData,
client: Client, # pylint: disable=unused-argument
userdata: "_UserData", # pylint: disable=unused-argument
message: "MQTTMessage",
) -> None:
self._connection = True
_LOGGER.info(
"Lifecycle Connection Success: %s", str(lifecycle_connect_success_data)
)
def _on_lifecycle_attempting_connect(
self,
lifecycle_attempting_connect_data: mqtt5.LifecycleAttemptingConnectData,
) -> None:
_LOGGER.info(
"Lifecycle Attempting Connect - %s", str(lifecycle_attempting_connect_data)
)
def _on_lifecycle_connection_failure(
self,
lifecycle_connection_failure_data: mqtt5.LifecycleConnectFailureData,
) -> None:
self._connection = False
_LOGGER.info(
"Lifecycle Connection Failure - %s", str(lifecycle_connection_failure_data)
)
def _on_lifecycle_disconnection(
self,
lifecycle_disconnect_data: mqtt5.LifecycleDisconnectData,
) -> None:
self._connection = False
_LOGGER.info("Lifecycle Disconnection - %s", str(lifecycle_disconnect_data))
def _on_publish_received(self, data: mqtt5.PublishReceivedData) -> None:
if not (data and data.publish_packet and data.publish_packet.payload):
if not message.payload or not message.topic:
return
payload = json.loads(data.publish_packet.payload.decode())
topic = data.publish_packet.topic
payload = json.loads(message.payload)
topic = message.topic
appliance = next(
a for a in self._appliances if topic in a.info["topics"]["subscribe"]
)
if topic and "appliancestatus" in topic:
topic_parts = topic.split("/")
if "appliancestatus" in topic_parts:
for parameter in payload["parameters"]:
appliance.attributes["parameters"][parameter["parName"]].update(
parameter
)
appliance.sync_params_to_command("settings")
elif topic and "disconnected" in topic:
elif "disconnected" in topic_parts:
_LOGGER.info(
"Disconnected %s: %s",
appliance.nick_name,
payload.get("disconnectReason"),
)
appliance.connection = False
elif topic and "connected" in topic:
elif "connected" in topic_parts:
appliance.connection = True
_LOGGER.info("Connected %s", appliance.nick_name)
elif topic and "discovery" in topic:
elif "discovery" in topic_parts:
_LOGGER.info("Discovered %s", appliance.nick_name)
self._hon.notify()
_LOGGER.info("%s - %s", topic, payload)
async def _start(self) -> None:
self._client = mqtt5_client_builder.websockets_with_custom_authorizer(
endpoint=const.AWS_ENDPOINT,
auth_authorizer_name=const.AWS_AUTHORIZER,
auth_authorizer_signature=await self._api.load_aws_token(),
auth_token_key_name="token",
auth_token_value=self._api.auth.id_token,
self._client = Client(
client_id=f"{self._mobile_id}_{secrets.token_hex(8)}",
on_lifecycle_stopped=self._on_lifecycle_stopped,
on_lifecycle_connection_success=self._on_lifecycle_connection_success,
on_lifecycle_attempting_connect=self._on_lifecycle_attempting_connect,
on_lifecycle_connection_failure=self._on_lifecycle_connection_failure,
on_lifecycle_disconnection=self._on_lifecycle_disconnection,
on_publish_received=self._on_publish_received,
protocol=MQTTv5,
reconnect_on_failure=True,
)
self.client.start()
self._client.on_message = self._on_message
self._client.enable_logger(_LOGGER)
query_params = urlencode(
{
"x-amz-customauthorizer-name": const.AWS_AUTHORIZER,
"x-amz-customauthorizer-signature": await self._api.load_aws_token(),
"token": self._api.auth.id_token,
}
)
self._client.username_pw_set(f"?{query_params}")
self._client.connect_async(const.AWS_ENDPOINT, 443)
self._client.loop_start()
def _subscribe_appliances(self) -> None:
for appliance in self._appliances:
self._subscribe(appliance)
def _subscribe(self, appliance: HonAppliance) -> None:
def _subscribe(self, appliance: "HonAppliance") -> None:
for topic in appliance.info.get("topics", {}).get("subscribe", []):
self.client.subscribe(
mqtt5.SubscribePacket([mqtt5.Subscription(topic)])
).result(10)
if self._client:
self._client.subscribe(topic)
_LOGGER.info("Subscribed to topic %s", topic)
async def start_watchdog(self) -> None:
if not self._watchdog_task or self._watchdog_task.done():
self._watchdog_task = asyncio.create_task(self._watchdog())
async def _watchdog(self) -> None:
while True:
await asyncio.sleep(5)
if not self._connection:
_LOGGER.info("Restart mqtt connection")
await self._start()
self._subscribe_appliances()

View file

@ -1,4 +1,4 @@
aiohttp>=3.8.6
yarl>=1.8
typing-extensions>=4.8
awsiotsdk>=1.21.0
paho-mqtt==1.6.1

View file

@ -3,4 +3,4 @@ flake8>=6.0
mypy>=0.991
pylint>=2.15
setuptools>=62.3
types-awscrt
types-paho-mqtt

View file

@ -7,7 +7,7 @@ with open("README.md", "r", encoding="utf-8") as f:
setup(
name="pyhOn",
version="0.17.4",
version="0.17.5",
author="Andre Basche",
description="Control hOn devices with python",
long_description=long_description,
@ -25,7 +25,7 @@ setup(
"aiohttp>=3.8.6",
"typing-extensions>=4.8",
"yarl>=1.8",
"awsiotsdk>=1.21.0",
"paho-mqtt==1.6.1",
],
classifiers=[
"Development Status :: 4 - Beta",