Skip to content

Commit 456ed3d

Browse files
authored
Maintenance update (August) (#150)
1 parent 14831ce commit 456ed3d

File tree

16 files changed

+106
-65
lines changed

16 files changed

+106
-65
lines changed

.devcontainer/devcontainer.json

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
22
{
3-
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
3+
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
44
"name": "ha-sagemcom-fast",
55
"forwardPorts": [
66
8123
@@ -12,22 +12,19 @@
1212
}
1313
},
1414
"features": {
15-
"ghcr.io/devcontainers-contrib/features/ffmpeg-apt-get:1": {}
15+
"ghcr.io/devcontainers-contrib/features/ffmpeg-apt-get:1": {} // required for Home Assistant
1616
},
17-
"postCreateCommand": "pip install -r requirements_dev.txt && pre-commit install && pre-commit install-hooks",
17+
"postCreateCommand": "pip install -r requirements_dev.txt && pre-commit install && pre-commit install-hooks && sudo apt-get update && sudo apt-get install -y libpcap-dev libturbojpeg0",
1818
"containerEnv": {
1919
"DEVCONTAINER": "1"
2020
},
2121
"remoteUser": "vscode",
2222
"customizations": {
2323
"vscode": {
2424
"extensions": [
25-
"ms-python.vscode-pylance",
2625
"ms-python.python",
27-
"redhat.vscode-yaml",
28-
"esbenp.prettier-vscode",
29-
"GitHub.vscode-pull-request-github",
30-
"GitHub.copilot"
26+
"GitHub.copilot",
27+
"GitHub.copilot-chat"
3128
],
3229
"settings": {
3330
"python.pythonPath": "/usr/local/bin/python",

.pre-commit-config.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
11
repos:
22
- repo: https://github.com/asottile/pyupgrade
3-
rev: v2.31.1
3+
rev: v3.17.0
44
hooks:
55
- id: pyupgrade
66
args: [--py37-plus]
77
- repo: https://github.com/psf/black
8-
rev: 22.3.0
8+
rev: 24.8.0
99
hooks:
1010
- id: black
1111
args:
1212
- --safe
1313
- --quiet
1414
files: ^((custom_components)/.+)?[^/]+\.py$
1515
- repo: https://github.com/codespell-project/codespell
16-
rev: v2.1.0
16+
rev: v2.3.0
1717
hooks:
1818
- id: codespell
1919
args:
2020
- --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing
21-
- --skip="./.*,*.csv,*.json,*.md"
21+
- --skip="./.*,*.csv,*.json,*.md",setup.cfg
2222
- --quiet-level=2
2323
exclude_types: [csv, json]
2424
- repo: https://github.com/PyCQA/flake8
25-
rev: 3.9.2
25+
rev: 7.1.1
2626
hooks:
2727
- id: flake8
2828
additional_dependencies:
2929
- flake8-docstrings==1.5.0
3030
- pydocstyle==5.1.1
3131
files: ^(custom_components)/.+\.py$
3232
- repo: https://github.com/PyCQA/isort
33-
rev: 5.12.0
33+
rev: 5.13.2
3434
hooks:
3535
- id: isort
3636
- repo: https://github.com/adrienverge/yamllint.git
37-
rev: v1.26.3
37+
rev: v1.35.1
3838
hooks:
3939
- id: yamllint

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ https://github.com/imicknl/ha-sagemcom-fast
3737

3838
This integration can only be configured via the Config Flow. Go to `Configuration -> Integrations -> Add Integration` and choose Sagemcom F@st. The prompt will ask you for your credentials. Please note that some routers require authentication, where others can login with `guest` username and an empty password.
3939

40-
The encryption method differs per device. Please refer to the table below to understand which option to select. If your device is not listed, please try both methods one by one.
40+
The first login might take a longer time (up to a minute), since we will try to retrieve the encryption method used by your router.
4141

4242
## Supported devices
4343

custom_components/sagemcom_fast/__init__.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""The Sagemcom F@st integration."""
2+
23
from __future__ import annotations
34

45
from dataclasses import dataclass
@@ -23,6 +24,7 @@
2324
from sagemcom_api.exceptions import (
2425
AccessRestrictionException,
2526
AuthenticationException,
27+
LoginRetryErrorException,
2628
MaximumSessionCountException,
2729
UnauthorizedException,
2830
)
@@ -57,11 +59,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
5759

5860
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=verify_ssl)
5961
client = SagemcomClient(
60-
host,
61-
username,
62-
password,
63-
EncryptionMethod(encryption_method),
64-
session,
62+
host=host,
63+
username=username,
64+
password=password,
65+
authentication_method=EncryptionMethod(encryption_method),
66+
session=session,
6567
ssl=ssl,
6668
)
6769

@@ -73,12 +75,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
7375
except (AuthenticationException, UnauthorizedException) as exception:
7476
LOGGER.error("Invalid_auth")
7577
raise ConfigEntryAuthFailed("Invalid credentials") from exception
76-
except (TimeoutError, ClientError) as exception:
78+
except (TimeoutError, ClientError, ConnectionError) as exception:
7779
LOGGER.error("Failed to connect")
7880
raise ConfigEntryNotReady("Failed to connect") from exception
7981
except MaximumSessionCountException as exception:
8082
LOGGER.error("Maximum session count reached")
8183
raise ConfigEntryNotReady("Maximum session count reached") from exception
84+
except LoginRetryErrorException as exception:
85+
LOGGER.error("Too many login attempts. Retry later.")
86+
raise ConfigEntryNotReady(
87+
"Too many login attempts. Retry later."
88+
) from exception
8289
except Exception as exception: # pylint: disable=broad-except
8390
LOGGER.exception(exception)
8491
return False
@@ -98,8 +105,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
98105
update_interval=timedelta(seconds=update_interval),
99106
)
100107

101-
await coordinator.async_config_entry_first_refresh()
102-
103108
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantSagemcomFastData(
104109
coordinator=coordinator, gateway=gateway
105110
)
@@ -118,6 +123,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
118123
configuration_url=f"{'https' if ssl else 'http'}://{host}",
119124
)
120125

126+
await coordinator.async_config_entry_first_refresh()
127+
121128
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
122129
entry.async_on_unload(entry.add_update_listener(update_listener))
123130

custom_components/sagemcom_fast/button.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Support for Sagencom F@st buttons."""
2+
23
from __future__ import annotations
34

45
from homeassistant.components.button import ButtonDeviceClass, ButtonEntity

custom_components/sagemcom_fast/config_flow.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Config flow for Sagemcom integration."""
2-
import logging
32

43
from aiohttp import ClientError
54
from homeassistant import config_entries
@@ -13,64 +12,56 @@
1312
from homeassistant.core import callback
1413
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1514
from sagemcom_api.client import SagemcomClient
16-
from sagemcom_api.enums import EncryptionMethod
1715
from sagemcom_api.exceptions import (
1816
AccessRestrictionException,
1917
AuthenticationException,
18+
LoginRetryErrorException,
2019
LoginTimeoutException,
2120
MaximumSessionCountException,
21+
UnsupportedHostException,
2222
)
2323
import voluptuous as vol
2424

25-
from .const import CONF_ENCRYPTION_METHOD, DOMAIN
25+
from .const import CONF_ENCRYPTION_METHOD, DOMAIN, LOGGER
2626
from .options_flow import OptionsFlow
2727

28-
_LOGGER = logging.getLogger(__name__)
29-
30-
ENCRYPTION_METHODS = [item.value for item in EncryptionMethod]
31-
32-
DATA_SCHEMA = vol.Schema(
33-
{
34-
vol.Required(CONF_HOST): str,
35-
vol.Optional(CONF_USERNAME): str,
36-
vol.Optional(CONF_PASSWORD): str,
37-
vol.Required(CONF_ENCRYPTION_METHOD): vol.In(ENCRYPTION_METHODS),
38-
vol.Required(CONF_SSL, default=False): bool,
39-
vol.Required(CONF_VERIFY_SSL, default=False): bool,
40-
}
41-
)
42-
4328

4429
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
4530
"""Handle a config flow for Sagemcom."""
4631

4732
VERSION = 1
4833
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
4934

35+
_host: str | None = None
36+
_username: str | None = None
37+
5038
async def async_validate_input(self, user_input):
5139
"""Validate user credentials."""
52-
username = user_input.get(CONF_USERNAME) or ""
40+
self._username = user_input.get(CONF_USERNAME) or ""
5341
password = user_input.get(CONF_PASSWORD) or ""
54-
host = user_input[CONF_HOST]
55-
encryption_method = user_input[CONF_ENCRYPTION_METHOD]
42+
self._host = user_input[CONF_HOST]
5643
ssl = user_input[CONF_SSL]
5744

5845
session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL])
5946

6047
client = SagemcomClient(
61-
host,
62-
username,
63-
password,
64-
EncryptionMethod(encryption_method),
65-
session,
48+
host=self._host,
49+
username=self._username,
50+
password=password,
51+
session=session,
6652
ssl=ssl,
6753
)
6854

55+
user_input[CONF_ENCRYPTION_METHOD] = await client.get_encryption_method()
56+
LOGGER.debug(
57+
"Detected encryption method: %s", user_input[CONF_ENCRYPTION_METHOD]
58+
)
59+
6960
await client.login()
7061
await client.logout()
7162

7263
return self.async_create_entry(
73-
title=host,
64+
title=self._host,
7465
data=user_input,
7566
)
7667

@@ -89,18 +80,32 @@ async def async_step_user(self, user_input=None):
8980
errors["base"] = "access_restricted"
9081
except AuthenticationException:
9182
errors["base"] = "invalid_auth"
92-
except (TimeoutError, ClientError):
83+
except (TimeoutError, ClientError, ConnectionError):
9384
errors["base"] = "cannot_connect"
9485
except LoginTimeoutException:
9586
errors["base"] = "login_timeout"
9687
except MaximumSessionCountException:
9788
errors["base"] = "maximum_session_count"
89+
except LoginRetryErrorException:
90+
errors["base"] = "login_retry_error"
91+
except UnsupportedHostException:
92+
errors["base"] = "unsupported_host"
9893
except Exception as exception: # pylint: disable=broad-except
9994
errors["base"] = "unknown"
100-
_LOGGER.exception(exception)
95+
LOGGER.exception(exception)
10196

10297
return self.async_show_form(
103-
step_id="user", data_schema=DATA_SCHEMA, errors=errors
98+
step_id="user",
99+
data_schema=vol.Schema(
100+
{
101+
vol.Required(CONF_HOST, default=self._host): str,
102+
vol.Optional(CONF_USERNAME, default=self._username): str,
103+
vol.Optional(CONF_PASSWORD): str,
104+
vol.Required(CONF_SSL, default=False): bool,
105+
vol.Required(CONF_VERIFY_SSL, default=False): bool,
106+
}
107+
),
108+
errors=errors,
104109
)
105110

106111
@staticmethod

custom_components/sagemcom_fast/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Constants for the Sagemcom F@st integration."""
2+
23
from __future__ import annotations
34

45
import logging

custom_components/sagemcom_fast/coordinator.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
"""Helpers to help coordinate updates."""
2+
23
from __future__ import annotations
34

5+
import asyncio
46
from datetime import timedelta
57
import logging
68

9+
from aiohttp.client_exceptions import ClientError
710
import async_timeout
811
from homeassistant.core import HomeAssistant
12+
from homeassistant.exceptions import ConfigEntryAuthFailed
913
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1014
from sagemcom_api.client import SagemcomClient
15+
from sagemcom_api.exceptions import (
16+
AccessRestrictionException,
17+
AuthenticationException,
18+
LoginRetryErrorException,
19+
MaximumSessionCountException,
20+
UnauthorizedException,
21+
)
1122
from sagemcom_api.models import Device
1223

1324

@@ -33,13 +44,15 @@ def __init__(
3344
self.data = {}
3445
self.hosts: dict[str, Device] = {}
3546
self.client = client
47+
self.logger = logger
3648

3749
async def _async_update_data(self) -> dict[str, Device]:
3850
"""Update hosts data."""
3951
try:
4052
async with async_timeout.timeout(10):
4153
try:
4254
await self.client.login()
55+
await asyncio.sleep(1)
4356
hosts = await self.client.get_hosts(only_active=True)
4457
finally:
4558
await self.client.logout()
@@ -52,5 +65,18 @@ async def _async_update_data(self) -> dict[str, Device]:
5265
self.hosts[host.id] = host
5366

5467
return self.hosts
68+
except AccessRestrictionException as exception:
69+
raise ConfigEntryAuthFailed("Access restricted") from exception
70+
except (AuthenticationException, UnauthorizedException) as exception:
71+
raise ConfigEntryAuthFailed("Invalid credentials") from exception
72+
except (TimeoutError, ClientError, ConnectionError) as exception:
73+
raise UpdateFailed("Failed to connect") from exception
74+
except LoginRetryErrorException as exception:
75+
raise UpdateFailed(
76+
"Too many login attempts. Retrying later."
77+
) from exception
78+
except MaximumSessionCountException as exception:
79+
raise UpdateFailed("Maximum session count reached") from exception
5580
except Exception as exception:
56-
raise UpdateFailed(f"Error communicating with API: {exception}")
81+
self.logger.exception(exception)
82+
raise UpdateFailed(f"Error communicating with API: {str(exception)}")

custom_components/sagemcom_fast/device_tracker.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Support for device tracking of client router."""
2+
23
from __future__ import annotations
34

45
from homeassistant.components.device_tracker import SourceType

custom_components/sagemcom_fast/diagnostics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Provides diagnostics for Overkiz."""
2+
23
from __future__ import annotations
34

45
from typing import Any

0 commit comments

Comments
 (0)