diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml
index f3adfb1..1ee9922 100644
--- a/.github/workflows/hassfest.yaml
+++ b/.github/workflows/hassfest.yaml
@@ -1,7 +1,6 @@
----
-
name: Validate with hassfest
+# yamllint disable-line rule:truthy
on:
push:
pull_request:
diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
index bdad7d1..b0e3f19 100644
--- a/.github/workflows/pre-commit.yaml
+++ b/.github/workflows/pre-commit.yaml
@@ -1,30 +1,15 @@
-name: Linting
+name: pre-commit
+# yamllint disable-line rule:truthy
on:
- push:
- branches:
- - main
- - master
- - dev
pull_request:
+ push:
+ branches: [master]
jobs:
pre-commit:
runs-on: ubuntu-latest
- name: pre-commit
steps:
- - uses: actions/checkout@v3.1.0
- - uses: actions/setup-python@v4.3.0
- with:
- python-version: "3.10"
- - run: |
- pip install --constraint=.github/workflows/constraints.txt pip
- pip install --constraint=.github/workflows/constraints.txt pre-commit
- - uses: actions/cache@v3.0.11
- if: matrix.os != 'windows-latest'
- with:
- path: ~/.cache/pre-commit
- key: ${{ steps.cache_key_prefix.outputs.result }}-${{ hashFiles('.pre-commit-config.yaml') }}
- restore-keys: |
- ${{ steps.cache_key_prefix.outputs.result }}-
- - run: pre-commit run --all-files --show-diff-on-failure --color=always
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ - uses: pre-commit/action@v3.0.0
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3726d13..100fe88 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,19 +1,16 @@
----
-
repos:
- repo: https://github.com/asottile/pyupgrade
- rev: v3.2.2
+ rev: v3.3.1
hooks:
- id: pyupgrade
- args: [ --py310 ]
+ args: [--py39-plus]
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.1.0
hooks:
- id: black
args:
- - --safe
- --quiet
- files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
+ files: ^((custom_components)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
hooks:
@@ -23,12 +20,26 @@ repos:
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
- exclude: ^tests/fixtures/
- - repo: https://github.com/pycqa/flake8
- rev: 5.0.4
+ - repo: https://github.com/PyCQA/autoflake
+ rev: v2.0.1
+ hooks:
+ - id: autoflake
+ args:
+ - --in-place
+ - --remove-all-unused-imports
+ - repo: https://github.com/PyCQA/flake8
+ rev: 6.0.0
hooks:
- id: flake8
- files: ^(homeassistant|script|tests)/.+\.py$
+ additional_dependencies:
+ - pycodestyle==2.10.0
+ - pyflakes==3.0.1
+ # - flake8-docstrings==1.6.0
+ # - pydocstyle==6.2.3
+ - flake8-comprehensions==3.10.1
+ - flake8-noqa==1.3.0
+ - mccabe==0.7.0
+ files: ^(custom_components)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
@@ -36,24 +47,26 @@ repos:
args:
- --quiet
- --format=custom
- files: ^(homeassistant|script|tests)/.+\.py$
+ - --configfile=bandit.yaml
+ files: ^(custom_components)/.+\.py$
- repo: https://github.com/PyCQA/isort
- rev: 5.11.5
+ rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- - id: check-executables-have-shebangs
- stages: [manual]
- id: check-json
exclude: (.vscode|.devcontainer)
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
+ - repo: https://github.com/adrienverge/yamllint.git
+ rev: v1.28.0
+ hooks:
+ - id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v2.6.1
+ rev: v3.0.0-alpha.4
hooks:
- id: prettier
- stages: [manual]
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 0000000..c2f877a
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,61 @@
+ignore: |
+ azure-*.yml
+rules:
+ braces:
+ level: error
+ min-spaces-inside: 0
+ max-spaces-inside: 1
+ min-spaces-inside-empty: -1
+ max-spaces-inside-empty: -1
+ brackets:
+ level: error
+ min-spaces-inside: 0
+ max-spaces-inside: 0
+ min-spaces-inside-empty: -1
+ max-spaces-inside-empty: -1
+ colons:
+ level: error
+ max-spaces-before: 0
+ max-spaces-after: 1
+ commas:
+ level: error
+ max-spaces-before: 0
+ min-spaces-after: 1
+ max-spaces-after: 1
+ comments:
+ level: error
+ require-starting-space: true
+ min-spaces-from-content: 2
+ comments-indentation:
+ level: error
+ document-end:
+ level: error
+ present: false
+ document-start:
+ level: error
+ present: false
+ empty-lines:
+ level: error
+ max: 1
+ max-start: 0
+ max-end: 1
+ hyphens:
+ level: error
+ max-spaces-after: 1
+ indentation:
+ level: error
+ spaces: 2
+ indent-sequences: true
+ check-multi-line-strings: false
+ key-duplicates:
+ level: error
+ line-length: disable
+ new-line-at-end-of-file:
+ level: error
+ new-lines:
+ level: error
+ type: unix
+ trailing-spaces:
+ level: error
+ truthy:
+ level: error
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1dcfab..01c12e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 2.0.26
+
+Configuration breaking change
+
+- Change units from Bytes, KBytes and MBytes to B, KB and MB, if the configured unit was other than Bytes, please re-configure it
+- Last activity as seconds to return without milliseconds
+- Upgrade pre-commit-configuration by [@tetienne](https://github.com/tetienne) [PR #91](https://github.com/elad-bar/ha-edgeos/pull/91)
+- Fix integration reload after changing configuration
+
## 2.0.25
- Add support for Home Assistant integration and device diagnostics
@@ -30,7 +39,7 @@
**Version requires HA v2022.11.0 and above**
-- Aligned *Core Select* according to new HA *SelectEntityDescription* object
+- Aligned _Core Select_ according to new HA _SelectEntityDescription_ object
## 2.0.20
@@ -66,12 +75,13 @@ For now, special interface do not support to turn on / off
## 2.0.14
**Debugging became easier (less IO and Disk Space)**
+
- Removed `Store Debug Data` switch (Moved to the API endpoints below)
- Removed WebSocket messages sensors (Moved to the API endpoints below)
- Add endpoints to expose the data was previously stored to files and the messages counters
| Endpoint Name | Method | Description |
-|----------------------------|--------|-----------------------------------------------------------------------------------------------------|
+| -------------------------- | ------ | --------------------------------------------------------------------------------------------------- |
| /api/edgeos/list | GET | List all the endpoints available (supporting multiple integrations), available once for integration |
| /api/edgeos/{ENTRY_ID}/ha | GET | JSON of all HA processed data before sent to entities including messages counters, per integration |
| /api/edgeos/{ENTRY_ID}/api | GET | JSON of all raw data from the EdgeOS API, per integration |
@@ -144,9 +154,11 @@ For now, special interface do not support to turn on / off
- Fix missing validation of entry
## 2.0.0
+
Component refactored to allow faster future integration for additional features.
New features:
+
- Enable / Disable interface (Ethernet / Bridge) using a new switch per interface
- Enable / Disable interface monitoring for received and sent data / rate / errors / packets and dropped packets using a switch per interface
- Enable / Disable device monitoring for received and sent data and rate (including device tracker) using a switch per interface
@@ -157,6 +169,7 @@ New features:
- New service: `Update configuration` allows to edit configuration of unit, store debug data, log incoming messages and consider away interval
**Breaking Changes!**
+
- Most of the configurations moved to be regular components of HA (Log incoming messages, Unit of measurement, Store debug data)
- Configuration UI will hold EdgeOS URL and credentials only:
- Hostname
@@ -167,7 +180,7 @@ New features:
**System**
| Entity Name | Type | Description | Additional information |
-|-------------------------------------|---------------|---------------------------------------------------------------------------|-----------------------------------------------|
+| ----------------------------------- | ------------- | ------------------------------------------------------------------------- | --------------------------------------------- |
| {Router Name} Unit | Select | Sets whether to monitor device and create all the components below or not | |
| {Router Name} Unknown devices | Sensor | Represents number of devices leased by the DHCP server | Attributes holds the leased hostname and IPs |
| {Router Name} CPU | Sensor | Represents CPU usage | Attributes holds the leased hostname and IPs |
@@ -177,11 +190,10 @@ New features:
| {Router Name} Log incoming messages | Switch | Sets whether to log WebSocket incoming messages for debugging | |
| {Router Name} Store Debug Data | Switch | Sets whether to store API and WebSocket latest data for debugging | |
-
**Per device**
| Entity Name | Type | Description | Additional information |
-|----------------------------------------------|----------------|---------------------------------------------------------------------------------|-----------------------------|
+| -------------------------------------------- | -------------- | ------------------------------------------------------------------------------- | --------------------------- |
| {Router Name} {Device Name} Monitored | Sensor | Sets whether to monitor device and create all the components below or not | |
| {Router Name} {Device Name} Received Rate | Sensor | Received Rate per second | Statistics: Measurement |
| {Router Name} {Device Name} Received Traffic | Sensor | Received total traffic | Statistics: Total Increment |
@@ -189,11 +201,10 @@ New features:
| {Router Name} {Device Name} Sent Traffic | Sensor | Sent total traffic | Statistics: Total Increment |
| {Router Name} {Device Name} | Device Tracker | Indication whether the device is or was connected over the configured timeframe | |
-
**Per interface**
| Entity Name | Type | Description | Additional information |
-|---------------------------------------------------------|--------|------------------------------------------------------------------------------|-----------------------------|
+| ------------------------------------------------------- | ------ | ---------------------------------------------------------------------------- | --------------------------- |
| {Router Name} {Interface Name} Status | Switch | Sets whether to interface is active or not | |
| {Router Name} {Interface Name} Monitored | Switch | Sets whether to monitor interface and create all the components below or not | |
| {Router Name} {Interface Name} Received Rate | Sensor | Received Rate per second | Statistics: Measurement |
@@ -207,7 +218,6 @@ New features:
| {Router Name} {Interface Name} Sent Errors | Sensor | Sent errors | Statistics: Total Increment |
| {Router Name} {Interface Name} Sent Packets | Sensor | Sent packets | Statistics: Total Increment |
-
## 1.2.6
- Restore value exception handling for WebSocket
diff --git a/README.md b/README.md
index df53797..b94c198 100644
--- a/README.md
+++ b/README.md
@@ -16,8 +16,9 @@ Provides an integration between EdgeOS (Ubiquiti) routers to Home Assistant.
- To enable / disable interfaces an `admin` role is a required
#### Installations via HACS
+
- In HACS, look for "Ubiquiti EdgeOS Routers" and install and restart
-- In Settings --> Devices & Services - (Lower Right) "Add Integration"
+- In Settings --> Devices & Services - (Lower Right) "Add Integration"
#### Setup
@@ -25,7 +26,7 @@ To add integration use Configuration -> Integrations -> Add `EdgeOS`
Integration supports **multiple** EdgeOS devices
| Fields name | Type | Required | Default | Description |
-|-------------|---------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------|
+| ----------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Host | Textbox | + | - | Hostname or IP address to access EdgeOS device, can hold also port (HOST:PORT), default port is 443 |
| Username | Textbox | + | - | Username of user with `Operator` level access or higher, better to create a dedicated user for that integration for faster issues identification |
| Password | Textbox | + | - | |
@@ -33,7 +34,7 @@ Integration supports **multiple** EdgeOS devices
###### EdgeOS Device validation errors
| Errors |
-|------------------------------------------------------------------------------------|
+| ---------------------------------------------------------------------------------- |
| Cannot reach device (404) |
| Invalid credentials (403) |
| General authentication error (when failed to get valid response from device) |
@@ -58,7 +59,7 @@ Please remove the integration and re-add it to make it work again.
_Configuration -> Integrations -> {Integration} -> Options_
| Fields name | Type | Required | Default | Description |
-|-------------------|-----------|----------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|
+| ----------------- | --------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Host | Textbox | + | - | Hostname or IP address to access EdgeOS device, can hold also port (HOST:PORT), default port is 443 |
| Username | Textbox | + | - | Username of user with `Operator` level access or higher, better to create a dedicated user for that integration for faster issues identification |
| Password | Textbox | + | - | |
@@ -78,8 +79,9 @@ logger:
## Components
### System
+
| Entity Name | Type | Description | Additional information |
-|-------------------------------------|---------------|---------------------------------------------------------------------------|-----------------------------------------------|
+| ----------------------------------- | ------------- | ------------------------------------------------------------------------- | --------------------------------------------- |
| {Router Name} Unit | Select | Sets whether to monitor device and create all the components below or not | |
| {Router Name} CPU | Sensor | Represents CPU usage | |
| {Router Name} RAM | Sensor | Represents RAM usage | |
@@ -88,11 +90,12 @@ logger:
| {Router Name} Firmware Updates | Binary Sensor | New firmware available indication | Attributes holds the url and new release name |
| {Router Name} Log incoming messages | Switch | Sets whether to log WebSocket incoming messages for debugging | |
-*Changing the unit will reload the integration*
+_Changing the unit will reload the integration_
### Per device
+
| Entity Name | Type | Description | Additional information |
-|----------------------------------------------|----------------|---------------------------------------------------------------------------------|-----------------------------|
+| -------------------------------------------- | -------------- | ------------------------------------------------------------------------------- | --------------------------- |
| {Router Name} {Device Name} Monitored | Sensor | Sets whether to monitor device and create all the components below or not | |
| {Router Name} {Device Name} Received Rate | Sensor | Received Rate per second | Statistics: Measurement |
| {Router Name} {Device Name} Received Traffic | Sensor | Received total traffic | Statistics: Total Increment |
@@ -100,10 +103,10 @@ logger:
| {Router Name} {Device Name} Sent Traffic | Sensor | Sent total traffic | Statistics: Total Increment |
| {Router Name} {Device Name} | Device Tracker | Indication whether the device is or was connected over the configured timeframe | |
-
### Per interface
+
| Entity Name | Type | Description | Additional information |
-|---------------------------------------------------------|---------------|------------------------------------------------------------------------------|---------------------------------------------|
+| ------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------- | ------------------------------------------- |
| {Router Name} {Interface Name} Status | Switch | Sets whether to interface is active or not | Available only if user level is `admin` |
| {Router Name} {Interface Name} Status | Binary Sensor | Indicates whether interface is active or not | Available only if user level is not `admin` |
| {Router Name} {Interface Name} Connected | Binary Sensor | Indicates whether interface's port is connected or not | |
@@ -119,13 +122,14 @@ logger:
| {Router Name} {Interface Name} Sent Errors | Sensor | Sent errors | Statistics: Total Increment |
| {Router Name} {Interface Name} Sent Packets | Sensor | Sent packets | Statistics: Total Increment |
-
_Unit of measurement for `Traffic` and `Rate` are according to the unit settings of the integration_
## Services
### Update configuration
+
Allows to set:
+
- Consider away interval - Time to consider a device without activity as AWAY (any value between 10 and 1800 in seconds)
- Log incoming messages - Enable / Disable logging of incoming WebSocket messages for debug
- Store debug data - Enable / Disable store debug data to './storage' directory of HA for API (edgeos.debug.api.json) and WS (edgeos.debug.ws.json) data for faster debugging or just to get more ideas for additional features
@@ -138,7 +142,7 @@ More details available in `Developer tools` -> `Services` -> `edgeos.update_conf
```yaml
service: edgeos.update_configuration
data:
- device_id: {Main device ID}
+ device_id: { Main device ID }
unit: Bytes
log_incoming_messages: true
consider_away_interval: 180
@@ -146,4 +150,4 @@ data:
update_entities_interval: 1
```
-*Changing the unit will reload the integration*
+_Changing the unit will reload the integration_
diff --git a/bandit.yaml b/bandit.yaml
new file mode 100644
index 0000000..568f77d
--- /dev/null
+++ b/bandit.yaml
@@ -0,0 +1,21 @@
+# https://bandit.readthedocs.io/en/latest/config.html
+
+tests:
+ - B103
+ - B108
+ - B306
+ - B307
+ - B313
+ - B314
+ - B315
+ - B316
+ - B317
+ - B318
+ - B319
+ - B320
+ - B325
+ - B601
+ - B602
+ - B604
+ - B608
+ - B609
diff --git a/custom_components/edgeos/__init__.py b/custom_components/edgeos/__init__.py
index 002700e..d4f4491 100644
--- a/custom_components/edgeos/__init__.py
+++ b/custom_components/edgeos/__init__.py
@@ -8,7 +8,7 @@
from homeassistant.core import HomeAssistant
from .component.helpers import async_set_ha, clear_ha, get_ha
-from .component.helpers.const import *
+from .configuration.helpers.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
diff --git a/custom_components/edgeos/binary_sensor.py b/custom_components/edgeos/binary_sensor.py
index 749fa9f..baa2bd1 100644
--- a/custom_components/edgeos/binary_sensor.py
+++ b/custom_components/edgeos/binary_sensor.py
@@ -14,12 +14,18 @@
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the component."""
await async_setup_base_entry(
- hass, config_entry, async_add_devices, CoreBinarySensor.get_domain(), CoreBinarySensor.get_component
+ hass,
+ config_entry,
+ async_add_devices,
+ CoreBinarySensor.get_domain(),
+ CoreBinarySensor.get_component,
)
async def async_unload_entry(hass, config_entry):
- _LOGGER.info(f"Unload entry for {CoreBinarySensor.get_domain()} domain: {config_entry}")
+ _LOGGER.info(
+ f"Unload entry for {CoreBinarySensor.get_domain()} domain: {config_entry}"
+ )
return True
diff --git a/custom_components/edgeos/component/api/api.py b/custom_components/edgeos/component/api/api.py
index 9613ea5..6bf8dbb 100644
--- a/custom_components/edgeos/component/api/api.py
+++ b/custom_components/edgeos/component/api/api.py
@@ -2,19 +2,57 @@
from asyncio import sleep
from collections.abc import Awaitable, Callable
-from datetime import datetime
+from datetime import datetime, timedelta
import json
import logging
import sys
from aiohttp import CookieJar
+from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
+from ...configuration.helpers.const import (
+ COOKIE_BEAKER_SESSION_ID,
+ COOKIE_CSRF_TOKEN,
+ COOKIE_PHPSESSID,
+ HEADER_CSRF_TOKEN,
+ MAXIMUM_RECONNECT,
+)
from ...configuration.models.config_data import ConfigData
from ...core.api.base_api import BaseAPI
+from ...core.helpers.const import EMPTY_STRING
from ...core.helpers.enums import ConnectivityStatus
-from ..helpers.const import *
+from ..helpers.const import (
+ API_DATA,
+ API_DATA_COOKIES,
+ API_DATA_INTERFACES,
+ API_DATA_LAST_UPDATE,
+ API_DATA_PRODUCT,
+ API_DATA_SAVE,
+ API_DATA_SESSION_ID,
+ API_DATA_SYSTEM,
+ API_DELETE,
+ API_GET,
+ API_SET,
+ API_URL_DATA,
+ API_URL_DATA_SUBSET,
+ API_URL_HEARTBEAT,
+ API_URL_PARAMETER_ACTION,
+ API_URL_PARAMETER_BASE_URL,
+ API_URL_PARAMETER_SUBSET,
+ API_URL_PARAMETER_TIMESTAMP,
+ HEARTBEAT_MAX_AGE,
+ RESPONSE_ERROR_KEY,
+ RESPONSE_FAILURE_CODE,
+ RESPONSE_OUTPUT,
+ RESPONSE_SUCCESS_KEY,
+ STRING_DASH,
+ STRING_UNDERSCORE,
+ SYSTEM_DATA_DISABLE,
+ TRUE_STR,
+ UPDATE_DATE_ENDPOINTS,
+)
from ..models.edge_os_interface_data import EdgeOSInterfaceData
from ..models.exceptions import SessionTerminatedException
@@ -26,12 +64,13 @@ class IntegrationAPI(BaseAPI):
_config_data: ConfigData | None
- def __init__(self,
- hass: HomeAssistant | None,
- async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
- async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
- ):
-
+ def __init__(
+ self,
+ hass: HomeAssistant | None,
+ async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
+ async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]]
+ | None = None,
+ ):
super().__init__(hass, async_on_data_changed, async_on_status_changed)
try:
@@ -45,9 +84,7 @@ def __init__(self,
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(
- f"Failed to load API, error: {ex}, line: {line_number}"
- )
+ _LOGGER.error(f"Failed to load API, error: {ex}, line: {line_number}")
@property
def session_id(self):
@@ -85,9 +122,7 @@ async def initialize(self, config_data: ConfigData):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(
- f"Failed to initialize API, error: {ex}, line: {line_number}"
- )
+ _LOGGER.error(f"Failed to initialize API, error: {ex}, line: {line_number}")
async def validate(self, data: dict | None = None):
config_data = ConfigData.from_dict(data)
@@ -125,8 +160,8 @@ async def login(self):
response.raise_for_status()
logged_in = (
- self.beaker_session_id is not None
- and self.beaker_session_id == self.session_id
+ self.beaker_session_id is not None
+ and self.beaker_session_id == self.session_id
)
if logged_in:
@@ -136,7 +171,9 @@ async def login(self):
if "EDGE.DeviceModel" in line:
line_parts = line.split(" = ")
value = line_parts[len(line_parts) - 1]
- self.data[API_DATA_PRODUCT] = value.replace("'", EMPTY_STRING)
+ self.data[API_DATA_PRODUCT] = value.replace(
+ "'", EMPTY_STRING
+ )
self.data[API_DATA_SESSION_ID] = self.session_id
self.data[API_DATA_COOKIES] = self._cookies
@@ -144,7 +181,7 @@ async def login(self):
break
else:
- _LOGGER.error(f"Failed to login, Invalid credentials")
+ _LOGGER.error("Failed to login, Invalid credentials")
if self.beaker_session_id is None and self.session_id is not None:
await self.set_status(ConnectivityStatus.Failed)
@@ -164,13 +201,13 @@ async def login(self):
await self.set_status(ConnectivityStatus.NotFound)
- async def _async_get(self,
- endpoint,
- timestamp: str | None = None,
- action: str | None = None,
- subset: str | None = None
- ):
-
+ async def _async_get(
+ self,
+ endpoint,
+ timestamp: str | None = None,
+ action: str | None = None,
+ subset: str | None = None,
+ ):
result = None
message = None
status = 404
@@ -240,7 +277,9 @@ async def _async_post(self, endpoint, data):
headers = self._get_post_headers()
data_json = json.dumps(data)
- async with self.session.post(url, headers=headers, data=data_json, ssl=False) as response:
+ async with self.session.post(
+ url, headers=headers, data=data_json, ssl=False
+ ) as response:
response.raise_for_status()
result = await response.json()
@@ -264,14 +303,18 @@ async def async_send_heartbeat(self, max_age=HEARTBEAT_MAX_AGE):
if current_invocation > timedelta(seconds=max_age):
current_ts = str(int(ts.timestamp()))
- response = await self._async_get(API_URL_HEARTBEAT, timestamp=current_ts)
+ response = await self._async_get(
+ API_URL_HEARTBEAT, timestamp=current_ts
+ )
if response is not None:
_LOGGER.debug(f"Heartbeat response: {response}")
self._last_valid = ts
else:
- _LOGGER.debug(f"Ignoring request to send heartbeat, Reason: closed session")
+ _LOGGER.debug(
+ "Ignoring request to send heartbeat, Reason: closed session"
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
@@ -299,7 +342,9 @@ async def async_update(self):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract WS data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract WS data, Error: {ex}, Line: {line_number}"
+ )
async def _load_system_data(self):
try:
@@ -314,14 +359,18 @@ async def _load_system_data(self):
if success_key == TRUE_STR:
if API_GET.upper() in result_json:
- self.data[API_DATA_SYSTEM] = result_json.get(API_GET.upper(), {})
+ self.data[API_DATA_SYSTEM] = result_json.get(
+ API_GET.upper(), {}
+ )
else:
error_message = result_json[RESPONSE_ERROR_KEY]
_LOGGER.error(f"Failed, Error: {error_message}")
else:
_LOGGER.error("Invalid response, not contain success status")
else:
- _LOGGER.debug(f"Ignoring request to get devices data, Reason: closed session")
+ _LOGGER.debug(
+ "Ignoring request to get devices data, Reason: closed session"
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
@@ -337,7 +386,9 @@ async def _load_general_data(self, key):
clean_item = key.replace(STRING_DASH, STRING_UNDERSCORE)
- data = await self._async_get(API_URL_DATA_SUBSET, action=API_DATA, subset=clean_item)
+ data = await self._async_get(
+ API_URL_DATA_SUBSET, action=API_DATA, subset=clean_item
+ )
if data is not None:
if RESPONSE_SUCCESS_KEY in data:
@@ -348,7 +399,9 @@ async def _load_general_data(self, key):
else:
self.data[key] = data.get(RESPONSE_OUTPUT)
else:
- _LOGGER.debug(f"Ignoring request to get data of {key}, Reason: closed session")
+ _LOGGER.debug(
+ f"Ignoring request to get data of {key}, Reason: closed session"
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
@@ -356,7 +409,9 @@ async def _load_general_data(self, key):
_LOGGER.error(f"Failed to load {key}, Error: {ex}, Line: {line_number}")
- async def set_interface_state(self, interface: EdgeOSInterfaceData, is_enabled: bool):
+ async def set_interface_state(
+ self, interface: EdgeOSInterfaceData, is_enabled: bool
+ ):
_LOGGER.info(f"Set state of interface {interface.name} to {is_enabled}")
modified = False
@@ -364,11 +419,7 @@ async def set_interface_state(self, interface: EdgeOSInterfaceData, is_enabled:
data = {
API_DATA_INTERFACES: {
- interface.interface_type: {
- interface.name: {
- SYSTEM_DATA_DISABLE: None
- }
- }
+ interface.interface_type: {interface.name: {SYSTEM_DATA_DISABLE: None}}
}
}
@@ -385,14 +436,22 @@ async def set_interface_state(self, interface: EdgeOSInterfaceData, is_enabled:
modified = success_key != RESPONSE_FAILURE_CODE
if not modified:
- _LOGGER.error(f"Failed to set state of interface {interface.name} to {is_enabled}")
+ _LOGGER.error(
+ f"Failed to set state of interface {interface.name} to {is_enabled}"
+ )
- def _build_endpoint(self, endpoint, timestamp: str | None = None, action: str | None = None, subset: str | None = None):
+ def _build_endpoint(
+ self,
+ endpoint,
+ timestamp: str | None = None,
+ action: str | None = None,
+ subset: str | None = None,
+ ):
data = {
API_URL_PARAMETER_BASE_URL: self._config_data.url,
API_URL_PARAMETER_TIMESTAMP: timestamp,
API_URL_PARAMETER_ACTION: action,
- API_URL_PARAMETER_SUBSET: subset
+ API_URL_PARAMETER_SUBSET: subset,
}
url = endpoint.format(**data)
diff --git a/custom_components/edgeos/component/api/storage_api.py b/custom_components/edgeos/component/api/storage_api.py
index b32ff61..2d72a77 100644
--- a/custom_components/edgeos/component/api/storage_api.py
+++ b/custom_components/edgeos/component/api/storage_api.py
@@ -10,8 +10,23 @@
from ...configuration.models.config_data import ConfigData
from ...core.api.base_api import BaseAPI
+from ...core.helpers.const import DOMAIN, STORAGE_VERSION
from ...core.helpers.enums import ConnectivityStatus
-from ..helpers.const import *
+from ..helpers.const import (
+ ATTR_BYTE,
+ DEFAULT_CONSIDER_AWAY_INTERVAL,
+ DEFAULT_UPDATE_API_INTERVAL,
+ DEFAULT_UPDATE_ENTITIES_INTERVAL,
+ STORAGE_DATA_CONSIDER_AWAY_INTERVAL,
+ STORAGE_DATA_FILE_CONFIG,
+ STORAGE_DATA_FILES,
+ STORAGE_DATA_LOG_INCOMING_MESSAGES,
+ STORAGE_DATA_MONITORED_DEVICES,
+ STORAGE_DATA_MONITORED_INTERFACES,
+ STORAGE_DATA_UNIT,
+ STORAGE_DATA_UPDATE_API_INTERVAL,
+ STORAGE_DATA_UPDATE_ENTITIES_INTERVAL,
+)
_LOGGER = logging.getLogger(__name__)
@@ -21,12 +36,13 @@ class StorageAPI(BaseAPI):
_config_data: ConfigData | None
_data: dict
- def __init__(self,
- hass: HomeAssistant | None,
- async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
- async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
- ):
-
+ def __init__(
+ self,
+ hass: HomeAssistant | None,
+ async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
+ async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]]
+ | None = None,
+ ):
super().__init__(hass, async_on_data_changed, async_on_status_changed)
self._config_data = None
@@ -53,7 +69,7 @@ def monitored_devices(self):
@property
def unit(self):
- result = self.data.get(STORAGE_DATA_UNIT, ATTR_BYTE).replace(ATTR_BYTE[1:], "").upper()
+ result = self.data.get(STORAGE_DATA_UNIT, ATTR_BYTE)
return result
@@ -65,19 +81,28 @@ def log_incoming_messages(self):
@property
def consider_away_interval(self):
- result = self.data.get(STORAGE_DATA_CONSIDER_AWAY_INTERVAL, DEFAULT_CONSIDER_AWAY_INTERVAL.total_seconds())
+ result = self.data.get(
+ STORAGE_DATA_CONSIDER_AWAY_INTERVAL,
+ DEFAULT_CONSIDER_AWAY_INTERVAL.total_seconds(),
+ )
return result
@property
def update_entities_interval(self):
- result = self.data.get(STORAGE_DATA_UPDATE_ENTITIES_INTERVAL, DEFAULT_UPDATE_ENTITIES_INTERVAL.total_seconds())
+ result = self.data.get(
+ STORAGE_DATA_UPDATE_ENTITIES_INTERVAL,
+ DEFAULT_UPDATE_ENTITIES_INTERVAL.total_seconds(),
+ )
return result
@property
def update_api_interval(self):
- result = self.data.get(STORAGE_DATA_UPDATE_API_INTERVAL, DEFAULT_UPDATE_API_INTERVAL.total_seconds())
+ result = self.data.get(
+ STORAGE_DATA_UPDATE_API_INTERVAL,
+ DEFAULT_UPDATE_API_INTERVAL.total_seconds(),
+ )
return result
@@ -96,7 +121,9 @@ def _initialize_storages(self):
for storage_data_file in STORAGE_DATA_FILES:
file_name = f"{DOMAIN}.{entry_id}.{storage_data_file}.json"
- stores[storage_data_file] = Store(self.hass, STORAGE_VERSION, file_name, encoder=JSONEncoder)
+ stores[storage_data_file] = Store(
+ self.hass, STORAGE_VERSION, file_name, encoder=JSONEncoder
+ )
self._stores = stores
@@ -112,7 +139,7 @@ async def _async_load_configuration(self):
STORAGE_DATA_LOG_INCOMING_MESSAGES: False,
STORAGE_DATA_CONSIDER_AWAY_INTERVAL: DEFAULT_CONSIDER_AWAY_INTERVAL.total_seconds(),
STORAGE_DATA_UPDATE_ENTITIES_INTERVAL: DEFAULT_UPDATE_ENTITIES_INTERVAL.total_seconds(),
- STORAGE_DATA_UPDATE_API_INTERVAL: DEFAULT_UPDATE_API_INTERVAL.total_seconds()
+ STORAGE_DATA_UPDATE_API_INTERVAL: DEFAULT_UPDATE_API_INTERVAL.total_seconds(),
}
await self._async_save()
diff --git a/custom_components/edgeos/component/api/websocket.py b/custom_components/edgeos/component/api/websocket.py
index c0b1743..5375038 100644
--- a/custom_components/edgeos/component/api/websocket.py
+++ b/custom_components/edgeos/component/api/websocket.py
@@ -10,12 +10,47 @@
import sys
from urllib.parse import urlparse
+import aiohttp
+
from homeassistant.core import HomeAssistant
-from ...component.helpers.const import *
+from ...configuration.helpers.const import WEBSOCKET_URL_TEMPLATE
from ...configuration.models.config_data import ConfigData
from ...core.api.base_api import BaseAPI
+from ...core.helpers.const import EMPTY_STRING
from ...core.helpers.enums import ConnectivityStatus
+from ..helpers.const import (
+ ADDRESS_HW_ADDR,
+ ADDRESS_IPV4,
+ ADDRESS_LIST,
+ API_DATA_COOKIES,
+ API_DATA_SESSION_ID,
+ BEGINS_WITH_SIX_DIGITS,
+ DEVICE_LIST,
+ DISCOVER_DEVICE_ITEMS,
+ INTERFACE_DATA_MULTICAST,
+ INTERFACES_MAIN_MAP,
+ INTERFACES_STATS,
+ STRING_COLON,
+ STRING_COMMA,
+ TRAFFIC_DATA_DEVICE_ITEMS,
+ TRAFFIC_DATA_DIRECTIONS,
+ TRAFFIC_DATA_INTERFACE_ITEMS,
+ WS_CLOSING_MESSAGE,
+ WS_COMPRESSION_DEFLATE,
+ WS_DISCOVER_KEY,
+ WS_EXPORT_KEY,
+ WS_IGNORED_MESSAGES,
+ WS_INTERFACES_KEY,
+ WS_MAX_MSG_SIZE,
+ WS_RECEIVED_MESSAGES,
+ WS_SESSION_ID,
+ WS_SYSTEM_STATS_KEY,
+ WS_TIMEOUT,
+ WS_TOPIC_NAME,
+ WS_TOPIC_SUBSCRIBE,
+ WS_TOPIC_UNSUBSCRIBE,
+)
_LOGGER = logging.getLogger(__name__)
@@ -27,12 +62,13 @@ class IntegrationWS(BaseAPI):
_previous_message: dict | None
_ws_handlers: dict
- def __init__(self,
- hass: HomeAssistant | None,
- async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
- async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
- ):
-
+ def __init__(
+ self,
+ hass: HomeAssistant | None,
+ async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
+ async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]]
+ | None = None,
+ ):
super().__init__(hass, async_on_data_changed, async_on_status_changed)
self._config_data = None
@@ -69,12 +105,12 @@ async def update_api_data(self, api_data: dict, can_log_messages: bool):
async def initialize(self, config_data: ConfigData | None = None):
if config_data is None:
- _LOGGER.debug(f"Reinitializing WebSocket connection")
+ _LOGGER.debug("Reinitializing WebSocket connection")
else:
self._config_data = config_data
- _LOGGER.debug(f"Initializing WebSocket connection")
+ _LOGGER.debug("Initializing WebSocket connection")
try:
self.data = {
@@ -90,9 +126,8 @@ async def initialize(self, config_data: ConfigData | None = None):
autoclose=True,
max_msg_size=WS_MAX_MSG_SIZE,
timeout=WS_TIMEOUT,
- compress=WS_COMPRESSION_DEFLATE
+ compress=WS_COMPRESSION_DEFLATE,
) as ws:
-
await self.set_status(ConnectivityStatus.Connected)
self._ws = ws
@@ -103,7 +138,7 @@ async def initialize(self, config_data: ConfigData | None = None):
except Exception as ex:
if self.session is not None and self.session.closed:
- _LOGGER.info(f"WS Session closed")
+ _LOGGER.info("WS Session closed")
await self.terminate()
@@ -112,10 +147,14 @@ async def initialize(self, config_data: ConfigData | None = None):
line_number = tb.tb_lineno
if self.status == ConnectivityStatus.Connected:
- _LOGGER.info(f"WS got disconnected will try to recover, Error: {ex}, Line: {line_number}")
+ _LOGGER.info(
+ f"WS got disconnected will try to recover, Error: {ex}, Line: {line_number}"
+ )
else:
- _LOGGER.warning(f"Failed to connect WS, Error: {ex}, Line: {line_number}")
+ _LOGGER.warning(
+ f"Failed to connect WS, Error: {ex}, Line: {line_number}"
+ )
await self.set_status(ConnectivityStatus.Failed)
@@ -132,17 +171,14 @@ async def terminate(self):
self._ws = None
async def async_send_heartbeat(self):
- _LOGGER.debug(f"Keep alive message sent")
+ _LOGGER.debug("Keep alive message sent")
if self.session is None or self.session.closed:
await self.set_status(ConnectivityStatus.NotConnected)
return
if self.status == ConnectivityStatus.Connected:
- content = {
- "CLIENT_PING": "",
- "SESSION_ID": self._api_session_id
- }
+ content = {"CLIENT_PING": "", "SESSION_ID": self._api_session_id}
content_str = json.dumps(content)
data = f"{len(content_str)}\n{content_str}"
@@ -154,14 +190,16 @@ async def async_send_heartbeat(self):
await self._ws.send_str(data)
except ConnectionResetError as crex:
- _LOGGER.debug(f"Gracefully failed to send heartbeat - Restarting connection, Error: {crex}")
+ _LOGGER.debug(
+ f"Gracefully failed to send heartbeat - Restarting connection, Error: {crex}"
+ )
await self.set_status(ConnectivityStatus.NotConnected)
except Exception as ex:
_LOGGER.error(f"Failed to send heartbeat, Error: {ex}")
async def _listen(self):
- _LOGGER.info(f"Starting to listen connected")
+ _LOGGER.info("Starting to listen connected")
subscription_data = self._get_subscription_data()
await self._ws.send_str(subscription_data)
@@ -172,10 +210,18 @@ async def _listen(self):
is_connected = self.status == ConnectivityStatus.Connected
is_closing_type = msg.type in WS_CLOSING_MESSAGE
is_error = msg.type == aiohttp.WSMsgType.ERROR
- is_closing_data = False if is_closing_type or is_error else msg.data == "close"
+ is_closing_data = (
+ False if is_closing_type or is_error else msg.data == "close"
+ )
session_is_closed = self.session is None or self.session.closed
- if is_closing_type or is_error or is_closing_data or session_is_closed or not is_connected:
+ if (
+ is_closing_type
+ or is_error
+ or is_closing_data
+ or session_is_closed
+ or not is_connected
+ ):
_LOGGER.warning(
f"WS stopped listening, "
f"Message: {str(msg)}, "
@@ -218,10 +264,7 @@ async def parse_message(self, message):
else:
length = int(previous_messages[0])
- self._previous_message = {
- "Length": length,
- "Content": message
- }
+ self._previous_message = {"Length": length, "Content": message}
_LOGGER.debug("Store partial message for later processing")
@@ -229,7 +272,9 @@ async def parse_message(self, message):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.warning(f"Parse message failed, Data: {message}, Error: {ex}, Line: {line_number}")
+ _LOGGER.warning(
+ f"Parse message failed, Data: {message}, Error: {ex}, Line: {line_number}"
+ )
def _get_corrected_message(self, message):
original_message = message
@@ -379,7 +424,9 @@ def _handle_interfaces(self, data):
interface[item] = item_data
elif INTERFACES_STATS == item:
- interface[INTERFACE_DATA_MULTICAST] = float(item_data.get(INTERFACE_DATA_MULTICAST))
+ interface[INTERFACE_DATA_MULTICAST] = float(
+ item_data.get(INTERFACE_DATA_MULTICAST)
+ )
for direction in TRAFFIC_DATA_DIRECTIONS:
for key in TRAFFIC_DATA_INTERFACE_ITEMS:
diff --git a/custom_components/edgeos/component/helpers/__init__.py b/custom_components/edgeos/component/helpers/__init__.py
index a4dcbf9..0a2935a 100644
--- a/custom_components/edgeos/component/helpers/__init__.py
+++ b/custom_components/edgeos/component/helpers/__init__.py
@@ -5,8 +5,8 @@
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant
-from ...component.helpers.const import *
from ...component.managers.home_assistant import EdgeOSHomeAssistantManager
+from ...core.helpers.const import DATA
_LOGGER = logging.getLogger(__name__)
@@ -26,9 +26,7 @@ async def _async_unload(_: Event) -> None:
await instance.async_unload()
entry.async_on_unload(
- hass.bus.async_listen_once(
- EVENT_HOMEASSISTANT_STOP, _async_unload
- )
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_unload)
)
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
@@ -38,7 +36,7 @@ async def _async_unload(_: Event) -> None:
def get_ha(hass: HomeAssistant, entry_id) -> EdgeOSHomeAssistantManager:
- ha_data = hass.data.get(DATA, dict())
+ ha_data = hass.data.get(DATA, {})
ha = ha_data.get(entry_id)
return ha
@@ -46,6 +44,6 @@ def get_ha(hass: HomeAssistant, entry_id) -> EdgeOSHomeAssistantManager:
def clear_ha(hass: HomeAssistant, entry_id):
if DATA not in hass.data:
- hass.data[DATA] = dict()
+ hass.data[DATA] = {}
del hass.data[DATA][entry_id]
diff --git a/custom_components/edgeos/component/helpers/const.py b/custom_components/edgeos/component/helpers/const.py
index c558afd..1b3079f 100644
--- a/custom_components/edgeos/component/helpers/const.py
+++ b/custom_components/edgeos/component/helpers/const.py
@@ -8,7 +8,6 @@
import homeassistant.helpers.config_validation as cv
-from ...core.helpers.const import *
from .enums import InterfaceTypes
ATTR_FRIENDLY_NAME = "friendly_name"
@@ -29,9 +28,7 @@
STORAGE_DATA_FILE_CONFIG = "config"
-STORAGE_DATA_FILES = [
- STORAGE_DATA_FILE_CONFIG
-]
+STORAGE_DATA_FILES = [STORAGE_DATA_FILE_CONFIG]
MESSAGES_COUNTER_SECTION = "messages"
@@ -106,17 +103,9 @@
WS_IGNORED_MESSAGES = "ignored-messages"
WS_ERROR_MESSAGES = "error-messages"
-WS_MESSAGES = [
- WS_RECEIVED_MESSAGES,
- WS_IGNORED_MESSAGES,
- WS_ERROR_MESSAGES
-]
+WS_MESSAGES = [WS_RECEIVED_MESSAGES, WS_IGNORED_MESSAGES, WS_ERROR_MESSAGES]
-UPDATE_DATE_ENDPOINTS = [
- API_DATA_SYS_INFO,
- API_DATA_DHCP_STATS,
- API_DATA_DHCP_LEASES
-]
+UPDATE_DATE_ENDPOINTS = [API_DATA_SYS_INFO, API_DATA_DHCP_STATS, API_DATA_DHCP_LEASES]
DISCOVER_DATA_FW_VERSION = "fwversion"
DISCOVER_DATA_PRODUCT = "product"
@@ -125,15 +114,11 @@
SYSTEM_STATS_DATA_CPU = "cpu"
SYSTEM_STATS_DATA_MEM = "mem"
-ATTR_KILO = "KBytes"
-ATTR_MEGA = "MBytes"
-ATTR_BYTE = "Bytes"
+ATTR_BYTE = "B"
+ATTR_KILO = "KB"
+ATTR_MEGA = "MB"
-UNIT_MAPPING = {
- ATTR_BYTE: BYTE,
- ATTR_KILO: KILO_BYTE,
- ATTR_MEGA: MEGA_BYTE
-}
+UNIT_MAPPING = {ATTR_BYTE: BYTE, ATTR_KILO: KILO_BYTE, ATTR_MEGA: MEGA_BYTE}
DEVICE_LIST = "devices"
ADDRESS_LIST = "addresses"
@@ -156,7 +141,6 @@
SERVICE_UPDATE_CONFIGURATION = "update_configuration"
-EMPTY_STRING = ""
BEGINS_WITH_SIX_DIGITS = "^([0-9]{1,6})"
STRING_DASH = "-"
@@ -238,22 +222,19 @@
TRAFFIC_DATA_DIRECTION_SENT = "tx"
TRAFFIC_DATA_DIRECTION_RECEIVED = "rx"
-TRAFFIC_DATA_DIRECTIONS = [
- TRAFFIC_DATA_DIRECTION_SENT,
- TRAFFIC_DATA_DIRECTION_RECEIVED
-]
+TRAFFIC_DATA_DIRECTIONS = [TRAFFIC_DATA_DIRECTION_SENT, TRAFFIC_DATA_DIRECTION_RECEIVED]
TRAFFIC_DATA_INTERFACE_ITEMS = {
TRAFFIC_STATS_BPS_KEY: TRAFFIC_DATA_RATE,
TRAFFIC_STATS_BYTES: TRAFFIC_DATA_TOTAL,
TRAFFIC_DATA_ERRORS: TRAFFIC_DATA_ERRORS,
TRAFFIC_DATA_PACKETS: TRAFFIC_DATA_PACKETS,
- TRAFFIC_DATA_DROPPED: TRAFFIC_DATA_DROPPED
+ TRAFFIC_DATA_DROPPED: TRAFFIC_DATA_DROPPED,
}
TRAFFIC_DATA_DEVICE_ITEMS = {
TRAFFIC_DATA_RATE: TRAFFIC_DATA_RATE,
- TRAFFIC_STATS_BYTES: TRAFFIC_DATA_TOTAL
+ TRAFFIC_STATS_BYTES: TRAFFIC_DATA_TOTAL,
}
INTERFACES_MAIN_MAP = [
@@ -269,17 +250,29 @@
DISCOVER_DATA_PRODUCT,
SYSTEM_STATS_DATA_UPTIME,
DISCOVER_DATA_FW_VERSION,
- "system_status"
+ "system_status",
]
SERVICE_SCHEMA_UPDATE_CONFIGURATION = vol.Schema(
{
vol.Required(CONF_DEVICE_ID): cv.string,
- vol.Optional(STORAGE_DATA_CONSIDER_AWAY_INTERVAL.replace(STRING_DASH, STRING_UNDERSCORE)): vol.Range(10, 1800),
- vol.Optional(STORAGE_DATA_UPDATE_ENTITIES_INTERVAL.replace(STRING_DASH, STRING_UNDERSCORE)): vol.Range(1, 60),
- vol.Optional(STORAGE_DATA_UPDATE_API_INTERVAL.replace(STRING_DASH, STRING_UNDERSCORE)): vol.Range(30, 180),
- vol.Optional(STORAGE_DATA_LOG_INCOMING_MESSAGES.replace(STRING_DASH, STRING_UNDERSCORE)): cv.boolean,
- vol.Optional(STORAGE_DATA_UNIT.replace(STRING_DASH, STRING_UNDERSCORE)): vol.In(UNIT_MAPPING.keys()),
+ vol.Optional(
+ STORAGE_DATA_CONSIDER_AWAY_INTERVAL.replace(STRING_DASH, STRING_UNDERSCORE)
+ ): vol.Range(10, 1800),
+ vol.Optional(
+ STORAGE_DATA_UPDATE_ENTITIES_INTERVAL.replace(
+ STRING_DASH, STRING_UNDERSCORE
+ )
+ ): vol.Range(1, 60),
+ vol.Optional(
+ STORAGE_DATA_UPDATE_API_INTERVAL.replace(STRING_DASH, STRING_UNDERSCORE)
+ ): vol.Range(30, 180),
+ vol.Optional(
+ STORAGE_DATA_LOG_INCOMING_MESSAGES.replace(STRING_DASH, STRING_UNDERSCORE)
+ ): cv.boolean,
+ vol.Optional(STORAGE_DATA_UNIT.replace(STRING_DASH, STRING_UNDERSCORE)): vol.In(
+ UNIT_MAPPING.keys()
+ ),
}
)
@@ -294,12 +287,10 @@
InterfaceTypes.SWITCH_PREFIX: "Switch",
InterfaceTypes.VIRTUAL_TUNNEL_PREFIX: "Virtual Tunnel",
InterfaceTypes.OPEN_VPN_PREFIX: "OpenVPN",
- InterfaceTypes.BONDING_PREFIX: "VLAN"
+ InterfaceTypes.BONDING_PREFIX: "VLAN",
}
-IGNORED_INTERFACES = [
- InterfaceTypes.LOOPBACK
-]
+IGNORED_INTERFACES = [InterfaceTypes.LOOPBACK]
RECEIVED_RATE_PREFIX = "Received Rate"
RECEIVED_TRAFFIC_PREFIX = "Received Traffic"
@@ -338,15 +329,9 @@
SENT_PACKETS_PREFIX: SENT_PACKETS_ICON,
}
-STATS_RATE = [
- RECEIVED_RATE_PREFIX,
- SENT_RATE_PREFIX
-]
+STATS_RATE = [RECEIVED_RATE_PREFIX, SENT_RATE_PREFIX]
-STATS_TRAFFIC = [
- RECEIVED_TRAFFIC_PREFIX,
- SENT_TRAFFIC_PREFIX
-]
+STATS_TRAFFIC = [RECEIVED_TRAFFIC_PREFIX, SENT_TRAFFIC_PREFIX]
STATS_UNITS = {
RECEIVED_DROPPED_PREFIX: TRAFFIC_DATA_DROPPED,
diff --git a/custom_components/edgeos/component/helpers/exceptions.py b/custom_components/edgeos/component/helpers/exceptions.py
index f1cb608..45ee69a 100644
--- a/custom_components/edgeos/component/helpers/exceptions.py
+++ b/custom_components/edgeos/component/helpers/exceptions.py
@@ -15,7 +15,9 @@ class APIValidationException(Exception):
status: ConnectivityStatus
def __init__(self, endpoint: str, status: ConnectivityStatus):
- super().__init__(f"API cannot process request to '{endpoint}', Status: {status}")
+ super().__init__(
+ f"API cannot process request to '{endpoint}', Status: {status}"
+ )
self.endpoint = endpoint
self.status = status
diff --git a/custom_components/edgeos/component/managers/home_assistant.py b/custom_components/edgeos/component/managers/home_assistant.py
index 18a255c..9e35440 100644
--- a/custom_components/edgeos/component/managers/home_assistant.py
+++ b/custom_components/edgeos/component/managers/home_assistant.py
@@ -5,7 +5,7 @@
from asyncio import sleep
from collections.abc import Awaitable, Callable
-from datetime import datetime
+from datetime import datetime, timedelta
import logging
import sys
@@ -13,24 +13,134 @@
BinarySensorDeviceClass,
BinarySensorEntityDescription,
)
+from homeassistant.components.homeassistant import SERVICE_RELOAD_CONFIG_ENTRY
from homeassistant.components.select import SelectEntityDescription
from homeassistant.components.sensor import SensorEntityDescription, SensorStateClass
from homeassistant.components.switch import SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
-from homeassistant.const import STATE_OFF, STATE_ON
+from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import async_get as async_get_device_registry
from homeassistant.helpers.entity import EntityCategory, EntityDescription
+from ...configuration.helpers.const import DEFAULT_NAME, DOMAIN, MANUFACTURER
from ...configuration.managers.configuration_manager import ConfigurationManager
from ...configuration.models.config_data import ConfigData
+from ...core.helpers.const import (
+ ACTION_CORE_ENTITY_SELECT_OPTION,
+ ACTION_CORE_ENTITY_TURN_OFF,
+ ACTION_CORE_ENTITY_TURN_ON,
+ DOMAIN_BINARY_SENSOR,
+ DOMAIN_DEVICE_TRACKER,
+ DOMAIN_SELECT,
+ DOMAIN_SENSOR,
+ DOMAIN_SWITCH,
+ ENTITY_CONFIG_ENTRY_ID,
+ ENTITY_UNIQUE_ID,
+ HA_NAME,
+)
from ...core.helpers.enums import ConnectivityStatus
from ...core.managers.home_assistant import HomeAssistantManager
from ...core.models.entity_data import EntityData
from ..api.api import IntegrationAPI
from ..api.storage_api import StorageAPI
from ..api.websocket import IntegrationWS
-from ..helpers.const import *
+from ..helpers.const import (
+ ADDRESS_LIST,
+ API_DATA_DHCP_LEASES,
+ API_DATA_DHCP_STATS,
+ API_DATA_INTERFACES,
+ API_DATA_SYS_INFO,
+ API_DATA_SYSTEM,
+ BYTE,
+ CONF_DEVICE_ID,
+ DATA_SYSTEM_SERVICE,
+ DATA_SYSTEM_SERVICE_DHCP_SERVER,
+ DEFAULT_HEARTBEAT_INTERVAL,
+ DEFAULT_UPDATE_API_INTERVAL,
+ DEVICE_DATA_MAC,
+ DEVICE_LIST,
+ DHCP_SERVER_IP_ADDRESS,
+ DHCP_SERVER_LEASED,
+ DHCP_SERVER_LEASES,
+ DHCP_SERVER_LEASES_CLIENT_HOSTNAME,
+ DHCP_SERVER_MAC_ADDRESS,
+ DHCP_SERVER_SHARED_NETWORK_NAME,
+ DHCP_SERVER_STATIC_MAPPING,
+ DHCP_SERVER_STATS,
+ DHCP_SERVER_SUBNET,
+ DISCOVER_DATA_FW_VERSION,
+ DISCOVER_DATA_PRODUCT,
+ FALSE_STR,
+ FW_LATEST_STATE_CAN_UPGRADE,
+ INTERFACE_DATA_ADDRESS,
+ INTERFACE_DATA_AGING,
+ INTERFACE_DATA_BRIDGE_GROUP,
+ INTERFACE_DATA_BRIDGED_CONNTRACK,
+ INTERFACE_DATA_DESCRIPTION,
+ INTERFACE_DATA_DUPLEX,
+ INTERFACE_DATA_HELLO_TIME,
+ INTERFACE_DATA_LINK_UP,
+ INTERFACE_DATA_MAC,
+ INTERFACE_DATA_MAX_AGE,
+ INTERFACE_DATA_MULTICAST,
+ INTERFACE_DATA_PRIORITY,
+ INTERFACE_DATA_PROMISCUOUS,
+ INTERFACE_DATA_SPEED,
+ INTERFACE_DATA_STP,
+ INTERFACE_DATA_UP,
+ LAST_ACTIVITY,
+ MESSAGES_COUNTER_SECTION,
+ SERVICE_SCHEMA_UPDATE_CONFIGURATION,
+ SERVICE_UPDATE_CONFIGURATION,
+ STATS_ICONS,
+ STATS_RATE,
+ STATS_TRAFFIC,
+ STATS_UNITS,
+ STORAGE_DATA_CONSIDER_AWAY_INTERVAL,
+ STORAGE_DATA_LOG_INCOMING_MESSAGES,
+ STORAGE_DATA_UNIT,
+ STORAGE_DATA_UPDATE_API_INTERVAL,
+ STORAGE_DATA_UPDATE_ENTITIES_INTERVAL,
+ STRING_DASH,
+ STRING_UNDERSCORE,
+ SYSTEM_DATA_DOMAIN_NAME,
+ SYSTEM_DATA_HOSTNAME,
+ SYSTEM_DATA_LOGIN,
+ SYSTEM_DATA_LOGIN_USER,
+ SYSTEM_DATA_LOGIN_USER_LEVEL,
+ SYSTEM_DATA_NTP,
+ SYSTEM_DATA_NTP_SERVER,
+ SYSTEM_DATA_OFFLOAD,
+ SYSTEM_DATA_OFFLOAD_HW_NAT,
+ SYSTEM_DATA_OFFLOAD_IPSEC,
+ SYSTEM_DATA_TIME_ZONE,
+ SYSTEM_DATA_TRAFFIC_ANALYSIS,
+ SYSTEM_DATA_TRAFFIC_ANALYSIS_DPI,
+ SYSTEM_DATA_TRAFFIC_ANALYSIS_EXPORT,
+ SYSTEM_INFO_DATA_FW_LATEST,
+ SYSTEM_INFO_DATA_FW_LATEST_STATE,
+ SYSTEM_INFO_DATA_FW_LATEST_URL,
+ SYSTEM_INFO_DATA_FW_LATEST_VERSION,
+ SYSTEM_INFO_DATA_SW_VER,
+ SYSTEM_STATS_DATA_CPU,
+ SYSTEM_STATS_DATA_MEM,
+ SYSTEM_STATS_DATA_UPTIME,
+ TRAFFIC_DATA_DEVICE_ITEMS,
+ TRAFFIC_DATA_DROPPED,
+ TRAFFIC_DATA_ERRORS,
+ TRAFFIC_DATA_INTERFACE_ITEMS,
+ TRAFFIC_DATA_PACKETS,
+ TRUE_STR,
+ UNIT_MAPPING,
+ USER_LEVEL_ADMIN,
+ WS_DISCOVER_KEY,
+ WS_EXPORT_KEY,
+ WS_INTERFACES_KEY,
+ WS_MESSAGES,
+ WS_RECONNECT_INTERVAL,
+ WS_SYSTEM_STATS_KEY,
+)
from ..helpers.enums import InterfaceHandlers
from ..models.edge_os_device_data import EdgeOSDeviceData
from ..models.edge_os_interface_data import EdgeOSInterfaceData
@@ -44,8 +154,12 @@ def __init__(self, hass: HomeAssistant):
super().__init__(hass, DEFAULT_UPDATE_API_INTERVAL, DEFAULT_HEARTBEAT_INTERVAL)
self._storage_api: StorageAPI = StorageAPI(self._hass)
- self._api: IntegrationAPI = IntegrationAPI(self._hass, self._api_data_changed, self._api_status_changed)
- self._ws: IntegrationWS = IntegrationWS(self._hass, self._ws_data_changed, self._ws_status_changed)
+ self._api: IntegrationAPI = IntegrationAPI(
+ self._hass, self._api_data_changed, self._api_status_changed
+ )
+ self._ws: IntegrationWS = IntegrationWS(
+ self._hass, self._ws_data_changed, self._ws_status_changed
+ )
self._config_manager: ConfigurationManager | None = None
self._system: EdgeOSSystemData | None = None
self._devices: dict[str, EdgeOSDeviceData] = {}
@@ -84,7 +198,7 @@ def system_name(self):
return name
async def async_send_heartbeat(self):
- """ Must be implemented to be able to send heartbeat to API """
+ """Must be implemented to be able to send heartbeat to API"""
await self.ws.async_send_heartbeat()
async def _api_data_changed(self):
@@ -96,7 +210,9 @@ async def _ws_data_changed(self):
await self._extract_ws_data()
async def _api_status_changed(self, status: ConnectivityStatus):
- _LOGGER.info(f"API Status changed to {status.name}, WS Status: {self.ws.status.name}")
+ _LOGGER.info(
+ f"API Status changed to {status.name}, WS Status: {self.ws.status.name}"
+ )
if status == ConnectivityStatus.Connected:
await self.api.async_update()
@@ -111,11 +227,16 @@ async def _api_status_changed(self, status: ConnectivityStatus):
await self.ws.terminate()
async def _ws_status_changed(self, status: ConnectivityStatus):
- _LOGGER.info(f"WS Status changed to {status.name}, API Status: {self.api.status.name}")
+ _LOGGER.info(
+ f"WS Status changed to {status.name}, API Status: {self.api.status.name}"
+ )
api_connected = self.api.status == ConnectivityStatus.Connected
ws_connected = status == ConnectivityStatus.Connected
- ws_reconnect = status in [ConnectivityStatus.NotConnected, ConnectivityStatus.Failed]
+ ws_reconnect = status in [
+ ConnectivityStatus.NotConnected,
+ ConnectivityStatus.Failed,
+ ]
self._can_load_components = ws_connected
@@ -131,17 +252,25 @@ async def async_component_initialize(self, entry: ConfigEntry):
await self.storage_api.initialize(self.config_data)
- update_entities_interval = timedelta(seconds=self.storage_api.update_entities_interval)
- update_api_interval = timedelta(seconds=self.storage_api.update_api_interval)
+ update_entities_interval = timedelta(
+ seconds=self.storage_api.update_entities_interval
+ )
+ update_api_interval = timedelta(
+ seconds=self.storage_api.update_api_interval
+ )
- _LOGGER.info(f"Setting intervals, API: {update_api_interval}, Entities: {update_entities_interval}")
+ _LOGGER.info(
+ f"Setting intervals, API: {update_api_interval}, Entities: {update_entities_interval}"
+ )
self.update_intervals(update_entities_interval, update_api_interval)
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to async_component_initialize, error: {ex}, line: {line_number}")
+ _LOGGER.error(
+ f"Failed to async_component_initialize, error: {ex}, line: {line_number}"
+ )
async def async_initialize_data_providers(self):
await self.storage_api.initialize(self.config_data)
@@ -157,7 +286,9 @@ async def async_initialize_data_providers(self):
migration_data[option_key] = entry_options.get(option_key)
if self._entry.data is not None:
- migration_data[STORAGE_DATA_UNIT] = self._entry.data.get(STORAGE_DATA_UNIT)
+ migration_data[STORAGE_DATA_UNIT] = self._entry.data.get(
+ STORAGE_DATA_UNIT
+ )
updated = await self._update_configuration_data(migration_data)
@@ -172,9 +303,9 @@ async def async_initialize_data_providers(self):
options = {}
- self._hass.config_entries.async_update_entry(self._entry,
- data=data,
- options=options)
+ self._hass.config_entries.async_update_entry(
+ self._entry, data=data, options=options
+ )
_LOGGER.info("Configuration migration completed, reloading integration")
@@ -194,13 +325,17 @@ async def async_update_data_providers(self):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to async_update_data_providers, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to async_update_data_providers, Error: {ex}, Line: {line_number}"
+ )
def register_services(self, entry: ConfigEntry | None = None):
- self._hass.services.async_register(DOMAIN,
- SERVICE_UPDATE_CONFIGURATION,
- self._update_configuration,
- SERVICE_SCHEMA_UPDATE_CONFIGURATION)
+ self._hass.services.async_register(
+ DOMAIN,
+ SERVICE_UPDATE_CONFIGURATION,
+ self._update_configuration,
+ SERVICE_SCHEMA_UPDATE_CONFIGURATION,
+ )
def load_devices(self):
if not self._can_load_components:
@@ -246,7 +381,9 @@ def load_entities(self):
for stats_data_key in stats_data:
stats_data_item = stats_data.get(stats_data_key)
- self._load_device_stats_sensor(device_item, stats_data_key, stats_data_item)
+ self._load_device_stats_sensor(
+ device_item, stats_data_key, stats_data_item
+ )
for unique_id in self._interfaces:
interface_item = self._interfaces.get(unique_id)
@@ -268,7 +405,9 @@ def load_entities(self):
for stats_data_key in stats_data:
stats_data_item = stats_data.get(stats_data_key)
- self._load_interface_stats_sensor(interface_item, stats_data_key, stats_data_item)
+ self._load_interface_stats_sensor(
+ interface_item, stats_data_key, stats_data_item
+ )
def get_device_name(self, device: EdgeOSDeviceData):
return f"{self.system_name} Device {device.hostname}"
@@ -307,7 +446,9 @@ async def _extract_ws_data(self):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract WS data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract WS data, Error: {ex}, Line: {line_number}"
+ )
async def _extract_api_data(self):
try:
@@ -334,12 +475,16 @@ async def _extract_api_data(self):
if len(warning_messages) > 0:
warning_message = " and ".join(warning_messages)
- _LOGGER.warning(f"Integration will not work correctly since {warning_message}")
+ _LOGGER.warning(
+ f"Integration will not work correctly since {warning_message}"
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract API data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract API data, Error: {ex}, Line: {line_number}"
+ )
def get_debug_data(self) -> dict:
messages = {}
@@ -354,7 +499,7 @@ def get_debug_data(self) -> dict:
API_DATA_SYSTEM: self._system,
DEVICE_LIST: self._devices,
API_DATA_INTERFACES: self._interfaces,
- MESSAGES_COUNTER_SECTION: messages
+ MESSAGES_COUNTER_SECTION: messages,
}
return data
@@ -372,16 +517,25 @@ def _extract_system(self, data: dict, system_info: dict):
system_data.ntp_servers = ntp.get(SYSTEM_DATA_NTP_SERVER)
offload: dict = system_details.get(SYSTEM_DATA_OFFLOAD, {})
- hardware_offload = EdgeOSSystemData.is_enabled(offload, SYSTEM_DATA_OFFLOAD_HW_NAT)
- ipsec_offload = EdgeOSSystemData.is_enabled(offload, SYSTEM_DATA_OFFLOAD_IPSEC)
+ hardware_offload = EdgeOSSystemData.is_enabled(
+ offload, SYSTEM_DATA_OFFLOAD_HW_NAT
+ )
+ ipsec_offload = EdgeOSSystemData.is_enabled(
+ offload, SYSTEM_DATA_OFFLOAD_IPSEC
+ )
system_data.hardware_offload = hardware_offload
system_data.ipsec_offload = ipsec_offload
- traffic_analysis: dict = system_details.get(SYSTEM_DATA_TRAFFIC_ANALYSIS, {})
- dpi = EdgeOSSystemData.is_enabled(traffic_analysis, SYSTEM_DATA_TRAFFIC_ANALYSIS_DPI)
- traffic_analysis_export = EdgeOSSystemData.is_enabled(traffic_analysis,
- SYSTEM_DATA_TRAFFIC_ANALYSIS_EXPORT)
+ traffic_analysis: dict = system_details.get(
+ SYSTEM_DATA_TRAFFIC_ANALYSIS, {}
+ )
+ dpi = EdgeOSSystemData.is_enabled(
+ traffic_analysis, SYSTEM_DATA_TRAFFIC_ANALYSIS_DPI
+ )
+ traffic_analysis_export = EdgeOSSystemData.is_enabled(
+ traffic_analysis, SYSTEM_DATA_TRAFFIC_ANALYSIS_EXPORT
+ )
system_data.deep_packet_inspection = dpi
system_data.traffic_analysis_export = traffic_analysis_export
@@ -393,7 +547,9 @@ def _extract_system(self, data: dict, system_info: dict):
fw_latest_version = fw_latest.get(SYSTEM_INFO_DATA_FW_LATEST_VERSION)
fw_latest_url = fw_latest.get(SYSTEM_INFO_DATA_FW_LATEST_URL)
- system_data.upgrade_available = fw_latest_state == FW_LATEST_STATE_CAN_UPGRADE
+ system_data.upgrade_available = (
+ fw_latest_state == FW_LATEST_STATE_CAN_UPGRADE
+ )
system_data.upgrade_url = fw_latest_url
system_data.upgrade_version = fw_latest_version
@@ -417,7 +573,9 @@ def _extract_system(self, data: dict, system_info: dict):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract System data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract System data, Error: {ex}, Line: {line_number}"
+ )
def _extract_interfaces(self, data: dict):
try:
@@ -428,15 +586,21 @@ def _extract_interfaces(self, data: dict):
for interface_name in interfaces:
interface_data = interfaces.get(interface_name, {})
- self._extract_interface(interface_name, interface_data, interface_type)
+ self._extract_interface(
+ interface_name, interface_data, interface_type
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract Interfaces data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract Interfaces data, Error: {ex}, Line: {line_number}"
+ )
- def _extract_interface(self, name: str, data: dict, interface_type: str | None = None) -> EdgeOSInterfaceData:
+ def _extract_interface(
+ self, name: str, data: dict, interface_type: str | None = None
+ ) -> EdgeOSInterfaceData:
interface = self._interfaces.get(name)
try:
@@ -460,7 +624,9 @@ def _extract_interface(self, name: str, data: dict, interface_type: str | None =
interface.max_age = data.get(INTERFACE_DATA_MAX_AGE)
interface.priority = data.get(INTERFACE_DATA_PRIORITY)
interface.promiscuous = data.get(INTERFACE_DATA_PROMISCUOUS)
- interface.stp = data.get(INTERFACE_DATA_STP, FALSE_STR).lower() == TRUE_STR
+ interface.stp = (
+ data.get(INTERFACE_DATA_STP, FALSE_STR).lower() == TRUE_STR
+ )
self._interfaces[interface.unique_id] = interface
@@ -480,8 +646,12 @@ def _extract_interface(self, name: str, data: dict, interface_type: str | None =
def _update_interface_stats(interface: EdgeOSInterfaceData, data: dict):
try:
if data is not None:
- interface.up = str(data.get(INTERFACE_DATA_UP, False)).lower() == TRUE_STR
- interface.l1up = str(data.get(INTERFACE_DATA_LINK_UP, False)).lower() == TRUE_STR
+ interface.up = (
+ str(data.get(INTERFACE_DATA_UP, False)).lower() == TRUE_STR
+ )
+ interface.l1up = (
+ str(data.get(INTERFACE_DATA_LINK_UP, False)).lower() == TRUE_STR
+ )
interface.mac = data.get(INTERFACE_DATA_MAC)
interface.multicast = data.get(INTERFACE_DATA_MULTICAST, 0)
interface.address = data.get(ADDRESS_LIST, [])
@@ -586,7 +756,7 @@ def _extract_unknown_devices(self):
static_mapping_data = {
DHCP_SERVER_IP_ADDRESS: ip,
- DHCP_SERVER_MAC_ADDRESS: device_data.get(DEVICE_DATA_MAC)
+ DHCP_SERVER_MAC_ADDRESS: device_data.get(DEVICE_DATA_MAC),
}
self._set_device(hostname, None, static_mapping_data, True)
@@ -594,7 +764,9 @@ def _extract_unknown_devices(self):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract Unknown Devices data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract Unknown Devices data, Error: {ex}, Line: {line_number}"
+ )
def _extract_devices(self, data: dict):
try:
@@ -603,7 +775,9 @@ def _extract_devices(self, data: dict):
shared_network_names = dhcp_server.get(DHCP_SERVER_SHARED_NETWORK_NAME, {})
for shared_network_name in shared_network_names:
- shared_network_name_data = shared_network_names.get(shared_network_name, {})
+ shared_network_name_data = shared_network_names.get(
+ shared_network_name, {}
+ )
subnets = shared_network_name_data.get(DHCP_SERVER_SUBNET, {})
for subnet in subnets:
@@ -615,21 +789,33 @@ def _extract_devices(self, data: dict):
for hostname in static_mappings:
static_mapping_data = static_mappings.get(hostname, {})
- self._set_device(hostname, domain_name, static_mapping_data, False)
+ self._set_device(
+ hostname, domain_name, static_mapping_data, False
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to extract Devices data, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to extract Devices data, Error: {ex}, Line: {line_number}"
+ )
- def _set_device(self, hostname: str, domain_name: str | None, static_mapping_data: dict, is_leased: bool):
+ def _set_device(
+ self,
+ hostname: str,
+ domain_name: str | None,
+ static_mapping_data: dict,
+ is_leased: bool,
+ ):
ip_address = static_mapping_data.get(DHCP_SERVER_IP_ADDRESS)
mac_address = static_mapping_data.get(DHCP_SERVER_MAC_ADDRESS)
existing_device_data = self._devices.get(mac_address)
if existing_device_data is None:
- device_data = EdgeOSDeviceData(hostname, ip_address, mac_address, domain_name, is_leased)
+ device_data = EdgeOSDeviceData(
+ hostname, ip_address, mac_address, domain_name, is_leased
+ )
else:
device_data = existing_device_data
@@ -649,14 +835,16 @@ def _get_device_by_ip(self, ip: str) -> EdgeOSDeviceData | None:
return device
- def _set_ha_device(self, name: str, model: str, manufacturer: str, version: str | None = None):
+ def _set_ha_device(
+ self, name: str, model: str, manufacturer: str, version: str | None = None
+ ):
device_details = self.device_manager.get(name)
device_details_data = {
"identifiers": {(DEFAULT_NAME, name)},
"name": name,
"manufacturer": manufacturer,
- "model": model
+ "model": model,
}
if version is not None:
@@ -668,7 +856,12 @@ def _set_ha_device(self, name: str, model: str, manufacturer: str, version: str
_LOGGER.debug(f"Created HA device {name} [{model}]")
def _load_main_device(self):
- self._set_ha_device(self.system_name, self._system.product, MANUFACTURER, self._system.fw_version)
+ self._set_ha_device(
+ self.system_name,
+ self._system.product,
+ MANUFACTURER,
+ self._system.fw_version,
+ )
def _load_device_device(self, device: EdgeOSDeviceData):
name = self.get_device_name(device)
@@ -694,21 +887,23 @@ def _load_unit_select(self):
key=unique_id,
name=entity_name,
device_class=f"{DOMAIN}__{STORAGE_DATA_UNIT}",
- options=list([unit.replace(ATTR_BYTE[1:], "").upper() for unit in UNIT_MAPPING.keys()]),
- entity_category=EntityCategory.CONFIG
+ options=list(UNIT_MAPPING.keys()),
+ entity_category=EntityCategory.CONFIG,
)
self.set_action(unique_id, ACTION_CORE_ENTITY_SELECT_OPTION, self._set_unit)
- self.entity_manager.set_entity(DOMAIN_SELECT,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SELECT,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(ex, f"Failed to load select for Data Unit")
+ self.log_exception(ex, "Failed to load select for Data Unit")
def _load_unknown_devices_sensor(self):
device_name = self.system_name
@@ -727,7 +922,7 @@ def _load_unknown_devices_sensor(self):
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
- DHCP_SERVER_LEASED: leased_devices
+ DHCP_SERVER_LEASED: leased_devices,
}
unique_id = EntityData.generate_unique_id(DOMAIN_SENSOR, entity_name)
@@ -737,20 +932,20 @@ def _load_unknown_devices_sensor(self):
key=unique_id,
name=entity_name,
icon=icon,
- state_class=SensorStateClass.MEASUREMENT
+ state_class=SensorStateClass.MEASUREMENT,
)
- self.entity_manager.set_entity(DOMAIN_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_cpu_sensor(self):
device_name = self.system_name
@@ -774,17 +969,17 @@ def _load_cpu_sensor(self):
native_unit_of_measurement="%",
)
- self.entity_manager.set_entity(DOMAIN_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_ram_sensor(self):
device_name = self.system_name
@@ -793,9 +988,7 @@ def _load_ram_sensor(self):
try:
state = self._system.mem
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SENSOR, entity_name)
icon = "mdi:memory"
@@ -808,17 +1001,17 @@ def _load_ram_sensor(self):
native_unit_of_measurement="%",
)
- self.entity_manager.set_entity(DOMAIN_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_uptime_sensor(self):
device_name = self.system_name
@@ -827,9 +1020,7 @@ def _load_uptime_sensor(self):
try:
state = self._system.uptime
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SENSOR, entity_name)
icon = "mdi:credit-card-clock"
@@ -838,20 +1029,20 @@ def _load_uptime_sensor(self):
key=unique_id,
name=entity_name,
icon=icon,
- state_class=SensorStateClass.TOTAL_INCREASING
+ state_class=SensorStateClass.TOTAL_INCREASING,
)
- self.entity_manager.set_entity(DOMAIN_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_firmware_upgrade_binary_sensor(self):
device_name = self.system_name
@@ -863,7 +1054,7 @@ def _load_firmware_upgrade_binary_sensor(self):
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
SYSTEM_INFO_DATA_FW_LATEST_URL: self._system.upgrade_url,
- SYSTEM_INFO_DATA_FW_LATEST_VERSION: self._system.upgrade_version
+ SYSTEM_INFO_DATA_FW_LATEST_VERSION: self._system.upgrade_version,
}
unique_id = EntityData.generate_unique_id(DOMAIN_BINARY_SENSOR, entity_name)
@@ -871,20 +1062,20 @@ def _load_firmware_upgrade_binary_sensor(self):
entity_description = BinarySensorEntityDescription(
key=unique_id,
name=entity_name,
- device_class=BinarySensorDeviceClass.UPDATE
+ device_class=BinarySensorDeviceClass.UPDATE,
)
- self.entity_manager.set_entity(DOMAIN_BINARY_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_BINARY_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_log_incoming_messages_switch(self):
device_name = self.system_name
@@ -893,9 +1084,7 @@ def _load_log_incoming_messages_switch(self):
try:
state = self.storage_api.log_incoming_messages
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SWITCH, entity_name)
@@ -905,18 +1094,28 @@ def _load_log_incoming_messages_switch(self):
key=unique_id,
name=entity_name,
icon=icon,
- entity_category=EntityCategory.CONFIG
+ entity_category=EntityCategory.CONFIG,
)
- self.entity_manager.set_entity(DOMAIN_SWITCH,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_SWITCH,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ )
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_ON, self._enable_log_incoming_messages)
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._disable_log_incoming_messages)
+ self.set_action(
+ unique_id,
+ ACTION_CORE_ENTITY_TURN_ON,
+ self._enable_log_incoming_messages,
+ )
+ self.set_action(
+ unique_id,
+ ACTION_CORE_ENTITY_TURN_OFF,
+ self._disable_log_incoming_messages,
+ )
except Exception as ex:
self.log_exception(
@@ -928,40 +1127,42 @@ def _load_device_tracker(self, device: EdgeOSDeviceData):
entity_name = f"{device_name}"
try:
- state = device.last_activity_in_seconds <= self.storage_api.consider_away_interval
+ state = (
+ device.last_activity_in_seconds
+ <= self.storage_api.consider_away_interval
+ )
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
- LAST_ACTIVITY: device.last_activity_in_seconds
+ LAST_ACTIVITY: device.last_activity_in_seconds,
}
- unique_id = EntityData.generate_unique_id(DOMAIN_DEVICE_TRACKER, entity_name)
-
- entity_description = EntityDescription(
- key=unique_id,
- name=entity_name
+ unique_id = EntityData.generate_unique_id(
+ DOMAIN_DEVICE_TRACKER, entity_name
)
- details = {
- ENTITY_UNIQUE_ID: device.unique_id
- }
+ entity_description = EntityDescription(key=unique_id, name=entity_name)
- is_monitored = self.storage_api.monitored_devices.get(device.unique_id, False)
+ details = {ENTITY_UNIQUE_ID: device.unique_id}
- self.entity_manager.set_entity(DOMAIN_DEVICE_TRACKER,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description,
- destructors=[not is_monitored],
- details=details)
+ is_monitored = self.storage_api.monitored_devices.get(
+ device.unique_id, False
+ )
- except Exception as ex:
- self.log_exception(
- ex, f"Failed to load device tracker for {entity_name}"
+ self.entity_manager.set_entity(
+ DOMAIN_DEVICE_TRACKER,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ destructors=[not is_monitored],
+ details=details,
)
+ except Exception as ex:
+ self.log_exception(ex, f"Failed to load device tracker for {entity_name}")
+
def _load_device_monitor_switch(self, device: EdgeOSDeviceData):
device_name = self.get_device_name(device)
entity_name = f"{device_name} Monitored"
@@ -969,9 +1170,7 @@ def _load_device_monitor_switch(self, device: EdgeOSDeviceData):
try:
state = self.storage_api.monitored_devices.get(device.unique_id, False)
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SWITCH, entity_name)
icon = "mdi:monitor-eye"
@@ -980,34 +1179,37 @@ def _load_device_monitor_switch(self, device: EdgeOSDeviceData):
key=unique_id,
name=entity_name,
icon=icon,
- entity_category=EntityCategory.CONFIG
+ entity_category=EntityCategory.CONFIG,
)
- details = {
- ENTITY_UNIQUE_ID: device.unique_id
- }
-
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_device_monitored)
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_device_unmonitored)
+ details = {ENTITY_UNIQUE_ID: device.unique_id}
- self.entity_manager.set_entity(DOMAIN_SWITCH,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description,
- details=details)
-
- except Exception as ex:
- self.log_exception(
- ex, f"Failed to load switch for {entity_name}"
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_device_monitored
+ )
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_device_unmonitored
)
- def _load_device_stats_sensor(self,
- device: EdgeOSDeviceData,
- entity_suffix: str,
- state: str | int | float | None):
+ self.entity_manager.set_entity(
+ DOMAIN_SWITCH,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ details=details,
+ )
+ except Exception as ex:
+ self.log_exception(ex, f"Failed to load switch for {entity_name}")
+
+ def _load_device_stats_sensor(
+ self,
+ device: EdgeOSDeviceData,
+ entity_suffix: str,
+ state: str | int | float | None,
+ ):
device_name = self.get_device_name(device)
entity_name = f"{device_name} {entity_suffix}"
@@ -1027,19 +1229,34 @@ def _load_device_stats_sensor(self,
else:
unit_of_measurement = str(STATS_UNITS.get(entity_suffix)).capitalize()
- state_class = SensorStateClass.MEASUREMENT if is_rate_stats else SensorStateClass.TOTAL_INCREASING
-
- self._load_stats_sensor(device_name, entity_name, state, unit_of_measurement, icon, state_class, is_monitored)
-
- def _load_interface_stats_sensor(self,
- interface: EdgeOSInterfaceData,
- entity_suffix: str,
- state: str | int | float | None):
-
+ state_class = (
+ SensorStateClass.MEASUREMENT
+ if is_rate_stats
+ else SensorStateClass.TOTAL_INCREASING
+ )
+
+ self._load_stats_sensor(
+ device_name,
+ entity_name,
+ state,
+ unit_of_measurement,
+ icon,
+ state_class,
+ is_monitored,
+ )
+
+ def _load_interface_stats_sensor(
+ self,
+ interface: EdgeOSInterfaceData,
+ entity_suffix: str,
+ state: str | int | float | None,
+ ):
device_name = self.get_interface_name(interface)
entity_name = f"{device_name} {entity_suffix}"
- is_monitored = self.storage_api.monitored_interfaces.get(interface.unique_id, False)
+ is_monitored = self.storage_api.monitored_interfaces.get(
+ interface.unique_id, False
+ )
is_rate_stats = entity_suffix in STATS_RATE
icon = STATS_ICONS.get(entity_suffix)
@@ -1055,22 +1272,34 @@ def _load_interface_stats_sensor(self,
else:
unit_of_measurement = str(STATS_UNITS.get(entity_suffix)).capitalize()
- state_class = SensorStateClass.MEASUREMENT if is_rate_stats else SensorStateClass.TOTAL_INCREASING
-
- self._load_stats_sensor(device_name, entity_name, state, unit_of_measurement, icon, state_class, is_monitored)
-
- def _load_stats_sensor(self,
- device_name: str,
- entity_name: str,
- state: str | int | float | None,
- unit_of_measurement: str,
- icon: str | None,
- state_class: SensorStateClass,
- is_monitored: bool):
+ state_class = (
+ SensorStateClass.MEASUREMENT
+ if is_rate_stats
+ else SensorStateClass.TOTAL_INCREASING
+ )
+
+ self._load_stats_sensor(
+ device_name,
+ entity_name,
+ state,
+ unit_of_measurement,
+ icon,
+ state_class,
+ is_monitored,
+ )
+
+ def _load_stats_sensor(
+ self,
+ device_name: str,
+ entity_name: str,
+ state: str | int | float | None,
+ unit_of_measurement: str,
+ icon: str | None,
+ state_class: SensorStateClass,
+ is_monitored: bool,
+ ):
try:
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SENSOR, entity_name)
@@ -1082,21 +1311,25 @@ def _load_stats_sensor(self,
native_unit_of_measurement=unit_of_measurement,
)
- if unit_of_measurement.lower() in [TRAFFIC_DATA_ERRORS, TRAFFIC_DATA_PACKETS, TRAFFIC_DATA_DROPPED]:
+ if unit_of_measurement.lower() in [
+ TRAFFIC_DATA_ERRORS,
+ TRAFFIC_DATA_PACKETS,
+ TRAFFIC_DATA_DROPPED,
+ ]:
state = self._format_number(state)
- self.entity_manager.set_entity(DOMAIN_SENSOR,
- self.entry_id,
- state,
- attributes,
- device_name,
- entity_description,
- destructors=[not is_monitored])
+ self.entity_manager.set_entity(
+ DOMAIN_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ destructors=[not is_monitored],
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load sensor for {entity_name}")
def _load_interface_status_switch(self, interface: EdgeOSInterfaceData):
interface_name = self.get_interface_name(interface)
@@ -1107,7 +1340,7 @@ def _load_interface_status_switch(self, interface: EdgeOSInterfaceData):
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
- ADDRESS_LIST: interface.address
+ ADDRESS_LIST: interface.address,
}
unique_id = EntityData.generate_unique_id(DOMAIN_SWITCH, entity_name)
@@ -1117,28 +1350,30 @@ def _load_interface_status_switch(self, interface: EdgeOSInterfaceData):
key=unique_id,
name=entity_name,
icon=icon,
- entity_category=EntityCategory.CONFIG
+ entity_category=EntityCategory.CONFIG,
)
- details = {
- ENTITY_UNIQUE_ID: interface.unique_id
- }
+ details = {ENTITY_UNIQUE_ID: interface.unique_id}
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_interface_enabled)
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_interface_disabled)
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_interface_enabled
+ )
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_interface_disabled
+ )
- self.entity_manager.set_entity(DOMAIN_SWITCH,
- self.entry_id,
- state,
- attributes,
- interface_name,
- entity_description,
- details=details)
+ self.entity_manager.set_entity(
+ DOMAIN_SWITCH,
+ self.entry_id,
+ state,
+ attributes,
+ interface_name,
+ entity_description,
+ details=details,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load switch for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load switch for {entity_name}")
def _load_interface_status_binary_sensor(self, interface: EdgeOSInterfaceData):
interface_name = self.get_interface_name(interface)
@@ -1149,7 +1384,7 @@ def _load_interface_status_binary_sensor(self, interface: EdgeOSInterfaceData):
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
- ADDRESS_LIST: interface.address
+ ADDRESS_LIST: interface.address,
}
unique_id = EntityData.generate_unique_id(DOMAIN_BINARY_SENSOR, entity_name)
@@ -1157,20 +1392,20 @@ def _load_interface_status_binary_sensor(self, interface: EdgeOSInterfaceData):
entity_description = BinarySensorEntityDescription(
key=unique_id,
name=entity_name,
- device_class=BinarySensorDeviceClass.CONNECTIVITY
+ device_class=BinarySensorDeviceClass.CONNECTIVITY,
)
- self.entity_manager.set_entity(DOMAIN_BINARY_SENSOR,
- self.entry_id,
- state,
- attributes,
- interface_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_BINARY_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ interface_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load binary sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load binary sensor for {entity_name}")
def _load_interface_connected_binary_sensor(self, interface: EdgeOSInterfaceData):
interface_name = self.get_interface_name(interface)
@@ -1181,7 +1416,7 @@ def _load_interface_connected_binary_sensor(self, interface: EdgeOSInterfaceData
attributes = {
ATTR_FRIENDLY_NAME: entity_name,
- ADDRESS_LIST: interface.address
+ ADDRESS_LIST: interface.address,
}
unique_id = EntityData.generate_unique_id(DOMAIN_BINARY_SENSOR, entity_name)
@@ -1189,31 +1424,31 @@ def _load_interface_connected_binary_sensor(self, interface: EdgeOSInterfaceData
entity_description = BinarySensorEntityDescription(
key=unique_id,
name=entity_name,
- device_class=BinarySensorDeviceClass.CONNECTIVITY
+ device_class=BinarySensorDeviceClass.CONNECTIVITY,
)
- self.entity_manager.set_entity(DOMAIN_BINARY_SENSOR,
- self.entry_id,
- state,
- attributes,
- interface_name,
- entity_description)
+ self.entity_manager.set_entity(
+ DOMAIN_BINARY_SENSOR,
+ self.entry_id,
+ state,
+ attributes,
+ interface_name,
+ entity_description,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load binary sensor for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load binary sensor for {entity_name}")
def _load_interface_monitor_switch(self, interface: EdgeOSInterfaceData):
interface_name = self.get_interface_name(interface)
entity_name = f"{interface_name} Monitored"
try:
- state = self.storage_api.monitored_interfaces.get(interface.unique_id, False)
+ state = self.storage_api.monitored_interfaces.get(
+ interface.unique_id, False
+ )
- attributes = {
- ATTR_FRIENDLY_NAME: entity_name
- }
+ attributes = {ATTR_FRIENDLY_NAME: entity_name}
unique_id = EntityData.generate_unique_id(DOMAIN_SWITCH, entity_name)
icon = None
@@ -1222,28 +1457,30 @@ def _load_interface_monitor_switch(self, interface: EdgeOSInterfaceData):
key=unique_id,
name=entity_name,
icon=icon,
- entity_category=EntityCategory.CONFIG
+ entity_category=EntityCategory.CONFIG,
)
- details = {
- ENTITY_UNIQUE_ID: interface.unique_id
- }
+ details = {ENTITY_UNIQUE_ID: interface.unique_id}
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_interface_monitored)
- self.set_action(unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_interface_unmonitored)
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_ON, self._set_interface_monitored
+ )
+ self.set_action(
+ unique_id, ACTION_CORE_ENTITY_TURN_OFF, self._set_interface_unmonitored
+ )
- self.entity_manager.set_entity(DOMAIN_SWITCH,
- self.entry_id,
- state,
- attributes,
- interface_name,
- entity_description,
- details=details)
+ self.entity_manager.set_entity(
+ DOMAIN_SWITCH,
+ self.entry_id,
+ state,
+ attributes,
+ interface_name,
+ entity_description,
+ details=details,
+ )
except Exception as ex:
- self.log_exception(
- ex, f"Failed to load switch for {entity_name}"
- )
+ self.log_exception(ex, f"Failed to load switch for {entity_name}")
async def _set_interface_enabled(self, entity: EntityData):
interface_item = self._get_interface_from_entity(entity)
@@ -1333,11 +1570,9 @@ def _get_rate_unit_of_measurement(self) -> str:
return result
async def _reload_integration(self):
- data = {
- ENTITY_CONFIG_ENTRY_ID: self.entry_id
- }
+ data = {ENTITY_CONFIG_ENTRY_ID: self.entry_id}
- await self._hass.services.async_call(HA_NAME, SERVICE_RELOAD, data)
+ await self._hass.services.async_call(HA_NAME, SERVICE_RELOAD_CONFIG_ENTRY, data)
def _update_configuration(self, service_call):
self._hass.async_create_task(self._async_update_configuration(service_call))
@@ -1365,12 +1600,14 @@ async def _async_update_configuration(self, service_call):
async def _update_configuration_data(self, data: dict):
result = False
- storage_data_import_keys: dict[str, Callable[[int | bool | str], Awaitable[None]]] = {
+ storage_data_import_keys: dict[
+ str, Callable[[int | bool | str], Awaitable[None]]
+ ] = {
STORAGE_DATA_CONSIDER_AWAY_INTERVAL: self.storage_api.set_consider_away_interval,
STORAGE_DATA_UPDATE_ENTITIES_INTERVAL: self.storage_api.set_update_entities_interval,
STORAGE_DATA_UPDATE_API_INTERVAL: self.storage_api.set_update_api_interval,
STORAGE_DATA_LOG_INCOMING_MESSAGES: self.storage_api.set_log_incoming_messages,
- STORAGE_DATA_UNIT: self.storage_api.set_unit
+ STORAGE_DATA_UNIT: self.storage_api.set_unit,
}
for key in storage_data_import_keys:
diff --git a/custom_components/edgeos/component/models/edge_os_device_data.py b/custom_components/edgeos/component/models/edge_os_device_data.py
index d0d7c3e..7150b41 100644
--- a/custom_components/edgeos/component/models/edge_os_device_data.py
+++ b/custom_components/edgeos/component/models/edge_os_device_data.py
@@ -1,8 +1,23 @@
from __future__ import annotations
-from datetime import datetime
-
-from ..helpers.const import *
+from datetime import datetime, timedelta
+
+from ...core.helpers.const import ENTITY_UNIQUE_ID
+from ..helpers.const import (
+ DEVICE_DATA_DOMAIN,
+ DEVICE_DATA_IP,
+ DEVICE_DATA_MAC,
+ DEVICE_DATA_NAME,
+ DEVICE_DATA_RECEIVED,
+ DEVICE_DATA_SENT,
+ DHCP_SERVER_LEASED,
+ RECEIVED_RATE_PREFIX,
+ RECEIVED_TRAFFIC_PREFIX,
+ SENT_RATE_PREFIX,
+ SENT_TRAFFIC_PREFIX,
+ TRAFFIC_DATA_DIRECTION_RECEIVED,
+ TRAFFIC_DATA_DIRECTION_SENT,
+)
from .edge_os_traffic_data import EdgeOSTrafficData
@@ -14,7 +29,9 @@ class EdgeOSDeviceData:
is_leased: bool
traffic: EdgeOSTrafficData
- def __init__(self, hostname: str, ip: str, mac: str, domain: str | None, is_leased: bool):
+ def __init__(
+ self, hostname: str, ip: str, mac: str, domain: str | None, is_leased: bool
+ ):
self.hostname = hostname
self.ip = ip
self.mac = mac
@@ -32,26 +49,28 @@ def get_stats(self):
RECEIVED_RATE_PREFIX: self.received.rate,
RECEIVED_TRAFFIC_PREFIX: self.received.total,
SENT_RATE_PREFIX: self.sent.rate,
- SENT_TRAFFIC_PREFIX: self.sent.total
+ SENT_TRAFFIC_PREFIX: self.sent.total,
}
return data
@property
- def last_activity(self):
- received_activity = self.received.last_activity
- sent_activity = self.sent.last_activity
+ def last_activity(self) -> int:
+ received_activity = int(self.received.last_activity)
+ sent_activity = int(self.sent.last_activity)
- last_activity = received_activity if received_activity > sent_activity else sent_activity
+ last_activity = (
+ received_activity if received_activity > sent_activity else sent_activity
+ )
return last_activity
@property
- def last_activity_in_seconds(self) -> float:
+ def last_activity_in_seconds(self) -> int:
now = datetime.now().timestamp()
- diff = int(now) - self.last_activity
- last_activity_in_seconds = timedelta(seconds=diff).total_seconds()
+ diff = int(now) - int(self.last_activity)
+ last_activity_in_seconds = int(timedelta(seconds=diff).total_seconds())
return last_activity_in_seconds
@@ -64,7 +83,7 @@ def to_dict(self):
DEVICE_DATA_RECEIVED: self.received.to_dict(),
DEVICE_DATA_SENT: self.sent.to_dict(),
ENTITY_UNIQUE_ID: self.unique_id,
- DHCP_SERVER_LEASED: self.is_leased
+ DHCP_SERVER_LEASED: self.is_leased,
}
return obj
diff --git a/custom_components/edgeos/component/models/edge_os_interface_data.py b/custom_components/edgeos/component/models/edge_os_interface_data.py
index 453ba9c..4116c18 100644
--- a/custom_components/edgeos/component/models/edge_os_interface_data.py
+++ b/custom_components/edgeos/component/models/edge_os_interface_data.py
@@ -1,6 +1,40 @@
from __future__ import annotations
-from ..helpers.const import *
+from ...core.helpers.const import ENTITY_UNIQUE_ID
+from ..helpers.const import (
+ IGNORED_INTERFACES,
+ INTERFACE_DATA_ADDRESS,
+ INTERFACE_DATA_AGING,
+ INTERFACE_DATA_BRIDGE_GROUP,
+ INTERFACE_DATA_BRIDGED_CONNTRACK,
+ INTERFACE_DATA_DESCRIPTION,
+ INTERFACE_DATA_DUPLEX,
+ INTERFACE_DATA_HANDLER,
+ INTERFACE_DATA_HELLO_TIME,
+ INTERFACE_DATA_MAX_AGE,
+ INTERFACE_DATA_MULTICAST,
+ INTERFACE_DATA_NAME,
+ INTERFACE_DATA_PRIORITY,
+ INTERFACE_DATA_PROMISCUOUS,
+ INTERFACE_DATA_RECEIVED,
+ INTERFACE_DATA_SENT,
+ INTERFACE_DATA_SPEED,
+ INTERFACE_DATA_STP,
+ INTERFACE_DATA_TYPE,
+ RECEIVED_DROPPED_PREFIX,
+ RECEIVED_ERRORS_PREFIX,
+ RECEIVED_PACKETS_PREFIX,
+ RECEIVED_RATE_PREFIX,
+ RECEIVED_TRAFFIC_PREFIX,
+ SENT_DROPPED_PREFIX,
+ SENT_ERRORS_PREFIX,
+ SENT_PACKETS_PREFIX,
+ SENT_RATE_PREFIX,
+ SENT_TRAFFIC_PREFIX,
+ SPECIAL_INTERFACES,
+ TRAFFIC_DATA_DIRECTION_RECEIVED,
+ TRAFFIC_DATA_DIRECTION_SENT,
+)
from ..helpers.enums import InterfaceHandlers
from .edge_os_traffic_data import EdgeOSTrafficData
@@ -76,7 +110,7 @@ def to_dict(self):
INTERFACE_DATA_MULTICAST: self.multicast,
INTERFACE_DATA_RECEIVED: self.received.to_dict(),
INTERFACE_DATA_SENT: self.sent.to_dict(),
- ENTITY_UNIQUE_ID: self.unique_id
+ ENTITY_UNIQUE_ID: self.unique_id,
}
return obj
@@ -110,7 +144,7 @@ def get_stats(self):
SENT_TRAFFIC_PREFIX: self.sent.total,
SENT_DROPPED_PREFIX: self.sent.dropped,
SENT_ERRORS_PREFIX: self.sent.errors,
- SENT_PACKETS_PREFIX: self.sent.packets
+ SENT_PACKETS_PREFIX: self.sent.packets,
}
return data
diff --git a/custom_components/edgeos/component/models/edge_os_system_data.py b/custom_components/edgeos/component/models/edge_os_system_data.py
index 5df7f77..5fe3453 100644
--- a/custom_components/edgeos/component/models/edge_os_system_data.py
+++ b/custom_components/edgeos/component/models/edge_os_system_data.py
@@ -2,7 +2,18 @@
from datetime import datetime
-from ..helpers.const import *
+from ..helpers.const import (
+ DHCP_SERVER_LEASES,
+ SYSTEM_DATA_DISABLE,
+ SYSTEM_DATA_ENABLE,
+ SYSTEM_DATA_HOSTNAME,
+ SYSTEM_DATA_LOGIN_USER_LEVEL,
+ SYSTEM_DATA_NTP,
+ SYSTEM_DATA_OFFLOAD_HW_NAT,
+ SYSTEM_DATA_TIME_ZONE,
+ SYSTEM_DATA_TRAFFIC_ANALYSIS_DPI,
+ SYSTEM_DATA_TRAFFIC_ANALYSIS_EXPORT,
+)
class EdgeOSSystemData:
@@ -64,7 +75,7 @@ def to_dict(self):
SYSTEM_DATA_TRAFFIC_ANALYSIS_DPI: self.deep_packet_inspection,
SYSTEM_DATA_TRAFFIC_ANALYSIS_EXPORT: self.traffic_analysis_export,
DHCP_SERVER_LEASES: self.leased_devices,
- SYSTEM_DATA_LOGIN_USER_LEVEL: self.user_level
+ SYSTEM_DATA_LOGIN_USER_LEVEL: self.user_level,
}
return obj
diff --git a/custom_components/edgeos/component/models/edge_os_traffic_data.py b/custom_components/edgeos/component/models/edge_os_traffic_data.py
index d74b5d1..d3a5770 100644
--- a/custom_components/edgeos/component/models/edge_os_traffic_data.py
+++ b/custom_components/edgeos/component/models/edge_os_traffic_data.py
@@ -1,8 +1,17 @@
from __future__ import annotations
-from datetime import datetime
+from datetime import datetime, timedelta
-from ..helpers.const import *
+from ..helpers.const import (
+ TRAFFIC_DATA_DIRECTION,
+ TRAFFIC_DATA_DROPPED,
+ TRAFFIC_DATA_ERRORS,
+ TRAFFIC_DATA_LAST_ACTIVITY,
+ TRAFFIC_DATA_LAST_ACTIVITY_IN_SECONDS,
+ TRAFFIC_DATA_PACKETS,
+ TRAFFIC_DATA_RATE,
+ TRAFFIC_DATA_TOTAL,
+)
class EdgeOSTrafficData:
@@ -32,18 +41,22 @@ def update(self, data: dict):
if self.rate > 0:
now = datetime.now().timestamp()
- self.last_activity = now
+ self.last_activity = int(now)
def to_dict(self):
now = datetime.now().timestamp()
- diff = "N/A" if self.last_activity == 0 else timedelta(seconds=(int(now) - self.last_activity)).total_seconds()
+ diff = (
+ "N/A"
+ if self.last_activity == 0
+ else timedelta(seconds=(int(now) - self.last_activity)).total_seconds()
+ )
obj = {
TRAFFIC_DATA_DIRECTION: self.direction,
TRAFFIC_DATA_RATE: self.rate,
TRAFFIC_DATA_TOTAL: self.total,
TRAFFIC_DATA_LAST_ACTIVITY: self.last_activity,
- TRAFFIC_DATA_LAST_ACTIVITY_IN_SECONDS: diff
+ TRAFFIC_DATA_LAST_ACTIVITY_IN_SECONDS: diff,
}
if self.errors is not None:
diff --git a/custom_components/edgeos/config_flow.py b/custom_components/edgeos/config_flow.py
index 32081b4..50fabcd 100644
--- a/custom_components/edgeos/config_flow.py
+++ b/custom_components/edgeos/config_flow.py
@@ -4,13 +4,14 @@
import logging
from cryptography.fernet import InvalidToken
+import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from .component.api.api import IntegrationAPI
-from .component.helpers.const import *
+from .configuration.helpers.const import DEFAULT_NAME, DOMAIN
from .configuration.helpers.exceptions import AlreadyExistsError, LoginError
from .configuration.managers.configuration_manager import ConfigurationManager
@@ -56,10 +57,10 @@ async def async_step_user(self, user_input=None):
except LoginError as lex:
errors = lex.errors
- except InvalidToken as itex:
+ except InvalidToken:
errors = {"base": "corrupted_encryption_key"}
- except AlreadyExistsError as aeex:
+ except AlreadyExistsError:
errors = {"base": "already_configured"}
if errors is not None:
@@ -71,11 +72,7 @@ async def async_step_user(self, user_input=None):
schema = vol.Schema(new_user_input)
- return self.async_show_form(
- step_id="user",
- data_schema=schema,
- errors=errors
- )
+ return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
class DomainOptionsFlowHandler(config_entries.OptionsFlow):
@@ -104,16 +101,20 @@ async def async_step_init(self, user_input=None):
_LOGGER.debug("User inputs are valid")
- options = self._config_manager.remap_entry_data(self._config_entry, user_input)
+ options = self._config_manager.remap_entry_data(
+ self._config_entry, user_input
+ )
- return self.async_create_entry(title=self._config_entry.title, data=options)
+ return self.async_create_entry(
+ title=self._config_entry.title, data=options
+ )
except LoginError as lex:
errors = lex.errors
- except InvalidToken as itex:
+ except InvalidToken:
errors = {"base": "corrupted_encryption_key"}
- except AlreadyExistsError as aeex:
+ except AlreadyExistsError:
errors = {"base": "already_configured"}
if errors is not None:
@@ -121,12 +122,10 @@ async def async_step_init(self, user_input=None):
_LOGGER.warning(f"Failed to create integration, Error: {error_message}")
- new_user_input = self._config_manager.get_options_fields(self._config_entry.data)
+ new_user_input = self._config_manager.get_options_fields(
+ self._config_entry.data
+ )
schema = vol.Schema(new_user_input)
- return self.async_show_form(
- step_id="init",
- data_schema=schema,
- errors=errors
- )
+ return self.async_show_form(step_id="init", data_schema=schema, errors=errors)
diff --git a/custom_components/edgeos/configuration/helpers/const.py b/custom_components/edgeos/configuration/helpers/const.py
index 909e435..5055adf 100644
--- a/custom_components/edgeos/configuration/helpers/const.py
+++ b/custom_components/edgeos/configuration/helpers/const.py
@@ -5,17 +5,13 @@
SUPPORTED_PLATFORMS - list of supported HA components to initialize
"""
-from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
DOMAIN = "edgeos"
DEFAULT_NAME = "EdgeOS"
MANUFACTURER = "Ubiquiti"
-DATA_KEYS = [
- CONF_HOST,
- CONF_USERNAME,
- CONF_PASSWORD
-]
+DATA_KEYS = [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]
MAXIMUM_RECONNECT = 3
diff --git a/custom_components/edgeos/configuration/managers/configuration_manager.py b/custom_components/edgeos/configuration/managers/configuration_manager.py
index e447396..71d21aa 100644
--- a/custom_components/edgeos/configuration/managers/configuration_manager.py
+++ b/custom_components/edgeos/configuration/managers/configuration_manager.py
@@ -7,12 +7,13 @@
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from ...core.api.base_api import BaseAPI
-from ...core.helpers.const import *
from ...core.helpers.enums import ConnectivityStatus
from ...core.managers.password_manager import PasswordManager
+from ..helpers.const import DATA_KEYS
from ..helpers.exceptions import LoginError
from ..models.config_data import ConfigData
@@ -128,7 +129,9 @@ def get_options_fields(self, user_input: dict[str, Any]) -> dict[vol.Marker, Any
return fields
- def remap_entry_data(self, entry: ConfigEntry, options: dict[str, Any]) -> dict[str, Any]:
+ def remap_entry_data(
+ self, entry: ConfigEntry, options: dict[str, Any]
+ ) -> dict[str, Any]:
config_options = {}
config_data = {}
diff --git a/custom_components/edgeos/configuration/models/config_data.py b/custom_components/edgeos/configuration/models/config_data.py
index 5e54474..5ea9ad2 100644
--- a/custom_components/edgeos/configuration/models/config_data.py
+++ b/custom_components/edgeos/configuration/models/config_data.py
@@ -3,8 +3,9 @@
from typing import Any
from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
-from ..helpers.const import *
+from ..helpers.const import API_URL_TEMPLATE
class ConfigData:
@@ -40,7 +41,7 @@ def to_dict(self):
obj = {
CONF_HOST: self.host,
CONF_USERNAME: self.username,
- CONF_PASSWORD: self.password
+ CONF_PASSWORD: self.password,
}
return obj
diff --git a/custom_components/edgeos/core/api/base_api.py b/custom_components/edgeos/core/api/base_api.py
index 8586142..53c6035 100644
--- a/custom_components/edgeos/core/api/base_api.py
+++ b/custom_components/edgeos/core/api/base_api.py
@@ -24,12 +24,13 @@ class BaseAPI:
onDataChangedAsync: Callable[[], Awaitable[None]] | None = None
onStatusChangedAsync: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
- def __init__(self,
- hass: HomeAssistant | None,
- async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
- async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]] | None = None
- ):
-
+ def __init__(
+ self,
+ hass: HomeAssistant | None,
+ async_on_data_changed: Callable[[], Awaitable[None]] | None = None,
+ async_on_status_changed: Callable[[ConnectivityStatus], Awaitable[None]]
+ | None = None,
+ ):
self.hass = hass
self.status = ConnectivityStatus.NotConnected
self.data = {}
@@ -45,7 +46,9 @@ def is_home_assistant(self):
async def initialize_session(self, cookies=None, cookie_jar=None):
try:
if self.is_home_assistant:
- self.session = async_create_clientsession(hass=self.hass, cookies=cookies, cookie_jar=cookie_jar)
+ self.session = async_create_clientsession(
+ hass=self.hass, cookies=cookies, cookie_jar=cookie_jar
+ )
else:
self.session = ClientSession(cookies=cookies, cookie_jar=cookie_jar)
@@ -56,7 +59,9 @@ async def initialize_session(self, cookies=None, cookie_jar=None):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.warning(f"Failed to initialize session, Error: {str(ex)}, Line: {line_number}")
+ _LOGGER.warning(
+ f"Failed to initialize session, Error: {str(ex)}, Line: {line_number}"
+ )
await self.set_status(ConnectivityStatus.Failed)
diff --git a/custom_components/edgeos/core/components/binary_sensor.py b/custom_components/edgeos/core/components/binary_sensor.py
index 753a11b..979292e 100644
--- a/custom_components/edgeos/core/components/binary_sensor.py
+++ b/custom_components/edgeos/core/components/binary_sensor.py
@@ -7,13 +7,14 @@
from homeassistant.const import STATE_ON
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_BINARY_SENSOR
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
class CoreBinarySensor(BinarySensorEntity, BaseEntity):
"""Representation a binary sensor that is updated."""
+
@property
def is_on(self):
"""Return true if the binary sensor is on."""
diff --git a/custom_components/edgeos/core/components/camera.py b/custom_components/edgeos/core/components/camera.py
index 027d7e9..2e7e9b5 100644
--- a/custom_components/edgeos/core/components/camera.py
+++ b/custom_components/edgeos/core/components/camera.py
@@ -15,10 +15,19 @@
from homeassistant.components.camera import DEFAULT_CONTENT_TYPE, SUPPORT_STREAM, Camera
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError
-from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-
-from ..helpers.const import *
+import homeassistant.helpers.config_validation as cv
+
+from ..helpers.const import (
+ ATTR_MODE_RECORD,
+ ATTR_STREAM_FPS,
+ CONF_MOTION_DETECTION,
+ CONF_STILL_IMAGE_URL,
+ CONF_STREAM_SOURCE,
+ DOMAIN_CAMERA,
+ EMPTY_STRING,
+ SINGLE_FRAME_PS,
+)
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
@@ -26,7 +35,7 @@
class CoreCamera(Camera, BaseEntity, ABC):
- """ Camera """
+ """Camera"""
def __init__(self, hass, device_info):
super().__init__()
@@ -47,12 +56,13 @@ def initialize(
hass: HomeAssistant,
entity: EntityData,
current_domain: str,
+ DOMAIN_STREAM=None,
):
super().initialize(hass, entity, current_domain)
try:
if self.ha is None:
- _LOGGER.warning(f"Failed to initialize CoreCamera without HA manager")
+ _LOGGER.warning("Failed to initialize CoreCamera without HA manager")
return
config_data = self.ha.config_data
@@ -72,7 +82,9 @@ def initialize(
stream_support = DOMAIN_STREAM in self.hass.data
- stream_support_flag = SUPPORT_STREAM if stream_source and stream_support else 0
+ stream_support_flag = (
+ SUPPORT_STREAM if stream_source and stream_support else 0
+ )
self._still_image_url = still_image_url_template
self._still_image_url.hass = hass
@@ -90,7 +102,9 @@ def initialize(
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to initialize CoreCamera instance, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to initialize CoreCamera instance, Error: {ex}, Line: {line_number}"
+ )
@property
def is_recording(self) -> bool:
@@ -110,18 +124,24 @@ def frame_interval(self):
"""Return the interval between frames of the mjpeg stream."""
return self._frame_interval
- def camera_image(self, width: int | None = None, height: int | None = None) -> bytes | None:
+ def camera_image(
+ self, width: int | None = None, height: int | None = None
+ ) -> bytes | None:
"""Return bytes of camera image."""
return asyncio.run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop
).result()
- async def async_camera_image(self, width: int | None = None, height: int | None = None) -> bytes | None:
+ async def async_camera_image(
+ self, width: int | None = None, height: int | None = None
+ ) -> bytes | None:
"""Return a still image response from the camera."""
try:
url = self._still_image_url.async_render()
except TemplateError as err:
- _LOGGER.error(f"Error parsing template {self._still_image_url}, Error: {err}")
+ _LOGGER.error(
+ f"Error parsing template {self._still_image_url}, Error: {err}"
+ )
return self._last_image
try:
@@ -137,7 +157,9 @@ async def async_camera_image(self, width: int | None = None, height: int | None
return self._last_image
except aiohttp.ClientError as err:
- _LOGGER.error(f"Error getting new camera image from {self.name}, Error: {err}")
+ _LOGGER.error(
+ f"Error getting new camera image from {self.name}, Error: {err}"
+ )
return self._last_image
self._last_url = url
diff --git a/custom_components/edgeos/core/components/device_tracker.py b/custom_components/edgeos/core/components/device_tracker.py
index 3977909..9216d81 100644
--- a/custom_components/edgeos/core/components/device_tracker.py
+++ b/custom_components/edgeos/core/components/device_tracker.py
@@ -10,7 +10,7 @@
from homeassistant.components.device_tracker.const import ATTR_IP, ATTR_MAC
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_DEVICE_TRACKER
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
diff --git a/custom_components/edgeos/core/components/light.py b/custom_components/edgeos/core/components/light.py
index d00eecc..769269c 100644
--- a/custom_components/edgeos/core/components/light.py
+++ b/custom_components/edgeos/core/components/light.py
@@ -10,7 +10,7 @@
from homeassistant.components.light import ColorMode, LightEntity
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_LIGHT
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
diff --git a/custom_components/edgeos/core/components/select.py b/custom_components/edgeos/core/components/select.py
index fffb7d3..38f42d4 100644
--- a/custom_components/edgeos/core/components/select.py
+++ b/custom_components/edgeos/core/components/select.py
@@ -10,7 +10,7 @@
from homeassistant.components.select import SelectEntity
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import ATTR_OPTIONS, DOMAIN_SELECT
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
@@ -18,7 +18,8 @@
class CoreSelect(SelectEntity, BaseEntity, ABC):
- """ Core Select """
+ """Core Select"""
+
def initialize(
self,
hass: HomeAssistant,
@@ -35,7 +36,9 @@ def initialize(
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to initialize CoreSelect instance, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to initialize CoreSelect instance, Error: {ex}, Line: {line_number}"
+ )
@property
def current_option(self) -> str:
diff --git a/custom_components/edgeos/core/components/sensor.py b/custom_components/edgeos/core/components/sensor.py
index e520702..cc7dde1 100644
--- a/custom_components/edgeos/core/components/sensor.py
+++ b/custom_components/edgeos/core/components/sensor.py
@@ -1,7 +1,7 @@
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_SENSOR
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
diff --git a/custom_components/edgeos/core/components/switch.py b/custom_components/edgeos/core/components/switch.py
index dd6d4d7..21b57e9 100644
--- a/custom_components/edgeos/core/components/switch.py
+++ b/custom_components/edgeos/core/components/switch.py
@@ -8,7 +8,7 @@
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_SWITCH
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
diff --git a/custom_components/edgeos/core/components/vacuum.py b/custom_components/edgeos/core/components/vacuum.py
index 5080031..262541e 100644
--- a/custom_components/edgeos/core/components/vacuum.py
+++ b/custom_components/edgeos/core/components/vacuum.py
@@ -8,7 +8,7 @@
from homeassistant.components.vacuum import StateVacuumEntity
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import ATTR_FANS_SPEED_LIST, ATTR_FEATURES, DOMAIN_VACUUM
from ..models.base_entity import BaseEntity
from ..models.entity_data import EntityData
@@ -28,16 +28,22 @@ def initialize(
try:
if hasattr(self.entity_description, ATTR_FEATURES):
- self._attr_supported_features = getattr(self.entity_description, ATTR_FEATURES)
+ self._attr_supported_features = getattr(
+ self.entity_description, ATTR_FEATURES
+ )
if hasattr(self.entity_description, ATTR_FANS_SPEED_LIST):
- self._attr_fan_speed_list = getattr(self.entity_description, ATTR_FANS_SPEED_LIST)
+ self._attr_fan_speed_list = getattr(
+ self.entity_description, ATTR_FANS_SPEED_LIST
+ )
except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to initialize CoreSelect instance, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to initialize CoreSelect instance, Error: {ex}, Line: {line_number}"
+ )
@property
def state(self) -> str | None:
@@ -75,10 +81,10 @@ async def async_toggle(self, **kwargs: Any) -> None:
await self.ha.async_core_entity_toggle(self.entity)
async def async_send_command(
- self,
- command: str,
- params: dict[str, Any] | list[Any] | None = None,
- **kwargs: Any,
+ self,
+ command: str,
+ params: dict[str, Any] | list[Any] | None = None,
+ **kwargs: Any,
) -> None:
"""Send a command to a vacuum cleaner."""
await self.ha.async_core_entity_send_command(self.entity, command, params)
diff --git a/custom_components/edgeos/core/helpers/__init__.py b/custom_components/edgeos/core/helpers/__init__.py
index f22bae1..13e4d8c 100644
--- a/custom_components/edgeos/core/helpers/__init__.py
+++ b/custom_components/edgeos/core/helpers/__init__.py
@@ -1,10 +1,10 @@
from homeassistant.core import HomeAssistant
-from ...core.helpers.const import *
+from ...core.helpers.const import DATA
def get_ha(hass: HomeAssistant, entry_id):
- ha_data = hass.data.get(DATA, dict())
+ ha_data = hass.data.get(DATA, {})
ha = ha_data.get(entry_id)
return ha
diff --git a/custom_components/edgeos/core/helpers/const.py b/custom_components/edgeos/core/helpers/const.py
index 1a8a203..a0b4616 100644
--- a/custom_components/edgeos/core/helpers/const.py
+++ b/custom_components/edgeos/core/helpers/const.py
@@ -2,14 +2,12 @@
from homeassistant.components.camera import DOMAIN as DOMAIN_CAMERA
from homeassistant.components.device_tracker import DOMAIN as DOMAIN_DEVICE_TRACKER
from homeassistant.components.light import DOMAIN as DOMAIN_LIGHT
-from homeassistant.components.media_source import DOMAIN as DOMAIN_MEDIA_SOURCE
from homeassistant.components.select import DOMAIN as DOMAIN_SELECT
from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR
-from homeassistant.components.stream import DOMAIN as DOMAIN_STREAM
from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH
from homeassistant.components.vacuum import DOMAIN as DOMAIN_VACUUM
-from ...configuration.helpers.const import *
+from ...configuration.helpers.const import DOMAIN
SUPPORTED_PLATFORMS = [
DOMAIN_BINARY_SENSOR,
@@ -19,10 +17,12 @@
DOMAIN_VACUUM,
DOMAIN_SENSOR,
DOMAIN_LIGHT,
- DOMAIN_DEVICE_TRACKER
+ DOMAIN_DEVICE_TRACKER,
]
-PLATFORMS = {domain: f"{DOMAIN}_{domain}_UPDATE_SIGNAL" for domain in SUPPORTED_PLATFORMS}
+PLATFORMS = {
+ domain: f"{DOMAIN}_{domain}_UPDATE_SIGNAL" for domain in SUPPORTED_PLATFORMS
+}
ENTITY_STATE = "state"
ENTITY_ATTRIBUTES = "attributes"
diff --git a/custom_components/edgeos/core/helpers/setup_base_entry.py b/custom_components/edgeos/core/helpers/setup_base_entry.py
index e980ca6..3c4f073 100644
--- a/custom_components/edgeos/core/helpers/setup_base_entry.py
+++ b/custom_components/edgeos/core/helpers/setup_base_entry.py
@@ -7,7 +7,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
-from ..helpers.const import *
+from ..helpers.const import DATA
from ..models.domain_data import DomainData
from ..models.entity_data import EntityData
@@ -25,7 +25,7 @@ async def async_setup_base_entry(
_LOGGER.debug(f"Starting async_setup_entry {domain}")
try:
- ha_data = hass.data.get(DATA, dict())
+ ha_data = hass.data.get(DATA, {})
ha = ha_data.get(entry.entry_id)
diff --git a/custom_components/edgeos/core/managers/entity_manager.py b/custom_components/edgeos/core/managers/entity_manager.py
index 295090f..e4b1886 100644
--- a/custom_components/edgeos/core/managers/entity_manager.py
+++ b/custom_components/edgeos/core/managers/entity_manager.py
@@ -9,7 +9,7 @@
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_registry import EntityRegistry, RegistryEntryDisabler
-from ..helpers.const import *
+from ..helpers.const import DOMAIN
from ..helpers.enums import EntityStatus
from ..models.domain_data import DomainData
from ..models.entity_data import EntityData
@@ -18,7 +18,7 @@
class EntityManager:
- """ Entity Manager is agnostic to component - PLEASE DON'T CHANGE """
+ """Entity Manager is agnostic to component - PLEASE DON'T CHANGE"""
hass: HomeAssistant
domain_component_manager: dict[str, DomainData]
@@ -56,8 +56,9 @@ async def _handle_disabled_entity(self, entity_id, entity: EntityData):
if entity.disabled:
_LOGGER.info(f"Disabling entity, Data: {entity}")
- self.entity_registry.async_update_entity(entity_id,
- disabled_by=RegistryEntryDisabler.INTEGRATION)
+ self.entity_registry.async_update_entity(
+ entity_id, disabled_by=RegistryEntryDisabler.INTEGRATION
+ )
else:
entity.disabled = entity_item.disabled
@@ -169,19 +170,19 @@ async def _async_update(self):
line_number = tb.tb_lineno
_LOGGER.error(
- f"Failed to update, "
- f"Error: {str(ex)}, "
- f"Line: {line_number}"
+ f"Failed to update, " f"Error: {str(ex)}, " f"Line: {line_number}"
)
- def _compare_data(self,
- entity_name: str,
- entity: EntityData,
- state: str | int | float | bool,
- attributes: dict,
- device_name: str,
- entity_description: EntityDescription | None = None,
- details: dict | None = None):
+ def _compare_data(
+ self,
+ entity_name: str,
+ entity: EntityData,
+ state: str | int | float | bool,
+ attributes: dict,
+ device_name: str,
+ entity_description: EntityDescription | None = None,
+ details: dict | None = None,
+ ):
msgs = []
if str(entity.state) != str(state):
@@ -196,8 +197,13 @@ def _compare_data(self,
if entity.device_name != device_name:
msgs.append(f"Device name {entity.device_name} -> {device_name}")
- if entity_description is not None and entity.entity_description != entity_description:
- msgs.append(f"Description {str(entity.entity_description)} -> {str(entity_description)}")
+ if (
+ entity_description is not None
+ and entity.entity_description != entity_description
+ ):
+ msgs.append(
+ f"Description {str(entity.entity_description)} -> {str(entity_description)}"
+ )
if details is not None and entity.details != details:
from_details = self._get_attributes_json(entity.details)
@@ -230,17 +236,17 @@ def get(self, unique_id: str) -> EntityData | None:
return entity
- def set_entity(self,
- domain: str,
- entry_id: str,
- state: str | int | float | bool | datetime,
- attributes: dict,
- device_name: str,
- entity_description: EntityDescription | None,
- details: dict | None = None,
- destructors: list[bool] = None
- ):
-
+ def set_entity(
+ self,
+ domain: str,
+ entry_id: str,
+ state: str | int | float | bool | datetime,
+ attributes: dict,
+ device_name: str,
+ entity_description: EntityDescription | None,
+ details: dict | None = None,
+ destructors: list[bool] = None,
+ ):
try:
entity = self.entities.get(entity_description.key)
entity_name = entity_description.name
@@ -260,17 +266,21 @@ def set_entity(self,
entity.status = EntityStatus.CREATED
entity.domain = domain
- self._compare_data(entity_name, entity, state, attributes, device_name)
+ self._compare_data(
+ entity_name, entity, state, attributes, device_name
+ )
else:
original_status = entity.status
- was_modified = self._compare_data(entity_name,
- entity,
- state,
- attributes,
- device_name,
- entity_description,
- details)
+ was_modified = self._compare_data(
+ entity_name,
+ entity,
+ state,
+ attributes,
+ device_name,
+ entity_description,
+ details,
+ )
if was_modified:
entity.status = EntityStatus.UPDATED
diff --git a/custom_components/edgeos/core/managers/home_assistant.py b/custom_components/edgeos/core/managers/home_assistant.py
index 4eb79e5..95555c4 100644
--- a/custom_components/edgeos/core/managers/home_assistant.py
+++ b/custom_components/edgeos/core/managers/home_assistant.py
@@ -16,7 +16,24 @@
from homeassistant.helpers.entity_registry import EntityRegistry, async_get
from homeassistant.helpers.event import async_track_time_interval
-from ..helpers.const import *
+from ..helpers.const import (
+ ACTION_CORE_ENTITY_DISABLE_MOTION_DETECTION,
+ ACTION_CORE_ENTITY_ENABLE_MOTION_DETECTION,
+ ACTION_CORE_ENTITY_LOCATE,
+ ACTION_CORE_ENTITY_PAUSE,
+ ACTION_CORE_ENTITY_RETURN_TO_BASE,
+ ACTION_CORE_ENTITY_SELECT_OPTION,
+ ACTION_CORE_ENTITY_SEND_COMMAND,
+ ACTION_CORE_ENTITY_SET_FAN_SPEED,
+ ACTION_CORE_ENTITY_START,
+ ACTION_CORE_ENTITY_STOP,
+ ACTION_CORE_ENTITY_TOGGLE,
+ ACTION_CORE_ENTITY_TURN_OFF,
+ ACTION_CORE_ENTITY_TURN_ON,
+ DOMAIN,
+ PLATFORMS,
+ SUPPORTED_PLATFORMS,
+)
from ..managers.device_manager import DeviceManager
from ..managers.entity_manager import EntityManager
from ..managers.storage_manager import StorageManager
@@ -26,12 +43,12 @@
class HomeAssistantManager:
- def __init__(self,
- hass: HomeAssistant,
- scan_interval: datetime.timedelta,
- heartbeat_interval: datetime.timedelta | None = None
- ):
-
+ def __init__(
+ self,
+ hass: HomeAssistant,
+ scan_interval: datetime.timedelta,
+ heartbeat_interval: datetime.timedelta | None = None,
+ ):
self._hass = hass
self._is_initialized = False
@@ -61,7 +78,9 @@ def _send_heartbeat(internal_now):
self._send_heartbeat = _send_heartbeat
- self._domains = {domain: self.is_domain_supported(domain) for domain in SUPPORTED_PLATFORMS}
+ self._domains = {
+ domain: self.is_domain_supported(domain) for domain in SUPPORTED_PLATFORMS
+ }
@property
def entity_manager(self) -> EntityManager:
@@ -90,45 +109,35 @@ def entry_id(self) -> str:
def entry_title(self) -> str:
return self._entry.title
- def update_intervals(self,
- entities_interval: datetime.timedelta,
- data_interval: datetime.timedelta
- ):
-
+ def update_intervals(
+ self, entities_interval: datetime.timedelta, data_interval: datetime.timedelta
+ ):
self._update_entities_interval = entities_interval
self._update_data_providers_interval = data_interval
async def async_component_initialize(self, entry: ConfigEntry):
- """ Component initialization """
- pass
+ """Component initialization"""
async def async_send_heartbeat(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
def register_services(self, entry: ConfigEntry | None = None):
- """ Must be implemented to be able to expose services """
- pass
+ """Must be implemented to be able to expose services"""
async def async_initialize_data_providers(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
async def async_stop_data_providers(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
async def async_update_data_providers(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
def load_entities(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
def load_devices(self):
- """ Must be implemented to be able to send heartbeat to API """
- pass
+ """Must be implemented to be able to send heartbeat to API"""
async def async_init(self, entry: ConfigEntry):
try:
@@ -185,7 +194,9 @@ async def async_update_entry(self, entry: ConfigEntry | None = None):
entry = self._entry
track_time_update_data_providers = async_track_time_interval(
- self._hass, self._update_data_providers, self._update_data_providers_interval
+ self._hass,
+ self._update_data_providers,
+ self._update_data_providers_interval,
)
self._async_track_time_handlers.append(track_time_update_data_providers)
@@ -208,7 +219,7 @@ async def async_update_entry(self, entry: ConfigEntry | None = None):
await self.async_initialize_data_providers()
async def async_unload(self):
- _LOGGER.info(f"HA was stopped")
+ _LOGGER.info("HA was stopped")
for handler in self._async_track_time_handlers:
if handler is not None:
@@ -254,7 +265,9 @@ def _update_entities(self, now):
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to update devices and entities, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to update devices and entities, Error: {ex}, Line: {line_number}"
+ )
self.entity_manager.update()
@@ -287,96 +300,104 @@ def get_core_entity_fan_speed(self, entity: EntityData) -> str | None:
pass
async def async_core_entity_return_to_base(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_RETURN_TO_BASE. """
+ """Handles ACTION_CORE_ENTITY_RETURN_TO_BASE."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_RETURN_TO_BASE)
if action is not None:
await action(entity)
- async def async_core_entity_set_fan_speed(self, entity: EntityData, fan_speed: str) -> None:
- """ Handles ACTION_CORE_ENTITY_SET_FAN_SPEED. """
+ async def async_core_entity_set_fan_speed(
+ self, entity: EntityData, fan_speed: str
+ ) -> None:
+ """Handles ACTION_CORE_ENTITY_SET_FAN_SPEED."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_SET_FAN_SPEED)
if action is not None:
await action(entity, fan_speed)
async def async_core_entity_start(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_START. """
+ """Handles ACTION_CORE_ENTITY_START."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_START)
if action is not None:
await action(entity)
async def async_core_entity_stop(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_STOP. """
+ """Handles ACTION_CORE_ENTITY_STOP."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_STOP)
if action is not None:
await action(entity)
async def async_core_entity_pause(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_PAUSE. """
+ """Handles ACTION_CORE_ENTITY_PAUSE."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_PAUSE)
if action is not None:
await action(entity)
async def async_core_entity_turn_on(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_TURN_ON. """
+ """Handles ACTION_CORE_ENTITY_TURN_ON."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_TURN_ON)
if action is not None:
await action(entity)
async def async_core_entity_turn_off(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_TURN_OFF. """
+ """Handles ACTION_CORE_ENTITY_TURN_OFF."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_TURN_OFF)
if action is not None:
await action(entity)
async def async_core_entity_send_command(
- self,
- entity: EntityData,
- command: str,
- params: dict[str, Any] | list[Any] | None = None
+ self,
+ entity: EntityData,
+ command: str,
+ params: dict[str, Any] | list[Any] | None = None,
) -> None:
- """ Handles ACTION_CORE_ENTITY_SEND_COMMAND. """
+ """Handles ACTION_CORE_ENTITY_SEND_COMMAND."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_SEND_COMMAND)
if action is not None:
await action(entity, command, params)
async def async_core_entity_locate(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_LOCATE. """
+ """Handles ACTION_CORE_ENTITY_LOCATE."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_LOCATE)
if action is not None:
await action(entity)
- async def async_core_entity_select_option(self, entity: EntityData, option: str) -> None:
- """ Handles ACTION_CORE_ENTITY_SELECT_OPTION. """
+ async def async_core_entity_select_option(
+ self, entity: EntityData, option: str
+ ) -> None:
+ """Handles ACTION_CORE_ENTITY_SELECT_OPTION."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_SELECT_OPTION)
if action is not None:
await action(entity, option)
async def async_core_entity_toggle(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_TOGGLE. """
+ """Handles ACTION_CORE_ENTITY_TOGGLE."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_TOGGLE)
if action is not None:
await action(entity)
- async def async_core_entity_enable_motion_detection(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_ENABLE_MOTION_DETECTION. """
+ async def async_core_entity_enable_motion_detection(
+ self, entity: EntityData
+ ) -> None:
+ """Handles ACTION_CORE_ENTITY_ENABLE_MOTION_DETECTION."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_ENABLE_MOTION_DETECTION)
if action is not None:
await action(entity)
- async def async_core_entity_disable_motion_detection(self, entity: EntityData) -> None:
- """ Handles ACTION_CORE_ENTITY_DISABLE_MOTION_DETECTION. """
+ async def async_core_entity_disable_motion_detection(
+ self, entity: EntityData
+ ) -> None:
+ """Handles ACTION_CORE_ENTITY_DISABLE_MOTION_DETECTION."""
action = self.get_action(entity.id, ACTION_CORE_ENTITY_DISABLE_MOTION_DETECTION)
if action is not None:
@@ -395,7 +416,8 @@ def is_domain_supported(domain) -> bool:
try:
__import__(f"custom_components.{DOMAIN}.{domain}")
- except ModuleNotFoundError as mnfe:
+
+ except ModuleNotFoundError:
is_supported = False
return is_supported
diff --git a/custom_components/edgeos/core/managers/password_manager.py b/custom_components/edgeos/core/managers/password_manager.py
index bb3c4de..3466679 100644
--- a/custom_components/edgeos/core/managers/password_manager.py
+++ b/custom_components/edgeos/core/managers/password_manager.py
@@ -8,7 +8,7 @@
from homeassistant.core import HomeAssistant
-from ..helpers.const import *
+from ..helpers.const import DOMAIN_KEY_FILE
from ..managers.storage_manager import StorageManager
from ..models.storage_data import StorageData
diff --git a/custom_components/edgeos/core/managers/storage_manager.py b/custom_components/edgeos/core/managers/storage_manager.py
index 7de71c2..d0ac584 100644
--- a/custom_components/edgeos/core/managers/storage_manager.py
+++ b/custom_components/edgeos/core/managers/storage_manager.py
@@ -4,7 +4,7 @@
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.storage import Store
-from ..helpers.const import *
+from ..helpers.const import DOMAIN, STORAGE_VERSION
from ..models.storage_data import StorageData
_LOGGER = logging.getLogger(__name__)
diff --git a/custom_components/edgeos/core/models/base_entity.py b/custom_components/edgeos/core/models/base_entity.py
index 8c2f438..bc7b12e 100644
--- a/custom_components/edgeos/core/models/base_entity.py
+++ b/custom_components/edgeos/core/models/base_entity.py
@@ -7,7 +7,7 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
-from ..helpers.const import *
+from ..helpers.const import DATA, PLATFORMS
from ..managers.device_manager import DeviceManager
from ..managers.entity_manager import EntityManager
from ..models.entity_data import EntityData
@@ -39,7 +39,7 @@ def initialize(
self.remove_dispatcher = None
self.current_domain = current_domain
- ha_data = hass.data.get(DATA, dict())
+ ha_data = hass.data.get(DATA, {})
self.ha = ha_data.get(entity.entry_id)
@@ -55,7 +55,9 @@ def initialize(
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno
- _LOGGER.error(f"Failed to initialize BaseEntity, Error: {ex}, Line: {line_number}")
+ _LOGGER.error(
+ f"Failed to initialize BaseEntity, Error: {ex}, Line: {line_number}"
+ )
@property
def entry_id(self) -> str | None:
diff --git a/custom_components/edgeos/core/models/domain_data.py b/custom_components/edgeos/core/models/domain_data.py
index 42c9e78..eec7444 100644
--- a/custom_components/edgeos/core/models/domain_data.py
+++ b/custom_components/edgeos/core/models/domain_data.py
@@ -8,7 +8,6 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
-from ..helpers.const import *
from ..models.entity_data import EntityData
_LOGGER = logging.getLogger(__name__)
@@ -20,10 +19,10 @@ class DomainData:
initializer: Callable[[HomeAssistant, EntityData], Any]
def __init__(
- self,
- name,
- async_add_devices: AddEntitiesCallback,
- initializer: Callable[[HomeAssistant, EntityData], Any]
+ self,
+ name,
+ async_add_devices: AddEntitiesCallback,
+ initializer: Callable[[HomeAssistant, EntityData], Any],
):
self.name = name
self.async_add_devices = async_add_devices
@@ -32,9 +31,7 @@ def __init__(
_LOGGER.info(f"Creating domain data for {name}")
def __repr__(self):
- obj = {
- CONF_NAME: self.name
- }
+ obj = {CONF_NAME: self.name}
to_string = f"{obj}"
diff --git a/custom_components/edgeos/core/models/entity_data.py b/custom_components/edgeos/core/models/entity_data.py
index c785ee4..15f91df 100644
--- a/custom_components/edgeos/core/models/entity_data.py
+++ b/custom_components/edgeos/core/models/entity_data.py
@@ -5,7 +5,17 @@
from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import slugify
-from ..helpers.const import *
+from ..helpers.const import (
+ ENTITY_ATTRIBUTES,
+ ENTITY_CONFIG_ENTRY_ID,
+ ENTITY_DETAILS,
+ ENTITY_DEVICE_NAME,
+ ENTITY_DISABLED,
+ ENTITY_DOMAIN,
+ ENTITY_STATE,
+ ENTITY_STATUS,
+ ENTITY_UNIQUE_ID,
+)
from ..helpers.enums import EntityStatus
@@ -49,7 +59,7 @@ def __repr__(self):
ENTITY_STATUS: self.status,
ENTITY_DISABLED: self.disabled,
ENTITY_DOMAIN: self.domain,
- ENTITY_CONFIG_ENTRY_ID: self.entry_id
+ ENTITY_CONFIG_ENTRY_ID: self.entry_id,
}
to_string = f"{obj}"
diff --git a/custom_components/edgeos/core/models/storage_data.py b/custom_components/edgeos/core/models/storage_data.py
index 4b5e2d5..c97c15b 100644
--- a/custom_components/edgeos/core/models/storage_data.py
+++ b/custom_components/edgeos/core/models/storage_data.py
@@ -17,9 +17,7 @@ def from_dict(obj: dict):
return data
def to_dict(self):
- obj = {
- "key": self.key
- }
+ obj = {"key": self.key}
return obj
diff --git a/custom_components/edgeos/device_tracker.py b/custom_components/edgeos/device_tracker.py
index 37ebf3f..f2e2408 100644
--- a/custom_components/edgeos/device_tracker.py
+++ b/custom_components/edgeos/device_tracker.py
@@ -15,7 +15,11 @@
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the entity."""
await async_setup_base_entry(
- hass, config_entry, async_add_devices, CoreScanner.get_domain(), CoreScanner.get_component
+ hass,
+ config_entry,
+ async_add_devices,
+ CoreScanner.get_domain(),
+ CoreScanner.get_component,
)
diff --git a/custom_components/edgeos/diagnostics.py b/custom_components/edgeos/diagnostics.py
index 97c4f2f..96c6cde 100644
--- a/custom_components/edgeos/diagnostics.py
+++ b/custom_components/edgeos/diagnostics.py
@@ -10,13 +10,13 @@
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntry
-from . import (
+from .component.helpers import get_ha
+from .component.helpers.const import (
API_DATA_INTERFACES,
API_DATA_SYSTEM,
DEVICE_LIST,
MESSAGES_COUNTER_SECTION,
)
-from .component.helpers import get_ha
from .component.managers.home_assistant import EdgeOSHomeAssistantManager
from .configuration.helpers.const import DOMAIN
@@ -85,9 +85,13 @@ def _async_get_diagnostics(
network_device = device_list.get(network_device_id)
if manager.get_device_name(network_device) == device_name:
- _LOGGER.debug(f"Getting diagnostic information for device #{network_device.unique_id}")
+ _LOGGER.debug(
+ f"Getting diagnostic information for device #{network_device.unique_id}"
+ )
- data |= _async_device_as_dict(hass, network_device.to_dict(), network_device.unique_id)
+ data |= _async_device_as_dict(
+ hass, network_device.to_dict(), network_device.unique_id
+ )
break
@@ -96,9 +100,13 @@ def _async_get_diagnostics(
interface = interfaces.get(unique_id)
if manager.get_interface_name(interface) == device_name:
- _LOGGER.debug(f"Getting diagnostic information for interface #{interface.unique_id}")
+ _LOGGER.debug(
+ f"Getting diagnostic information for interface #{interface.unique_id}"
+ )
- data |= _async_device_as_dict(hass, interface.to_dict(), interface.unique_id)
+ data |= _async_device_as_dict(
+ hass, interface.to_dict(), interface.unique_id
+ )
break
else:
@@ -106,14 +114,22 @@ def _async_get_diagnostics(
data.update(
devices=[
- _async_device_as_dict(hass, device_list[device_id].to_dict(), device_list[device_id].unique_id)
+ _async_device_as_dict(
+ hass,
+ device_list[device_id].to_dict(),
+ device_list[device_id].unique_id,
+ )
for device_id in device_list
],
interfaces=[
- _async_device_as_dict(hass, interfaces[interface_id].to_dict(), interfaces[interface_id].unique_id)
+ _async_device_as_dict(
+ hass,
+ interfaces[interface_id].to_dict(),
+ interfaces[interface_id].unique_id,
+ )
for interface_id in interfaces
],
- system=_async_device_as_dict(hass, system.to_dict(), manager.system_name)
+ system=_async_device_as_dict(hass, system.to_dict(), manager.system_name),
)
return data
@@ -121,10 +137,8 @@ def _async_get_diagnostics(
@callback
def _async_device_as_dict(
- hass: HomeAssistant,
- data: dict,
- unique_id: str) -> dict[str, Any]:
-
+ hass: HomeAssistant, data: dict, unique_id: str
+) -> dict[str, Any]:
"""Represent a Shinobi monitor as a dictionary."""
device_registry = dr.async_get(hass)
entity_registry = er.async_get(hass)
diff --git a/custom_components/edgeos/manifest.json b/custom_components/edgeos/manifest.json
index b86b366..c8c0b47 100644
--- a/custom_components/edgeos/manifest.json
+++ b/custom_components/edgeos/manifest.json
@@ -8,5 +8,5 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/elad-bar/ha-edgeos/issues",
"requirements": ["aiohttp"],
- "version": "2.0.25"
+ "version": "2.0.26"
}
diff --git a/custom_components/edgeos/select.py b/custom_components/edgeos/select.py
index a5450ff..facfc33 100644
--- a/custom_components/edgeos/select.py
+++ b/custom_components/edgeos/select.py
@@ -14,7 +14,11 @@
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the component."""
await async_setup_base_entry(
- hass, config_entry, async_add_devices, CoreSelect.get_domain(), CoreSelect.get_component
+ hass,
+ config_entry,
+ async_add_devices,
+ CoreSelect.get_domain(),
+ CoreSelect.get_component,
)
diff --git a/custom_components/edgeos/sensor.py b/custom_components/edgeos/sensor.py
index d55cf0f..f57921a 100644
--- a/custom_components/edgeos/sensor.py
+++ b/custom_components/edgeos/sensor.py
@@ -14,7 +14,11 @@
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the Sensor."""
await async_setup_base_entry(
- hass, config_entry, async_add_devices, CoreSensor.get_domain(), CoreSensor.get_component
+ hass,
+ config_entry,
+ async_add_devices,
+ CoreSensor.get_domain(),
+ CoreSensor.get_component,
)
diff --git a/custom_components/edgeos/services.yaml b/custom_components/edgeos/services.yaml
index a0aa16e..7c8ae65 100644
--- a/custom_components/edgeos/services.yaml
+++ b/custom_components/edgeos/services.yaml
@@ -1,4 +1,3 @@
-
update_configuration:
name: Update configuration
description: Update configuration of EdgeOS integration
diff --git a/custom_components/edgeos/switch.py b/custom_components/edgeos/switch.py
index 6dd79d7..5ef5c99 100644
--- a/custom_components/edgeos/switch.py
+++ b/custom_components/edgeos/switch.py
@@ -14,7 +14,11 @@
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the Switch."""
await async_setup_base_entry(
- hass, config_entry, async_add_devices, CoreSwitch.get_domain(), CoreSwitch.get_component
+ hass,
+ config_entry,
+ async_add_devices,
+ CoreSwitch.get_domain(),
+ CoreSwitch.get_component,
)
diff --git a/info.md b/info.md
index df53797..b94c198 100644
--- a/info.md
+++ b/info.md
@@ -16,8 +16,9 @@ Provides an integration between EdgeOS (Ubiquiti) routers to Home Assistant.
- To enable / disable interfaces an `admin` role is a required
#### Installations via HACS
+
- In HACS, look for "Ubiquiti EdgeOS Routers" and install and restart
-- In Settings --> Devices & Services - (Lower Right) "Add Integration"
+- In Settings --> Devices & Services - (Lower Right) "Add Integration"
#### Setup
@@ -25,7 +26,7 @@ To add integration use Configuration -> Integrations -> Add `EdgeOS`
Integration supports **multiple** EdgeOS devices
| Fields name | Type | Required | Default | Description |
-|-------------|---------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------|
+| ----------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Host | Textbox | + | - | Hostname or IP address to access EdgeOS device, can hold also port (HOST:PORT), default port is 443 |
| Username | Textbox | + | - | Username of user with `Operator` level access or higher, better to create a dedicated user for that integration for faster issues identification |
| Password | Textbox | + | - | |
@@ -33,7 +34,7 @@ Integration supports **multiple** EdgeOS devices
###### EdgeOS Device validation errors
| Errors |
-|------------------------------------------------------------------------------------|
+| ---------------------------------------------------------------------------------- |
| Cannot reach device (404) |
| Invalid credentials (403) |
| General authentication error (when failed to get valid response from device) |
@@ -58,7 +59,7 @@ Please remove the integration and re-add it to make it work again.
_Configuration -> Integrations -> {Integration} -> Options_
| Fields name | Type | Required | Default | Description |
-|-------------------|-----------|----------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|
+| ----------------- | --------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Host | Textbox | + | - | Hostname or IP address to access EdgeOS device, can hold also port (HOST:PORT), default port is 443 |
| Username | Textbox | + | - | Username of user with `Operator` level access or higher, better to create a dedicated user for that integration for faster issues identification |
| Password | Textbox | + | - | |
@@ -78,8 +79,9 @@ logger:
## Components
### System
+
| Entity Name | Type | Description | Additional information |
-|-------------------------------------|---------------|---------------------------------------------------------------------------|-----------------------------------------------|
+| ----------------------------------- | ------------- | ------------------------------------------------------------------------- | --------------------------------------------- |
| {Router Name} Unit | Select | Sets whether to monitor device and create all the components below or not | |
| {Router Name} CPU | Sensor | Represents CPU usage | |
| {Router Name} RAM | Sensor | Represents RAM usage | |
@@ -88,11 +90,12 @@ logger:
| {Router Name} Firmware Updates | Binary Sensor | New firmware available indication | Attributes holds the url and new release name |
| {Router Name} Log incoming messages | Switch | Sets whether to log WebSocket incoming messages for debugging | |
-*Changing the unit will reload the integration*
+_Changing the unit will reload the integration_
### Per device
+
| Entity Name | Type | Description | Additional information |
-|----------------------------------------------|----------------|---------------------------------------------------------------------------------|-----------------------------|
+| -------------------------------------------- | -------------- | ------------------------------------------------------------------------------- | --------------------------- |
| {Router Name} {Device Name} Monitored | Sensor | Sets whether to monitor device and create all the components below or not | |
| {Router Name} {Device Name} Received Rate | Sensor | Received Rate per second | Statistics: Measurement |
| {Router Name} {Device Name} Received Traffic | Sensor | Received total traffic | Statistics: Total Increment |
@@ -100,10 +103,10 @@ logger:
| {Router Name} {Device Name} Sent Traffic | Sensor | Sent total traffic | Statistics: Total Increment |
| {Router Name} {Device Name} | Device Tracker | Indication whether the device is or was connected over the configured timeframe | |
-
### Per interface
+
| Entity Name | Type | Description | Additional information |
-|---------------------------------------------------------|---------------|------------------------------------------------------------------------------|---------------------------------------------|
+| ------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------- | ------------------------------------------- |
| {Router Name} {Interface Name} Status | Switch | Sets whether to interface is active or not | Available only if user level is `admin` |
| {Router Name} {Interface Name} Status | Binary Sensor | Indicates whether interface is active or not | Available only if user level is not `admin` |
| {Router Name} {Interface Name} Connected | Binary Sensor | Indicates whether interface's port is connected or not | |
@@ -119,13 +122,14 @@ logger:
| {Router Name} {Interface Name} Sent Errors | Sensor | Sent errors | Statistics: Total Increment |
| {Router Name} {Interface Name} Sent Packets | Sensor | Sent packets | Statistics: Total Increment |
-
_Unit of measurement for `Traffic` and `Rate` are according to the unit settings of the integration_
## Services
### Update configuration
+
Allows to set:
+
- Consider away interval - Time to consider a device without activity as AWAY (any value between 10 and 1800 in seconds)
- Log incoming messages - Enable / Disable logging of incoming WebSocket messages for debug
- Store debug data - Enable / Disable store debug data to './storage' directory of HA for API (edgeos.debug.api.json) and WS (edgeos.debug.ws.json) data for faster debugging or just to get more ideas for additional features
@@ -138,7 +142,7 @@ More details available in `Developer tools` -> `Services` -> `edgeos.update_conf
```yaml
service: edgeos.update_configuration
data:
- device_id: {Main device ID}
+ device_id: { Main device ID }
unit: Bytes
log_incoming_messages: true
consider_away_interval: 180
@@ -146,4 +150,4 @@ data:
update_entities_interval: 1
```
-*Changing the unit will reload the integration*
+_Changing the unit will reload the integration_
diff --git a/pyproject.toml b/pyproject.toml
index f478815..23ee312 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,2 +1,154 @@
[tool.black]
-target-version = ["py39"]
+target-version = ["py39", "py310"]
+extend-exclude = "/generated/"
+
+[tool.isort]
+# https://github.com/PyCQA/isort/wiki/isort-Settings
+profile = "black"
+# will group `import x` and `from x import` of the same module.
+force_sort_within_sections = true
+known_first_party = [
+ "homeassistant",
+ "tests",
+]
+forced_separate = [
+ "tests",
+]
+combine_as_imports = true
+
+[tool.pylint.MAIN]
+py-version = "3.9"
+ignore = [
+ "tests",
+]
+# Use a conservative default here; 2 should speed up most setups and not hurt
+# any too bad. Override on command line as appropriate.
+jobs = 2
+init-hook = """\
+ from pathlib import Path; \
+ import sys; \
+ from pylint.config import find_default_config_files; \
+ sys.path.append( \
+ str(Path(next(find_default_config_files())).parent.joinpath('pylint/plugins'))
+ ) \
+ """
+load-plugins = [
+ "pylint.extensions.code_style",
+ "pylint.extensions.typing",
+ "hass_constructor",
+ "hass_enforce_type_hints",
+ "hass_imports",
+ "hass_logger",
+]
+persistent = false
+extension-pkg-allow-list = [
+ "av.audio.stream",
+ "av.stream",
+ "ciso8601",
+ "orjson",
+ "cv2",
+]
+fail-on = [
+ "I",
+]
+
+[tool.pylint.BASIC]
+class-const-naming-style = "any"
+good-names = [
+ "_",
+ "ev",
+ "ex",
+ "fp",
+ "i",
+ "id",
+ "j",
+ "k",
+ "Run",
+ "ip",
+]
+
+[tool.pylint."MESSAGES CONTROL"]
+# Reasons disabled:
+# format - handled by black
+# locally-disabled - it spams too much
+# duplicate-code - unavoidable
+# cyclic-import - doesn't test if both import on load
+# abstract-class-little-used - prevents from setting right foundation
+# unused-argument - generic callbacks and setup methods create a lot of warnings
+# too-many-* - are not enforced for the sake of readability
+# too-few-* - same as too-many-*
+# abstract-method - with intro of async there are always methods missing
+# inconsistent-return-statements - doesn't handle raise
+# too-many-ancestors - it's too strict.
+# wrong-import-order - isort guards this
+# consider-using-f-string - str.format sometimes more readable
+# ---
+# Enable once current issues are fixed:
+# consider-using-namedtuple-or-dataclass (Pylint CodeStyle extension)
+# consider-using-assignment-expr (Pylint CodeStyle extension)
+disable = [
+ "format",
+ "abstract-method",
+ "cyclic-import",
+ "duplicate-code",
+ "inconsistent-return-statements",
+ "locally-disabled",
+ "not-context-manager",
+ "too-few-public-methods",
+ "too-many-ancestors",
+ "too-many-arguments",
+ "too-many-branches",
+ "too-many-instance-attributes",
+ "too-many-lines",
+ "too-many-locals",
+ "too-many-public-methods",
+ "too-many-return-statements",
+ "too-many-statements",
+ "too-many-boolean-expressions",
+ "unused-argument",
+ "wrong-import-order",
+ "consider-using-f-string",
+ "consider-using-namedtuple-or-dataclass",
+ "consider-using-assignment-expr",
+]
+enable = [
+ #"useless-suppression", # temporarily every now and then to clean them up
+ "use-symbolic-message-instead",
+]
+
+[tool.pylint.REPORTS]
+score = false
+
+[tool.pylint.TYPECHECK]
+ignored-classes = [
+ "_CountingAttr", # for attrs
+]
+mixin-class-rgx = ".*[Mm]ix[Ii]n"
+
+[tool.pylint.FORMAT]
+expected-line-ending-format = "LF"
+
+[tool.pylint.EXCEPTIONS]
+overgeneral-exceptions = [
+ "BaseException",
+ "Exception",
+ "HomeAssistantError",
+]
+
+[tool.pylint.TYPING]
+runtime-typing = false
+
+[tool.pylint.CODE_STYLE]
+max-line-length-suggestions = 72
+
+[tool.pytest.ini_options]
+testpaths = [
+ "tests",
+]
+norecursedirs = [
+ ".git",
+ "testing_config",
+]
+log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
+log_date_format = "%Y-%m-%d %H:%M:%S"
+asyncio_mode = "auto"
diff --git a/setup.cfg b/setup.cfg
index 1620535..781d7d9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,40 +1,17 @@
[flake8]
-exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
+exclude = .venv,.git,docs,venv,bin,lib,deps,build
+max-complexity = 25
doctests = True
# To work with Black
-max-line-length = 88
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# W504 line break after binary operator
-# F405
-# F403
ignore =
E501,
W503,
E203,
D202,
- W504,
- F405,
- F403,
-
-[isort]
-# https://github.com/timothycrosley/isort
-# https://github.com/timothycrosley/isort/wiki/isort-Settings
-# splits long import on multiple lines indented by 4 spaces
-multi_line_output = 3
-include_trailing_comma=True
-force_grid_wrap=0
-use_parentheses=True
-line_length=88
-indent = " "
-# by default isort don't check module indexes
-not_skip = __init__.py
-# will group `import x` and `from x import` of the same module.
-force_sort_within_sections = true
-sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-default_section = THIRDPARTY
-known_first_party = homeassistant,tests
-forced_separate = tests
-combine_as_imports = true
+ W504
+noqa-require-code = True