Skip to content

Commit

Permalink
Implemented state change and make scan interval configurable (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
arunpoudel authored Apr 9, 2021
1 parent ad25981 commit f733348
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 33 deletions.
40 changes: 36 additions & 4 deletions custom_components/sagemcom_fast/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""The Sagemcom integration."""
import asyncio
from datetime import timedelta
import logging

from aiohttp.client_exceptions import ClientError
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SOURCE, CONF_USERNAME
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_SOURCE,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, service
Expand All @@ -18,7 +25,8 @@
UnauthorizedException,
)

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

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -80,9 +88,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.exception(exception)
return False

update_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)

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

await coordinator.async_refresh()

hass.data[DOMAIN][entry.entry_id] = {
"client": client,
"devices": await client.get_hosts(only_active=True),
"coordinator": coordinator,
"update_listener": entry.add_update_listener(update_listener),
}

# Create gateway device in Home Assistant
Expand Down Expand Up @@ -129,6 +149,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)
)
if unload_ok:
hass.data[DOMAIN][entry.entry_id]["update_listener"]()
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


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(
seconds=entry.options[CONF_SCAN_INTERVAL]
)

await coordinator.async_refresh()
11 changes: 9 additions & 2 deletions custom_components/sagemcom_fast/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from aiohttp import ClientError
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from sagemcom_api.client import SagemcomClient
from sagemcom_api.enums import EncryptionMethod
from sagemcom_api.exceptions import (
Expand All @@ -13,8 +14,8 @@
)
import voluptuous as vol

from .const import CONF_ENCRYPTION_METHOD
from .const import DOMAIN # pylint: disable=unused-import
from .const import CONF_ENCRYPTION_METHOD, DOMAIN
from .options_flow import OptionsFlow

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -77,3 +78,9 @@ async def async_step_user(self, user_input=None):
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get options flow for this handler."""
return OptionsFlow(config_entry)
3 changes: 3 additions & 0 deletions custom_components/sagemcom_fast/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
DEFAULT_TRACK_WIRED_CLIENTS = True

ATTR_MANUFACTURER = "Sagemcom"

MIN_SCAN_INTERVAL = 10
DEFAULT_SCAN_INTERVAL = 10
102 changes: 75 additions & 27 deletions custom_components/sagemcom_fast/device_tracker.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
"""Support for device tracking of client router."""

from datetime import timedelta
import logging
from typing import Any, Dict
from typing import Any, Dict, Optional

import async_timeout
from homeassistant.components.device_tracker import SOURCE_TYPE_ROUTER
from homeassistant.components.device_tracker.config_entry import ScannerEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from sagemcom_api.client import SagemcomClient
from sagemcom_api.models import Device

from .const import DOMAIN

Expand All @@ -15,42 +25,80 @@
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up from config entry."""

# TODO Handle status of disconnected devices
entities = []
client = hass.data[DOMAIN][config_entry.entry_id]["client"]

new_devices = await client.get_hosts(only_active=True)

for device in new_devices:
entity = SagemcomScannerEntity(device, config_entry.entry_id)
entities.append(entity)

async_add_entities(entities, update_before_add=True)


class SagemcomScannerEntity(ScannerEntity, RestoreEntity):
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]

async_add_entities(
SagemcomScannerEntity(coordinator, idx, config_entry.entry_id)
for idx, device in coordinator.data.items()
)


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

def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
*,
name: str,
client: SagemcomClient,
update_interval: Optional[timedelta] = 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):
hosts = await self._client.get_hosts(only_active=True)
"""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}")


class SagemcomScannerEntity(ScannerEntity, RestoreEntity, CoordinatorEntity):
"""Sagemcom router scanner entity."""

def __init__(self, device, parent):
def __init__(self, coordinator, idx, parent):
"""Initialize the device."""
self._device = device
super().__init__(coordinator)
self._idx = idx
self._via_device = parent

super().__init__()
@property
def device(self):
"""Return the device entity."""
return self.coordinator.data[self._idx]

@property
def name(self) -> str:
"""Return the name of the device."""
return (
self._device.name
or self._device.user_friendly_name
or self._device.mac_address
self.device.name
or self.device.user_friendly_name
or self.device.mac_address
)

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._device.id
return self.device.id

@property
def source_type(self) -> str:
Expand All @@ -60,7 +108,7 @@ def source_type(self) -> str:
@property
def is_connected(self) -> bool:
"""Get whether the entity is connected."""
return self._device.active or False
return self.device.active or False

@property
def device_info(self):
Expand All @@ -74,21 +122,21 @@ def device_info(self):
@property
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the state attributes of the device."""
attr = {"interface_type": self._device.interface_type}
attr = {"interface_type": self.device.interface_type}

return attr

@property
def ip_address(self) -> str:
"""Return the primary ip address of the device."""
return self._device.ip_address or None
return self.device.ip_address or None

@property
def mac_address(self) -> str:
"""Return the mac address of the device."""
return self._device.phys_address
return self.device.phys_address

@property
def hostname(self) -> str:
"""Return hostname of the device."""
return self._device.user_host_name or self._device.host_name
return self.device.user_host_name or self.device.host_name
1 change: 1 addition & 0 deletions custom_components/sagemcom_fast/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"domain": "sagemcom_fast",
"name": "Sagemcom F@st",
"version": "0.2.0",
"config_flow": true,
"documentation": "https://github.com/imicknl/ha-sagemcom-fast",
"requirements": [
Expand Down
35 changes: 35 additions & 0 deletions custom_components/sagemcom_fast/options_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Options flow for Sagemcom integration."""

from homeassistant import config_entries
from homeassistant.const import CONF_SCAN_INTERVAL
import homeassistant.helpers.config_validation as cv
import voluptuous as vol

from .const import DEFAULT_SCAN_INTERVAL, MIN_SCAN_INTERVAL


class OptionsFlow(config_entries.OptionsFlow):
"""Handle a options flow for Sagemcom."""

def __init__(self, config_entry):
"""Initialize Sagemcom options flow."""
self._options = dict(config_entry.options)

async def async_step_init(self, user_input=None):
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_SCAN_INTERVAL,
default=self._options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
): vol.All(cv.positive_int, vol.Clamp(min=MIN_SCAN_INTERVAL))
}
),
)

0 comments on commit f733348

Please sign in to comment.