Skip to content

Commit

Permalink
Fixes for offline devices
Browse files Browse the repository at this point in the history
  • Loading branch information
cdpuk committed Dec 18, 2022
1 parent c67160d commit 23ff791
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 39 deletions.
32 changes: 16 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
---
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
rev: v3.2.2
hooks:
- id: pyupgrade
args: [--py39-plus]
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 22.10.0
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((custom_components|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.1.0
rev: v2.2.2
hooks:
- id: codespell
args:
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
- repo: https://gitlab.com/PyCQA/flake8
rev: 4.0.1
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies:
- pycodestyle==2.8.0
- pyflakes==2.4.0
- pycodestyle==2.10.0
- pyflakes==3.0.1
- flake8-docstrings==1.6.0
- pydocstyle==6.1.1
- flake8-comprehensions==3.7.0
- flake8-noqa==1.2.1
- mccabe==0.6.1
- flake8-comprehensions==3.10.1
- flake8-noqa==1.3.0
- mccabe==0.7.0
files: ^(custom_components|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.0
rev: 1.7.4
hooks:
- id: bandit
args:
Expand All @@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(custom_components|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.10.0
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand All @@ -56,16 +56,16 @@ repos:
- id: check-json
exclude: (.vscode|.devcontainer)
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.3
rev: v1.28.0
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1
rev: v2.7.1
hooks:
- id: prettier
stages: [manual]
- repo: https://github.com/cdce8p/python-typing-update
rev: v0.3.5
rev: v0.5.0
hooks:
# Run `python-typing-update` hook manually from time to time
# to update python typing syntax.
Expand All @@ -78,7 +78,7 @@ repos:
- --keep-updates
files: ^(custom_components|tests|script)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.950
rev: v0.942
hooks:
- id: mypy
args:
Expand Down
12 changes: 8 additions & 4 deletions custom_components/bestway/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""The bestway integration."""
from __future__ import annotations

from datetime import timedelta
from datetime import datetime, timedelta
from logging import getLogger

import async_timeout
Expand Down Expand Up @@ -37,14 +37,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
password = entry.data.get(CONF_PASSWORD)
api_root = entry.data.get(CONF_API_ROOT)
user_token = entry.data.get(CONF_USER_TOKEN)
user_token_expiry = entry.data.get(CONF_USER_TOKEN_EXPIRY)
user_token_expiry = int(entry.data.get(CONF_USER_TOKEN_EXPIRY))

session = async_get_clientsession(hass)

if user_token:
# Check for an auth token
# If we have one that expires within 30 days, refresh it
expiry_cutoff = (datetime.now() + timedelta(days=30)).timestamp()

if user_token and expiry_cutoff < user_token_expiry:
_LOGGER.info("Reusing existing access token")
else:
_LOGGER.info("No saved token found - requesting a new one")
_LOGGER.info("Requesting a new auth token")
try:
token = await BestwayApi.get_user_token(
session, username, password, api_root
Expand Down
32 changes: 22 additions & 10 deletions custom_components/bestway/bestway.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class BestwayDeviceReport:
"""A device report, which combines device metadata with a current status snapshot."""

device: BestwayDevice
status: BestwayDeviceStatus
status: BestwayDeviceStatus | None


class BestwayException(Exception):
Expand All @@ -90,6 +90,10 @@ class BestwayAuthException(BestwayException):
"""An authentication error."""


class BestwayTokenInvalidException(BestwayAuthException):
"""Auth token is invalid or expired."""


class BestwayUserDoesNotExistException(BestwayAuthException):
"""User does not exist."""

Expand All @@ -110,6 +114,8 @@ async def raise_for_status(response: ClientResponse) -> None:
response.raise_for_status()

error_code = api_error.get("error_code", 0)
if error_code == 9004:
raise BestwayTokenInvalidException()
if error_code == 9005:
raise BestwayUserDoesNotExistException()
if error_code == 9042:
Expand Down Expand Up @@ -175,14 +181,10 @@ async def _get_bindings(self) -> list[BestwayDevice]:
headers = dict(_HEADERS)
headers["X-Gizwits-User-token"] = self._user_token
api_data = await self._do_get(f"{self._api_root}/app/bindings", headers)
return list(
map(
lambda raw: BestwayDevice(
raw["did"], raw["dev_alias"], raw["product_name"]
),
api_data["devices"],
)
)
return [
BestwayDevice(raw["did"], raw["dev_alias"], raw["product_name"])
for raw in api_data["devices"]
]

async def fetch_data(self) -> dict[str, BestwayDeviceReport]:
"""Fetch the latest data for all devices."""
Expand All @@ -197,9 +199,19 @@ async def fetch_data(self) -> dict[str, BestwayDeviceReport]:
f"{self._api_root}/app/devdata/{did}/latest", _HEADERS
)

# Get the age of the data according to the API
api_update_timestamp = latest_data["updated_at"]

# Zero indicates the device is offline
# This has been observed after a device was offline for a few months
if api_update_timestamp == 0:
# In testing, the 'attrs' dictionary has been observed to be empty
_LOGGER.debug("No data available for device %s", did)
results[did] = BestwayDeviceReport(device_info, None)
continue

# Work out whether the received API update is more recent than the
# locally cached state
api_update_timestamp = latest_data["updated_at"]
local_update_timestamp = 0
if cached_state := self._local_state_cache.get(did):
local_update_timestamp = cached_state.timestamp
Expand Down
3 changes: 2 additions & 1 deletion custom_components/bestway/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Binary sensor platform."""
from __future__ import annotations

from typing import Any, Mapping
from collections.abc import Mapping
from typing import Any

from homeassistant.components.binary_sensor import (
DEVICE_CLASS_CONNECTIVITY,
Expand Down
7 changes: 3 additions & 4 deletions custom_components/bestway/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from . import BestwayUpdateCoordinator
from .bestway import BestwayDevice, BestwayDeviceReport, BestwayDeviceStatus
from .bestway import BestwayDeviceStatus
from .const import DOMAIN


Expand All @@ -28,7 +28,7 @@ def __init__(
def device_info(self) -> DeviceInfo:
"""Device information for the spa providing this entity."""

device_info: BestwayDevice = self.coordinator.data[self.device_id].device
device_info = self.coordinator.data[self.device_id].device

return DeviceInfo(
identifiers={(DOMAIN, self.device_id)},
Expand All @@ -40,8 +40,7 @@ def device_info(self) -> DeviceInfo:
@property
def device_status(self) -> BestwayDeviceStatus | None:
"""Get status data for the spa providing this entity."""
device_report: BestwayDeviceReport = self.coordinator.data.get(self.device_id)
if device_report:
if (device_report := self.coordinator.data.get(self.device_id)) is not None:
return device_report.status
return None

Expand Down
4 changes: 2 additions & 2 deletions custom_components/bestway/switch.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Switch platform support."""
from __future__ import annotations

from collections.abc import Callable
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any, Awaitable
from typing import Any

from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
Expand Down
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
homeassistant==2022.5.5
homeassistant
pre-commit
mypy
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pytest-homeassistant-custom-component==0.9.3
pytest-homeassistant-custom-component

0 comments on commit 23ff791

Please sign in to comment.