Skip to content

Commit

Permalink
2024 codestyle and modernization (#88)
Browse files Browse the repository at this point in the history
* Update constants

* Add host to gateway device

* Add host to gateway device 2

* Add typing to device_tracker

* Move coordinator to a new file

* Add more typing

* Add MAC address

* Better type entry data

* Bugfix

* Add reboot button as entity
  • Loading branch information
iMicknl authored Jan 7, 2024
1 parent 10edb3f commit 0acc32e
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 165 deletions.
125 changes: 44 additions & 81 deletions custom_components/sagemcom_fast/__init__.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,53 @@
"""The Sagemcom integration."""
import asyncio
"""The Sagemcom F@st integration."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
import logging

from aiohttp.client_exceptions import ClientError
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_SOURCE,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, service
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from sagemcom_api.client import SagemcomClient
from sagemcom_api.enums import EncryptionMethod
from sagemcom_api.exceptions import (
AccessRestrictionException,
AuthenticationException,
LoginTimeoutException,
MaximumSessionCountException,
UnauthorizedException,
)
from sagemcom_api.models import DeviceInfo as GatewayDeviceInfo

from .const import (
CONF_ENCRYPTION_METHOD,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
LOGGER,
PLATFORMS,
)
from .coordinator import SagemcomDataUpdateCoordinator

from .const import CONF_ENCRYPTION_METHOD, DEFAULT_SCAN_INTERVAL, DOMAIN
from .device_tracker import SagemcomDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["device_tracker"]

SERVICE_REBOOT = "reboot"


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Sagemcom component."""

hass.data.setdefault(DOMAIN, {})
@dataclass
class HomeAssistantSagemcomFastData:
"""SagemcomFast data stored in the Home Assistant data object."""

return True
coordinator: SagemcomDataUpdateCoordinator
gateway: GatewayDeviceInfo


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Sagemcom from a config entry."""

"""Set up Sagemcom F@st from a config entry."""
host = entry.data[CONF_HOST]
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
Expand All @@ -68,37 +67,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

try:
await client.login()
except AccessRestrictionException:
_LOGGER.error("access_restricted")
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH},
data=entry.data,
)
)
return False
except (AuthenticationException, UnauthorizedException):
_LOGGER.error("invalid_auth")
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_REAUTH},
data=entry.data,
)
)
return False
except AccessRestrictionException as exception:
LOGGER.error("Access restricted")
raise ConfigEntryAuthFailed("Access restricted") from exception
except (AuthenticationException, UnauthorizedException) as exception:
LOGGER.error("Invalid_auth")
raise ConfigEntryAuthFailed("Invalid credentials") from exception
except (TimeoutError, ClientError) as exception:
_LOGGER.error("Failed to connect")
LOGGER.error("Failed to connect")
raise ConfigEntryNotReady("Failed to connect") from exception
except MaximumSessionCountException as exception:
_LOGGER.error("Maximum session count reached")
LOGGER.error("Maximum session count reached")
raise ConfigEntryNotReady("Maximum session count reached") from exception
except LoginTimeoutException:
_LOGGER.error("Request timed-out")
return False
except Exception as exception: # pylint: disable=broad-except
_LOGGER.exception(exception)
LOGGER.exception(exception)
return False

try:
Expand All @@ -110,18 +92,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

coordinator = SagemcomDataUpdateCoordinator(
hass,
_LOGGER,
LOGGER,
name="sagemcom_hosts",
client=client,
update_interval=timedelta(seconds=update_interval),
)

await coordinator.async_refresh()
await coordinator.async_config_entry_first_refresh()

hass.data[DOMAIN][entry.entry_id] = {
"coordinator": coordinator,
"update_listener": entry.add_update_listener(update_listener),
}
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantSagemcomFastData(
coordinator=coordinator, gateway=gateway
)

# Create gateway device in Home Assistant
device_registry = hass.helpers.device_registry.async_get(hass)
Expand All @@ -134,36 +115,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
name=f"{gateway.manufacturer} {gateway.model_number}",
model=gateway.model_name,
sw_version=gateway.software_version,
configuration_url=f"{'https' if ssl else 'http'}://{host}",
)

# Register components
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

# Handle gateway device services
async def async_command_reboot(call):
"""Handle reboot service call."""
await client.reboot()

service.async_register_admin_service(
hass, DOMAIN, SERVICE_REBOOT, async_command_reboot
)
entry.async_on_unload(entry.add_update_listener(update_listener))

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""

unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN][entry.entry_id]["update_listener"]()
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
Expand All @@ -172,9 +135,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
"""Update when entry options update."""
if entry.options[CONF_SCAN_INTERVAL]:
coordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"]
coordinator.update_interval = timedelta(
data: HomeAssistantSagemcomFastData = hass.data[DOMAIN][entry.entry_id]
data.coordinator.update_interval = timedelta(
seconds=entry.options[CONF_SCAN_INTERVAL]
)

await coordinator.async_refresh()
await data.coordinator.async_refresh()
53 changes: 53 additions & 0 deletions custom_components/sagemcom_fast/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Support for Sagencom F@st buttons."""
from __future__ import annotations

from homeassistant.components.button import ButtonDeviceClass, ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from sagemcom_api.client import SagemcomClient
from sagemcom_api.models import DeviceInfo as GatewayDeviceInfo

from . import HomeAssistantSagemcomFastData
from .const import DOMAIN


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Sagemcom F@st button from a config entry."""
data: HomeAssistantSagemcomFastData = hass.data[DOMAIN][entry.entry_id]
entities: list[ButtonEntity] = []
entities.append(SagemcomFastRebootButton(data.gateway, data.coordinator.client))

async_add_entities(entities)


class SagemcomFastRebootButton(ButtonEntity):
"""Representation of an Sagemcom F@st Button."""

_attr_has_entity_name = True
_attr_name = "Reboot"
_attr_device_class = ButtonDeviceClass.RESTART
_attr_entity_category = EntityCategory.CONFIG

def __init__(self, gateway: GatewayDeviceInfo, client: SagemcomClient) -> None:
"""Initialize the button."""
self.gateway = gateway
self.client = client
self._attr_unique_id = f"{self.gateway.serial_number}_reboot"

async def async_press(self) -> None:
"""Handle the button press."""
await self.client.reboot()

@property
def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity."""
return DeviceInfo(
identifiers={(DOMAIN, self.gateway.serial_number)},
)
31 changes: 21 additions & 10 deletions custom_components/sagemcom_fast/const.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
"""Constants for the Sagemcom integration."""
DOMAIN = "sagemcom_fast"
"""Constants for the Sagemcom F@st integration."""
from __future__ import annotations

CONF_ENCRYPTION_METHOD = "encryption_method"
CONF_TRACK_WIRELESS_CLIENTS = "track_wireless_clients"
CONF_TRACK_WIRED_CLIENTS = "track_wired_clients"
import logging
from typing import Final

DEFAULT_TRACK_WIRELESS_CLIENTS = True
DEFAULT_TRACK_WIRED_CLIENTS = True
from homeassistant.const import Platform

ATTR_MANUFACTURER = "Sagemcom"
LOGGER: logging.Logger = logging.getLogger(__package__)

MIN_SCAN_INTERVAL = 10
DEFAULT_SCAN_INTERVAL = 10
DOMAIN: Final = "sagemcom_fast"

CONF_ENCRYPTION_METHOD: Final = "encryption_method"
CONF_TRACK_WIRELESS_CLIENTS: Final = "track_wireless_clients"
CONF_TRACK_WIRED_CLIENTS: Final = "track_wired_clients"

DEFAULT_TRACK_WIRELESS_CLIENTS: Final = True
DEFAULT_TRACK_WIRED_CLIENTS: Final = True

ATTR_MANUFACTURER: Final = "Sagemcom"

MIN_SCAN_INTERVAL: Final = 10
DEFAULT_SCAN_INTERVAL: Final = 10

PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER, Platform.BUTTON]
56 changes: 56 additions & 0 deletions custom_components/sagemcom_fast/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Helpers to help coordinate updates."""
from __future__ import annotations

from datetime import timedelta
import logging

import async_timeout
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from sagemcom_api.client import SagemcomClient
from sagemcom_api.models import Device


class SagemcomDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Sagemcom data."""

def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
*,
name: str,
client: SagemcomClient,
update_interval: timedelta | None = None,
):
"""Initialize update coordinator."""
super().__init__(
hass,
logger,
name=name,
update_interval=update_interval,
)
self.data = {}
self.hosts: dict[str, Device] = {}
self.client = client

async def _async_update_data(self) -> dict[str, Device]:
"""Update hosts data."""
try:
async with async_timeout.timeout(10):
try:
await self.client.login()
hosts = await self.client.get_hosts(only_active=True)
finally:
await self.client.logout()

"""Mark all device as non-active."""
for idx, host in self.hosts.items():
host.active = False
self.hosts[idx] = host
for host in hosts:
self.hosts[host.id] = host

return self.hosts
except Exception as exception:
raise UpdateFailed(f"Error communicating with API: {exception}")
Loading

0 comments on commit 0acc32e

Please sign in to comment.