Skip to content

Commit

Permalink
HACS update to Battery Notes #16
Browse files Browse the repository at this point in the history
  • Loading branch information
BeardedTinker committed Dec 23, 2023
1 parent 4e4ae54 commit 8e46a5d
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 3 deletions.
15 changes: 15 additions & 0 deletions custom_components/battery_notes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.const import __version__ as HA_VERSION # noqa: N812
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from homeassistant.helpers.typing import ConfigType

from .discovery import DiscoveryManager
from .library_coordinator import BatteryNotesLibraryUpdateCoordinator
from .library_updater import (
LibraryUpdaterClient,
)

from .const import (
DOMAIN,
DOMAIN_CONFIG,
PLATFORMS,
CONF_ENABLE_AUTODISCOVERY,
CONF_LIBRARY,
DATA_UPDATE_COORDINATOR,
)

MIN_HA_VERSION = "2023.7"
Expand Down Expand Up @@ -66,6 +72,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
DOMAIN_CONFIG: domain_config,
}

coordinator = BatteryNotesLibraryUpdateCoordinator(
hass=hass,
client=LibraryUpdaterClient(session=async_get_clientsession(hass)),
)

hass.data[DOMAIN][DATA_UPDATE_COORDINATOR] = coordinator

await coordinator.async_refresh()

if domain_config.get(CONF_ENABLE_AUTODISCOVERY):
discovery_manager = DiscoveryManager(hass, config)
await discovery_manager.start_discovery()
Expand Down
5 changes: 5 additions & 0 deletions custom_components/battery_notes/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
CONF_DEVICE_NAME,
CONF_MANUFACTURER,
CONF_MODEL,
DATA_UPDATE_COORDINATOR,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -84,6 +85,10 @@ async def async_step_user(

device_id = user_input[CONF_DEVICE_ID]

coordinator = self.hass.data[DOMAIN][DATA_UPDATE_COORDINATOR]

await coordinator.async_refresh()

device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get(device_id)

Expand Down
3 changes: 3 additions & 0 deletions custom_components/battery_notes/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
CONF_MODEL = "model"
CONF_MANUFACTURER = "manufacturer"
CONF_DEVICE_NAME = "device_name"
CONF_LIBRARY_URL = "https://raw.githubusercontent.com/andrew-codechimp/HA-Battery-Notes/main/custom_components/battery_notes/data/library.json" # pylint: disable=line-too-long

DATA_CONFIGURED_ENTITIES = "configured_entities"
DATA_DISCOVERED_ENTITIES = "discovered_entities"
DATA_DOMAIN_ENTITIES = "domain_entities"
DATA_LIBRARY = "library"
DATA_UPDATE_COORDINATOR = "update_coordinator"
DATA_LIBRARY_LAST_UPDATE = "library_last_update"

PLATFORMS: Final = [
Platform.SENSOR,
Expand Down
1 change: 1 addition & 0 deletions custom_components/battery_notes/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from homeassistant.config_entries import ConfigEntry


async def async_get_config_entry_diagnostics(
entry: ConfigEntry,
) -> dict:
Expand Down
1 change: 1 addition & 0 deletions custom_components/battery_notes/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from homeassistant.helpers.entity import EntityDescription


@dataclass
class BatteryNotesRequiredKeysMixin:
"""Mixin for required keys."""
Expand Down
109 changes: 109 additions & 0 deletions custom_components/battery_notes/library_coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""DataUpdateCoordinator for battery notes library."""
from __future__ import annotations

from datetime import datetime, timedelta
import logging
import json
import os

from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
)

from .library_updater import (
LibraryUpdaterClient,
LibraryUpdaterClientError,
)

from .const import (
DOMAIN,
LOGGER,
DATA_LIBRARY_LAST_UPDATE,
)

_LOGGER = logging.getLogger(__name__)

BUILT_IN_DATA_DIRECTORY = os.path.join(os.path.dirname(__file__), "data")


class BatteryNotesLibraryUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching the library from GitHub."""

def __init__(
self,
hass: HomeAssistant,
client: LibraryUpdaterClient,
) -> None:
"""Initialize."""
self.client = client
super().__init__(
hass=hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(hours=24),
)

async def _async_update_data(self):
"""Update data via library."""

if await self.time_to_update_library() is False:
return

try:
_LOGGER.debug("Getting library updates")

content = await self.client.async_get_data()

if validate_json(content):
json_path = os.path.join(
BUILT_IN_DATA_DIRECTORY,
"library.json",
)

f = open(json_path, mode="w", encoding="utf-8")
f.write(content)

self.hass.data[DOMAIN][DATA_LIBRARY_LAST_UPDATE] = datetime.now()

_LOGGER.debug("Updated library")
else:
_LOGGER.error("Library file is invalid, not updated")

except LibraryUpdaterClientError as exception:
raise UpdateFailed(exception) from exception

async def time_to_update_library(self) -> bool:
"""Check when last updated and if OK to do a new library update."""
try:
if DATA_LIBRARY_LAST_UPDATE in self.hass.data[DOMAIN]:
time_since_last_update = (
datetime.now() - self.hass.data[DOMAIN][DATA_LIBRARY_LAST_UPDATE]
)

time_difference_in_hours = time_since_last_update / timedelta(hours=1)

if time_difference_in_hours < 23:
_LOGGER.debug("Skipping library updates")
return False
return True
except ConfigEntryNotReady:
# Ignore as we are initial load
return True


def validate_json(content: str) -> bool:
"""Check if content is valid json."""
try:
library = json.loads(content)

if "version" not in library:
return False

if library["version"] > 1:
return False
except ValueError:
return False
return True
62 changes: 62 additions & 0 deletions custom_components/battery_notes/library_updater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Sample API Client."""
from __future__ import annotations

import asyncio
import socket

import aiohttp
import async_timeout

from .const import CONF_LIBRARY_URL


class LibraryUpdaterClientError(Exception):
"""Exception to indicate a general API error."""


class LibraryUpdaterClientCommunicationError(LibraryUpdaterClientError):
"""Exception to indicate a communication error."""


class LibraryUpdaterClient:
"""Library downloader."""

def __init__(
self,
session: aiohttp.ClientSession,
) -> None:
"""Client to get latest library file from GitHub."""
self._session = session

async def async_get_data(self) -> any:
"""Get data from the API."""
return await self._api_wrapper(method="get", url=CONF_LIBRARY_URL)

async def _api_wrapper(
self,
method: str,
url: str,
) -> any:
"""Get information from the API."""
try:
async with async_timeout.timeout(10):
response = await self._session.request(
method=method,
url=url,
allow_redirects=True,
)
# response.raise_for_status()
return await response.text()

except asyncio.TimeoutError as exception:
raise LibraryUpdaterClientCommunicationError(
"Timeout error fetching information",
) from exception
except (aiohttp.ClientError, socket.gaierror) as exception:
raise LibraryUpdaterClientCommunicationError(
"Error fetching information",
) from exception
except Exception as exception: # pylint: disable=broad-except
raise LibraryUpdaterClientError(
"Something really wrong happened!"
) from exception
2 changes: 1 addition & 1 deletion custom_components/battery_notes/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"integration_type": "device",
"iot_class": "calculated",
"issue_tracker": "https://github.com/andrew-codechimp/ha-battery-notes/issues",
"version": "1.1.9"
"version": "1.2.0"
}
17 changes: 15 additions & 2 deletions custom_components/battery_notes/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
async_track_state_change_event,
async_track_entity_registry_updated_event,
)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)

from homeassistant.helpers.reload import async_setup_reload_service

Expand All @@ -36,8 +39,11 @@
DOMAIN,
PLATFORMS,
CONF_BATTERY_TYPE,
DATA_UPDATE_COORDINATOR,
)

from .library_coordinator import BatteryNotesLibraryUpdateCoordinator

from .entity import (
BatteryNotesEntityDescription,
)
Expand Down Expand Up @@ -129,9 +135,12 @@ async def async_registry_updated(event: Event) -> None:

device_id = async_add_to_device(hass, config_entry)

coordinator = hass.data[DOMAIN][DATA_UPDATE_COORDINATOR]

entities = [
BatteryNotesTypeSensor(
hass,
coordinator,
typeSensorEntityDescription,
device_id,
f"{config_entry.entry_id}{typeSensorEntityDescription.unique_id_suffix}",
Expand All @@ -149,7 +158,7 @@ async def async_setup_platform(
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)


class BatteryNotesSensor(RestoreSensor, SensorEntity):
class BatteryNotesSensor(RestoreSensor, SensorEntity, CoordinatorEntity):
"""Represents a battery note sensor."""

_attr_should_poll = False
Expand All @@ -158,11 +167,14 @@ class BatteryNotesSensor(RestoreSensor, SensorEntity):
def __init__(
self,
hass,
coordinator: BatteryNotesLibraryUpdateCoordinator,
description: BatteryNotesSensorEntityDescription,
device_id: str,
unique_id: str,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)

device_registry = dr.async_get(hass)

self.entity_description = description
Expand Down Expand Up @@ -219,13 +231,14 @@ class BatteryNotesTypeSensor(BatteryNotesSensor):
def __init__(
self,
hass,
coordinator: BatteryNotesLibraryUpdateCoordinator,
description: BatteryNotesSensorEntityDescription,
device_id: str,
unique_id: str,
battery_type: str | None = None,
) -> None:
"""Initialize the sensor."""
super().__init__(hass, description, device_id, unique_id)
super().__init__(hass, coordinator, description, device_id, unique_id)

self._battery_type = battery_type

Expand Down

0 comments on commit 8e46a5d

Please sign in to comment.