Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion franklinwh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

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",
"AccessoryType",
"CachingThread",
"Client",
"GridStatus",
"HttpClientfactory"
"Mode",
"Stats",
"TokenFetcher",
Expand Down
40 changes: 24 additions & 16 deletions franklinwh/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()
Expand All @@ -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__(
Expand All @@ -361,14 +374,16 @@ 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:
# logs:
# 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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down