Skip to content

Commit

Permalink
Adding functions to get firmware version info and get/set device pref…
Browse files Browse the repository at this point in the history
…erences. Various refactoring.
  • Loading branch information
jordanruthe committed Jan 17, 2024
1 parent 32dac15 commit 762d420
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 18 deletions.
53 changes: 40 additions & 13 deletions aiophyn/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
import asyncio
import logging
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from typing import Optional
from urllib.parse import urlparse

import boto3
from aiohttp import ClientSession, ClientTimeout
Expand All @@ -17,8 +16,6 @@
from .errors import BrandError, RequestError
from .home import Home

from .const import API_BASE


_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -49,7 +46,8 @@ class API:

def __init__(
self, username: str, password: str, *, phyn_brand: str, session: Optional[ClientSession] = None,
client_id: Optional[str] = None, verify_ssl: bool = True, proxy: Optional[str] = None, proxy_port: Optional[int] = None
client_id: Optional[str] = None, verify_ssl: bool = True, proxy: Optional[str] = None,
proxy_port: Optional[int] = None
) -> None:
"""Initialize."""
if phyn_brand not in BRANDS:
Expand All @@ -75,13 +73,16 @@ def __init__(
self._iot_id = None
self._iot_credentials = None
self.mqtt = None
self._id_token = None
self._refresh_token = None
self._mqtt_settings = {}

self.verify_ssl = verify_ssl
self.proxy = proxy
self.proxy_port = proxy_port
self.proxy_url: Optional[str] = None
if self.proxy is not None and self.proxy_port is not None:
self.proxy_url = "https://%s:%s" % (proxy, proxy_port)
self.proxy_url = f"https://{proxy}:{proxy_port}"

self._token: Optional[str] = None
self._token_expiration: Optional[datetime] = None
Expand All @@ -90,8 +91,24 @@ def __init__(
self.device: Device = Device(self._request)
self.mqtt = MQTTClient(self, client_id=client_id, verify_ssl=verify_ssl, proxy=proxy, proxy_port=proxy_port)

@property
def username(self) -> Optional[str]:
"""Get the API username"""
return self._username

async def _request(self, method: str, url: str, token_type:str = "access", **kwargs) -> dict:
"""Make a request against the API."""
"""Make a request against the API.
:param method: GET or POST request
:type method: str
:param url: API URL
:type url: str
:param token_type: ID or Access token, defaults to "access"
:type token_type: str, optional
:raises RequestError: Error if issue accessing URL
:return: JSON response
:rtype: dict
"""
if self._token_expiration and datetime.now() >= self._token_expiration:
_LOGGER.info("Requesting new access token to replace expired one")

Expand Down Expand Up @@ -122,10 +139,10 @@ async def _request(self, method: str, url: str, token_type:str = "access", **kwa
elif token_type == "id":
if self._id_token:
kwargs["headers"]["Authorization"] = self._id_token

if self.proxy_url is not None:
kwargs["proxy"] = self.proxy_url

if not self.verify_ssl:
kwargs["ssl"] = False

Expand All @@ -150,9 +167,9 @@ async def _request(self, method: str, url: str, token_type:str = "access", **kwa
async def async_authenticate(self) -> None:
"""Authenticate the user and set the access token with its expiration."""
if self._brand == BRANDS["kohler"]:
if self._password == None:
if self._password is None:
_LOGGER.info("Auhenticating to Kohler")
self._partner_api = KOHLER_API(self._username, self._partner_password, verify_ssl=self.verify_ssl,
self._partner_api = KOHLER_API(self._username, self._partner_password, verify_ssl=self.verify_ssl,
proxy=self.proxy, proxy_port=self.proxy_port)
await self._partner_api.authenticate()
self._password = self._partner_api.get_phyn_password()
Expand Down Expand Up @@ -190,7 +207,8 @@ def _authenticate(self):

async def async_get_api(
username: str, password: str, *, phyn_brand: str = "phyn", session: Optional[ClientSession] = None,
client_id: Optional[str] = None, verify_ssl: bool = True, proxy: Optional[str] = None, proxy_port: Optional[int] = None
client_id: Optional[str] = None, verify_ssl: bool = True, proxy: Optional[str] = None,
proxy_port: Optional[int] = None
) -> API:
"""Instantiate an authenticated API object.
Expand All @@ -202,8 +220,17 @@ async def async_get_api(
:type password: ``str``
:param phyn_brand: A brand for phyn
:type phyn_brand: ``str``
:param client_id: A MQTT client id name
:type client_id: ``str``
:param verify_ssl: Should SSL certificates be verified
:type verify_ssl: ``bool``
:param proxy: HTTP proxy hostname/IP
:type proxy: ``str``
:param proxy_port: Port for HTTP proxy
:type proxy_port: ``int``
:rtype: :meth:`aiophyn.api.API`
"""
api = API(username, password, phyn_brand=phyn_brand, session=session, client_id=client_id, verify_ssl=verify_ssl, proxy=proxy, proxy_port=proxy_port)
api = API(username, password, phyn_brand=phyn_brand, session=session, client_id=client_id,
verify_ssl=verify_ssl, proxy=proxy, proxy_port=proxy_port)
await api.async_authenticate()
return api
40 changes: 38 additions & 2 deletions aiophyn/device.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Define /devices endpoints."""
from typing import Awaitable, Callable, Optional
from typing import Awaitable, Any, Callable, Optional

from .const import API_BASE

Expand Down Expand Up @@ -88,7 +88,7 @@ async def close_valve(self, device_id: str) -> None:
"post",
f"{API_BASE}/devices/{device_id}/sov/Close",
)

async def get_away_mode(self, device_id: str) -> dict:
"""Return away mode status of a device.
Expand Down Expand Up @@ -134,3 +134,39 @@ async def disable_away_mode(self, device_id: str) -> None:
return await self._request(
"post", f"{API_BASE}/preferences/device/{device_id}", json=data
)

async def get_latest_firmware_info(self, device_id: str) -> dict:
"""Get Latest Firmware Information
:param device_id: Unique identifier for the device
:type device_id: str
:return: Returns dict with fw_img_name, fw_version, product_code
:rtype: dict
"""
return await self._request(
"get", f"{API_BASE}/firmware/latestVersion/v2?device_id={device_id}"
)

async def get_device_preferences(self, device_id: str) -> dict:
"""Get phyn device preferences.
:param device_id: Unique identifier for the device
:type device_id: str
:return: List of dicts with the following keys: created_ts, device_id, name, updated_ts, value
:rtype: dict
"""
return await self._request(
"get", f"{API_BASE}/preferences/device/{device_id}"
)

async def set_device_preferences(self, device_id: str, data: list[dict]) -> None:
"""Set device preferences
:param device_id: Unique identifier for the device
:type device_id: str
:param data: List of dicts which have the keys: device_id, name, value
:type data: List[dict]
"""
return await self._request(
"post", f"{API_BASE}/preferences/device/{device_id}", json=data
)
4 changes: 2 additions & 2 deletions aiophyn/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def __init__(self, api, client_id: str =None, verify_ssl: bool =True, proxy: str
self.connect_task = None
self.disconnect_evt: Optional[asyncio.Event] = None
self.reconnect_evt: asyncio.Event = asyncio.Event()
self.host = None
self.host = None
self.port = 443

if client_id is None:
Expand Down Expand Up @@ -326,7 +326,7 @@ def _on_message(
) -> None:
# pylint: disable=unused-argument
msg = message.payload.decode()
_LOGGER.debug("Message received on %s %s", message.topic, msg)
_LOGGER.debug("Message received on %s", message.topic)
try:
data = json.loads(msg)
except json.decoder.JSONDecodeError:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "aiophyn"
version = "2024.1.1"
version = "2024.1.2"
description = "An asynchronous library for Phyn Smart Water devices"
authors = ["MizterB <5458030+MizterB@users.noreply.github.com>","jordanruthe <31575189+jordanruthe@users.noreply.github.com>"]
license = "MIT"
Expand Down

0 comments on commit 762d420

Please sign in to comment.