diff --git a/franklinwh/__init__.py b/franklinwh/__init__.py index 1e4ea85..a5d0edd 100644 --- a/franklinwh/__init__.py +++ b/franklinwh/__init__.py @@ -2,7 +2,7 @@ from .api import DEFAULT_URL_BASE from .caching_thread import CachingThread -from .client import AccessoryType, Client, GridStatus, Mode, Stats, TokenFetcher +from .client import AccessoryType, Client, GridStatus, Mode, Stats, TokenFetcher, HttpClientFactory __all__ = [ "DEFAULT_URL_BASE", @@ -10,6 +10,7 @@ "CachingThread", "Client", "GridStatus", + "HttpClientfactory" "Mode", "Stats", "TokenFetcher", diff --git a/franklinwh/client.py b/franklinwh/client.py index 83e5539..c8967c6 100644 --- a/franklinwh/client.py +++ b/franklinwh/client.py @@ -291,8 +291,18 @@ class DeviceTimeoutException(Exception): class GatewayOfflineException(Exception): """raised when the gateway is offline.""" +class HttpClientFactory: + # If you store a function in an attribute, it becomes a bound method + factory = (lambda: httpx.AsyncClient(http2=True),) -class TokenFetcher: + @classmethod + def set_client_factory(cls, factory): + cls.factory = (factory,) + + def get_client(self): + return self.factory[0]() + +class TokenFetcher(HttpClientFactory): """Fetches and refreshes authentication tokens for FranklinWH API.""" def __init__(self, username: str, password: str) -> None: @@ -306,27 +316,30 @@ async def get_token(self): Store the intermediate account information in self.info. """ - self.info = await TokenFetcher._login(self.username, self.password) + self.info = await self.fetch_token() return self.info["token"] @staticmethod async def login(username: str, password: str): """Log in to the FranklinWH API and retrieve an authentication token.""" - return (await TokenFetcher._login(username, password))["token"] + await TokenFetcher(username, password).get_token() @staticmethod async def _login(username: str, password: str) -> dict: + await TokenFetcher(username, password).get_token() + + async def fetch_token(self): """Log in to the FranklinWH API and retrieve account information.""" url = ( DEFAULT_URL_BASE + "hes-gateway/terminal/initialize/appUserOrInstallerLogin" ) form = { - "account": username, - "password": hashlib.md5(bytes(password, "ascii")).hexdigest(), + "account": self.username, + "password": hashlib.md5(bytes(self.password, "ascii")).hexdigest(), "lang": "en_US", "type": 1, } - async with httpx.AsyncClient(http2=True) as client: + async with self.get_client() as client: res = await client.post(url, data=form, timeout=10) res.raise_for_status() js = res.json() @@ -349,7 +362,7 @@ async def retry(func, filter, refresh_func): return await func() -class Client: +class Client(HttpClientFactory): """Client for interacting with FranklinWH gateway API.""" def __init__( @@ -361,7 +374,7 @@ def __init__( self.url_base = url_base self.token = "" self.snno = 0 - self.session = httpx.AsyncClient(http2=True) + self.session = self.get_client() # to enable detailed logging add this to configuration.yaml: # logger: @@ -369,6 +382,8 @@ def __init__( # franklinwh: debug logger = logging.getLogger("franklinwh") + logger.warning("Session class: %s" % type(self.session)) + self.logger = logger if logger.isEnabledFor(logging.DEBUG): async def debug_request(request: httpx.Request): @@ -397,14 +412,6 @@ async def debug_response(response: httpx.Response): ) return response - self.logger = logger - self.session = httpx.AsyncClient( - http2=True, - event_hooks={ - "request": [debug_request], - "response": [debug_response], - }, - ) # TODO(richo) Setup timeouts and deal with them gracefully. async def _post(self, url, payload, params: dict | None = None): @@ -572,6 +579,7 @@ async def get_stats(self) -> Stats: This includes instantaneous measurements for current power, as well as totals for today (in local time) """ + self.logger.warning("get_stats: Session class: %s" % type(self.session)) data = await self._status() grid_status: GridStatus = GridStatus.NORMAL if "offgridreason" in data: