Support cloud push with wss mqtt

This commit is contained in:
Andre Basche 2024-02-17 04:07:28 +01:00
parent a1347f7a46
commit f108005a4d
7 changed files with 102 additions and 1 deletions

View File

@ -7,10 +7,12 @@ from types import TracebackType
from typing import Dict, Optional, Any, List, no_type_check, Type
from aiohttp import ClientSession
from awscrt import mqtt5
from typing_extensions import Self
from pyhon import const, exceptions
from pyhon.appliance import HonAppliance
from pyhon.connection import mqtt
from pyhon.connection.auth import HonAuth
from pyhon.connection.handler.anonym import HonAnonymousConnectionHandler
from pyhon.connection.handler.hon import HonConnectionHandler
@ -18,6 +20,7 @@ from pyhon.connection.handler.hon import HonConnectionHandler
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-instance-attributes
class HonAPI:
def __init__(
self,
@ -37,6 +40,7 @@ class HonAPI:
self._hon_handler: Optional[HonConnectionHandler] = None
self._hon_anonymous_handler: Optional[HonAnonymousConnectionHandler] = None
self._session: Optional[ClientSession] = session
self._mqtt_client: mqtt5.Client | None = None
async def __aenter__(self) -> Self:
return await self.create()
@ -191,6 +195,13 @@ class HonAPI:
maintenance: Dict[str, Any] = (await response.json()).get("payload", {})
return maintenance
async def load_aws_token(self) -> str:
url: str = f"{const.API_URL}/auth/v1/introspection"
async with self._hon.get(url) as response:
introspection: Dict[str, Any] = (await response.json()).get("payload", {})
result: str = introspection.get("tokenSigned", "")
return result
async def send_command(
self,
appliance: HonAppliance,
@ -258,6 +269,10 @@ class HonAPI:
result: Dict[str, Any] = await response.json()
return result
async def subscribe_mqtt(self, appliances: list[HonAppliance]) -> None:
if not self._mqtt_client:
self._mqtt_client = await mqtt.start(self, appliances)
async def close(self) -> None:
if self._hon_handler is not None:
await self._hon_handler.close()

76
pyhon/connection/mqtt.py Normal file
View File

@ -0,0 +1,76 @@
import json
import logging
from typing import TYPE_CHECKING
from awscrt import mqtt5
from awsiot import mqtt5_client_builder # type: ignore[import-untyped]
from pyhon import const
from pyhon.appliance import HonAppliance
if TYPE_CHECKING:
from pyhon import HonAPI
_LOGGER = logging.getLogger(__name__)
appliances: list[HonAppliance] = []
def on_lifecycle_stopped(lifecycle_stopped_data: mqtt5.LifecycleStoppedData) -> None:
print("Lifecycle Stopped")
print(lifecycle_stopped_data)
def on_lifecycle_connection_success(
lifecycle_connect_success_data: mqtt5.LifecycleConnectSuccessData,
) -> None:
print("Lifecycle Connection Success")
print(lifecycle_connect_success_data.connack_packet)
print(lifecycle_connect_success_data.negotiated_settings)
def on_publish_received(data: mqtt5.PublishReceivedData) -> None:
if not (data and data.publish_packet and data.publish_packet.payload):
return
payload = json.loads(data.publish_packet.payload.decode())
topic = data.publish_packet.topic
if topic and "appliancestatus" in topic:
appliance = next(
a for a in appliances if topic in a.info["topics"]["subscribe"]
)
for parameter in payload["parameters"]:
appliance.attributes["parameters"][parameter["parName"]].update(parameter)
print(parameter)
else:
print(topic, payload)
async def create_mqtt_client(api: "HonAPI") -> mqtt5.Client:
client: mqtt5.Client = mqtt5_client_builder.websockets_with_custom_authorizer(
endpoint=const.AWS_ENDPOINT,
auth_authorizer_name=const.AWS_AUTHORIZER,
auth_authorizer_signature=await api.load_aws_token(),
auth_token_key_name="token",
auth_token_value=api.auth.id_token,
client_id=const.MOBILE_ID,
on_lifecycle_stopped=on_lifecycle_stopped,
on_lifecycle_connection_success=on_lifecycle_connection_success,
on_publish_received=on_publish_received,
)
client.start()
return client
def subscribe(client: mqtt5.Client, appliance: HonAppliance) -> None:
for topic in appliance.info.get("topics", {}).get("subscribe", []):
client.subscribe(mqtt5.SubscribePacket([mqtt5.Subscription(topic)])).result(10)
_LOGGER.error("Subscribed to topic %s", topic)
async def start(api: "HonAPI", app: list[HonAppliance]) -> mqtt5.Client:
client = await create_mqtt_client(api)
global appliances # pylint: disable=global-statement
appliances = app
for appliance in appliances:
subscribe(client, appliance)
return client

View File

@ -1,6 +1,8 @@
AUTH_API = "https://account2.hon-smarthome.com"
API_URL = "https://api-iot.he.services"
API_KEY = "GRCqFhC6Gk@ikWXm1RmnSmX1cm,MxY-configuration"
AWS_ENDPOINT = "a30f6tqw0oh1x0-ats.iot.eu-west-1.amazonaws.com"
AWS_AUTHORIZER = "candy-iot-authorizer"
APP = "hon"
CLIENT_ID = (
"3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9."

View File

@ -120,6 +120,7 @@ class Hon:
api = TestAPI(test_data)
for appliance in await api.load_appliances():
await self._create_appliance(appliance, api)
await self.api.subscribe_mqtt(self.appliances)
async def close(self) -> None:
await self.api.close()

View File

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

View File

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

View File

@ -21,7 +21,12 @@ setup(
packages=find_packages(),
include_package_data=True,
python_requires=">=3.10",
install_requires=["aiohttp>=3.8.6", "typing-extensions>=4.8", "yarl>=1.8"],
install_requires=[
"aiohttp>=3.8.6",
"typing-extensions>=4.8",
"yarl>=1.8",
"awsiotsdk>=1.21.0",
],
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",