mirror of
https://github.com/Andre0512/pyhOn.git
synced 2024-12-22 10:45:31 +00:00
Support cloud push with wss mqtt
This commit is contained in:
parent
a1347f7a46
commit
f108005a4d
|
@ -7,10 +7,12 @@ from types import TracebackType
|
||||||
from typing import Dict, Optional, Any, List, no_type_check, Type
|
from typing import Dict, Optional, Any, List, no_type_check, Type
|
||||||
|
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
|
from awscrt import mqtt5
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from pyhon import const, exceptions
|
from pyhon import const, exceptions
|
||||||
from pyhon.appliance import HonAppliance
|
from pyhon.appliance import HonAppliance
|
||||||
|
from pyhon.connection import mqtt
|
||||||
from pyhon.connection.auth import HonAuth
|
from pyhon.connection.auth import HonAuth
|
||||||
from pyhon.connection.handler.anonym import HonAnonymousConnectionHandler
|
from pyhon.connection.handler.anonym import HonAnonymousConnectionHandler
|
||||||
from pyhon.connection.handler.hon import HonConnectionHandler
|
from pyhon.connection.handler.hon import HonConnectionHandler
|
||||||
|
@ -18,6 +20,7 @@ from pyhon.connection.handler.hon import HonConnectionHandler
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
class HonAPI:
|
class HonAPI:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -37,6 +40,7 @@ class HonAPI:
|
||||||
self._hon_handler: Optional[HonConnectionHandler] = None
|
self._hon_handler: Optional[HonConnectionHandler] = None
|
||||||
self._hon_anonymous_handler: Optional[HonAnonymousConnectionHandler] = None
|
self._hon_anonymous_handler: Optional[HonAnonymousConnectionHandler] = None
|
||||||
self._session: Optional[ClientSession] = session
|
self._session: Optional[ClientSession] = session
|
||||||
|
self._mqtt_client: mqtt5.Client | None = None
|
||||||
|
|
||||||
async def __aenter__(self) -> Self:
|
async def __aenter__(self) -> Self:
|
||||||
return await self.create()
|
return await self.create()
|
||||||
|
@ -191,6 +195,13 @@ class HonAPI:
|
||||||
maintenance: Dict[str, Any] = (await response.json()).get("payload", {})
|
maintenance: Dict[str, Any] = (await response.json()).get("payload", {})
|
||||||
return maintenance
|
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(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
appliance: HonAppliance,
|
appliance: HonAppliance,
|
||||||
|
@ -258,6 +269,10 @@ class HonAPI:
|
||||||
result: Dict[str, Any] = await response.json()
|
result: Dict[str, Any] = await response.json()
|
||||||
return result
|
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:
|
async def close(self) -> None:
|
||||||
if self._hon_handler is not None:
|
if self._hon_handler is not None:
|
||||||
await self._hon_handler.close()
|
await self._hon_handler.close()
|
||||||
|
|
76
pyhon/connection/mqtt.py
Normal file
76
pyhon/connection/mqtt.py
Normal 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
|
|
@ -1,6 +1,8 @@
|
||||||
AUTH_API = "https://account2.hon-smarthome.com"
|
AUTH_API = "https://account2.hon-smarthome.com"
|
||||||
API_URL = "https://api-iot.he.services"
|
API_URL = "https://api-iot.he.services"
|
||||||
API_KEY = "GRCqFhC6Gk@ikWXm1RmnSmX1cm,MxY-configuration"
|
API_KEY = "GRCqFhC6Gk@ikWXm1RmnSmX1cm,MxY-configuration"
|
||||||
|
AWS_ENDPOINT = "a30f6tqw0oh1x0-ats.iot.eu-west-1.amazonaws.com"
|
||||||
|
AWS_AUTHORIZER = "candy-iot-authorizer"
|
||||||
APP = "hon"
|
APP = "hon"
|
||||||
CLIENT_ID = (
|
CLIENT_ID = (
|
||||||
"3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9."
|
"3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9."
|
||||||
|
|
|
@ -120,6 +120,7 @@ class Hon:
|
||||||
api = TestAPI(test_data)
|
api = TestAPI(test_data)
|
||||||
for appliance in await api.load_appliances():
|
for appliance in await api.load_appliances():
|
||||||
await self._create_appliance(appliance, api)
|
await self._create_appliance(appliance, api)
|
||||||
|
await self.api.subscribe_mqtt(self.appliances)
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
await self.api.close()
|
await self.api.close()
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
aiohttp>=3.8.6
|
aiohttp>=3.8.6
|
||||||
yarl>=1.8
|
yarl>=1.8
|
||||||
typing-extensions>=4.8
|
typing-extensions>=4.8
|
||||||
|
awsiotsdk>=1.21.0
|
|
@ -3,3 +3,4 @@ flake8>=6.0
|
||||||
mypy>=0.991
|
mypy>=0.991
|
||||||
pylint>=2.15
|
pylint>=2.15
|
||||||
setuptools>=62.3
|
setuptools>=62.3
|
||||||
|
types-awscrt
|
7
setup.py
7
setup.py
|
@ -21,7 +21,12 @@ setup(
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
python_requires=">=3.10",
|
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=[
|
classifiers=[
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 4 - Beta",
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
|
|
Loading…
Reference in a new issue