diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 13a2079..8501df5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,9 +3,7 @@ "image": "ghcr.io/ludeeus/devcontainer/integration:stable", "name": "miner integration development", "context": "..", - "appPort": [ - "9123:8123" - ], + "appPort": ["9123:8123"], "postCreateCommand": "container install", "extensions": [ "ms-python.python", @@ -27,4 +25,4 @@ "editor.formatOnType": true, "files.trimTrailingWhitespace": true } -} \ No newline at end of file +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 511f9f0..a945479 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,32 +1,24 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.3.0 + rev: v4.2.0 hooks: - id: check-added-large-files - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: local + - repo: https://github.com/psf/black + rev: 22.3.0 hooks: - id: black - name: black - entry: black - language: system - types: [python] - require_serial: true + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: - id: flake8 - name: flake8 - entry: flake8 - language: system - types: [python] - require_serial: true + - repo: https://github.com/asottile/reorder_python_imports + rev: v3.1.0 + hooks: - id: reorder-python-imports - name: Reorder python imports - entry: reorder-python-imports - language: system - types: [python] - args: [--application-directories=custom_components] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.2.1 + rev: v2.6.2 hooks: - id: prettier diff --git a/README.md b/README.md index 358c6c8..1bc1f7d 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,15 @@ [![hacs][hacsbadge]][hacs] [![Project Maintenance][maintenance-shield]][user_profile] - Controll your Braiins OS+ enabled Bitcoin miner from Home Assistant **This component will set up the following platforms.** -| Platform | Description | -| --------------- | ------------------------------------------------------------------------- | -| `sensor` | Show info from miner API. | -| `number` | Set Power Limit of Miner. | -| `switch` | Switch Miner on and off | +| Platform | Description | +| -------- | ------------------------- | +| `sensor` | Show info from miner API. | +| `number` | Set Power Limit of Miner. | +| `switch` | Switch Miner on and off | ## Installation @@ -29,7 +28,6 @@ Installation and usage: [![Installation and usage](http://img.youtube.com/vi/eL83eYLbgQM/0.jpg)](http://www.youtube.com/watch?v=eL83eYLbgQM) - ## Contributions are welcome! If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md) diff --git a/custom_components/miner/__init__.py b/custom_components/miner/__init__.py index b025767..b32c09a 100644 --- a/custom_components/miner/__init__.py +++ b/custom_components/miner/__init__.py @@ -4,11 +4,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant - from miners.miner_factory import MinerFactory -from .coordinator import MinerCoordinator from .const import DOMAIN +from .coordinator import MinerCoordinator # TODO List the platforms that you want to support. # For your initial PR, limit it to 1 platform. diff --git a/custom_components/miner/config_flow.py b/custom_components/miner/config_flow.py index 8758067..233a3ac 100644 --- a/custom_components/miner/config_flow.py +++ b/custom_components/miner/config_flow.py @@ -1,16 +1,17 @@ """Config flow for Miner.""" -from homeassistant.helpers import config_entry_flow -from homeassistant import config_entries, core, exceptions -from .const import DOMAIN, CONF_IP, CONF_HOSTNAME - -from API import APIError - import ipaddress +import logging import voluptuous as vol - +from API import APIError +from homeassistant import config_entries +from homeassistant import core +from homeassistant import exceptions from miners.miner_factory import MinerFactory -import logging + +from .const import CONF_HOSTNAME +from .const import CONF_IP +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -31,10 +32,9 @@ async def validate_input( miner_ip = ipaddress.ip_address(data.get(CONF_IP)) miner_factory = MinerFactory() - miner_data = {} try: miner = await miner_factory.get_miner(miner_ip) - miner_data = await miner.get_data() + await miner.get_data() except APIError: return {"base": "cannot_connect"} except Exception: # pylint: disable=broad-except diff --git a/custom_components/miner/const.py b/custom_components/miner/const.py index fdd1038..9e00677 100644 --- a/custom_components/miner/const.py +++ b/custom_components/miner/const.py @@ -2,4 +2,4 @@ DOMAIN = "miner" CONF_IP = "ip" -CONF_HOSTNAME = "hostname" \ No newline at end of file +CONF_HOSTNAME = "hostname" diff --git a/custom_components/miner/coordinator.py b/custom_components/miner/coordinator.py index 6a1fbe2..07acc34 100644 --- a/custom_components/miner/coordinator.py +++ b/custom_components/miner/coordinator.py @@ -1,25 +1,21 @@ """IoTaWatt DataUpdateCoordinator.""" from __future__ import annotations -from datetime import datetime, timedelta +import ipaddress import logging +from datetime import timedelta -from miners.miner_factory import MinerFactory -from miners import BaseMiner - +from API import APIError from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant -from homeassistant.helpers import httpx_client from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed - -from .const import CONF_IP, CONF_HOSTNAME - -from API import APIError -import ipaddress +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import UpdateFailed +from miners import BaseMiner +from miners.miner_factory import MinerFactory -from .parse_data import safe_parse_api_data +from .const import CONF_HOSTNAME +from .const import CONF_IP _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/miner/manifest.json b/custom_components/miner/manifest.json index bf5cd1f..40a43eb 100644 --- a/custom_components/miner/manifest.json +++ b/custom_components/miner/manifest.json @@ -3,16 +3,12 @@ "name": "Miner", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/miner", - "requirements": [ - "minerinterface==0.5.2" - ], + "requirements": ["minerinterface==0.5.2"], "ssdp": [], "zeroconf": [], "homekit": {}, "dependencies": [], - "codeowners": [ - "@Schnitzel" - ], + "codeowners": ["@Schnitzel"], "iot_class": "local_polling", "version": "0.1.0" -} \ No newline at end of file +} diff --git a/custom_components/miner/number.py b/custom_components/miner/number.py index cdb503a..791ee0a 100644 --- a/custom_components/miner/number.py +++ b/custom_components/miner/number.py @@ -1,32 +1,16 @@ """Support for IoTaWatt Energy monitor.""" from __future__ import annotations -from collections.abc import Callable -from dataclasses import dataclass import logging -import yaml -from unittest import case - -from homeassistant.components import switch -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.components.number import ( - NumberEntity, -) +import yaml +from homeassistant.components.number import NumberEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity, entity_registry -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.core import callback +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import dt from .const import ( DOMAIN, diff --git a/custom_components/miner/parse_data.py b/custom_components/miner/parse_data.py deleted file mode 100644 index 635818b..0000000 --- a/custom_components/miner/parse_data.py +++ /dev/null @@ -1,66 +0,0 @@ -from API import APIError - - -# noinspection PyPep8 -async def safe_parse_api_data(data: dict or list, *path: str or int, idx: int = 0): - path = [*path] - if len(path) == idx + 1: - if isinstance(path[idx], str): - if isinstance(data, dict): - if path[idx] in data.keys(): - return data[path[idx]] - elif isinstance(path[idx], int): - if isinstance(data, list): - if len(data) > path[idx]: - return data[path[idx]] - else: - if isinstance(path[idx], str): - if isinstance(data, dict): - if path[idx] in data.keys(): - parsed_data = await safe_parse_api_data( - data[path[idx]], idx=idx + 1, *path - ) - # has to be == None, or else it fails on 0.0 hashrates - # noinspection PyPep8 - if parsed_data == None: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return parsed_data - else: - if idx == 0: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return False - else: - if idx == 0: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return False - elif isinstance(path[idx], int): - if isinstance(data, list): - if len(data) > path[idx]: - parsed_data = await safe_parse_api_data( - data[path[idx]], idx=idx + 1, *path - ) - # has to be == None, or else it fails on 0.0 hashrates - # noinspection PyPep8 - if parsed_data == None: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return parsed_data - else: - if idx == 0: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return False - else: - if idx == 0: - raise APIError( - f"Data parsing failed on path index {idx} - \nKey: {path[idx]} \nData: {data}" - ) - return False diff --git a/custom_components/miner/sensor.py b/custom_components/miner/sensor.py index 290f246..ccfc256 100644 --- a/custom_components/miner/sensor.py +++ b/custom_components/miner/sensor.py @@ -1,32 +1,23 @@ """Support for IoTaWatt Energy monitor.""" from __future__ import annotations +import logging from collections.abc import Callable from dataclasses import dataclass -import logging -from unittest import case - -from homeassistant.components import switch -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.components.number import ( - NumberEntity, -) +from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.components.sensor import SensorStateClass from homeassistant.config_entries import ConfigEntry -from homeassistant.const import TEMP_CELSIUS, POWER_WATT -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity, entity_registry -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.const import POWER_WATT +from homeassistant.const import TEMP_CELSIUS +from homeassistant.core import callback +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import dt from .const import ( DOMAIN, diff --git a/custom_components/miner/switch.py b/custom_components/miner/switch.py index 0ef2f1c..8101661 100644 --- a/custom_components/miner/switch.py +++ b/custom_components/miner/switch.py @@ -1,31 +1,18 @@ """Support for IoTaWatt Energy monitor.""" from __future__ import annotations +import logging from collections.abc import Callable from dataclasses import dataclass -import logging -from unittest import case - -from homeassistant.components import switch -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.components.switch import ( - SwitchEntity, -) +from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity, entity_registry -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.core import callback +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import dt from .const import ( DOMAIN, diff --git a/custom_components/miner/translations/en.json b/custom_components/miner/translations/en.json index 7c7b923..36e3991 100644 --- a/custom_components/miner/translations/en.json +++ b/custom_components/miner/translations/en.json @@ -1,23 +1,23 @@ { - "config": { - "abort": { - "no_devices_found": "No devices found on the network", - "single_instance_allowed": "Already configured. Only a single configuration possible." - }, - "step": { - "confirm": { - "description": "Do you want to start set up?" - }, - "user": { - "data": { - "ip": "IP Address" - } - }, - "hostname": { - "data": { - "hostname": "Hostname of Miner to be used in HomeAssistant" - } - } + "config": { + "abort": { + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "confirm": { + "description": "Do you want to start set up?" + }, + "user": { + "data": { + "ip": "IP Address" } + }, + "hostname": { + "data": { + "hostname": "Hostname of Miner to be used in HomeAssistant" + } + } } -} \ No newline at end of file + } +} diff --git a/hacs.json b/hacs.json index e0493a1..61c52d0 100644 --- a/hacs.json +++ b/hacs.json @@ -1,10 +1,7 @@ { "name": "miner", "hacs": "1.6.0", - "domains": [ - "sensor", - "switch" - ], + "domains": ["sensor", "switch"], "iot_class": "local_polling", "homeassistant": "0.118.0" -} \ No newline at end of file +} diff --git a/requirements_dev.txt b/requirements_dev.txt index 9981e44..4a7a0d5 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,2 @@ homeassistant -minerinterface==0.5.3 \ No newline at end of file +minerinterface==0.5.3 diff --git a/tests/test_api.py b/tests/test_api.py index da1c179..a96b0d2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2,10 +2,11 @@ import asyncio import aiohttp +from homeassistant.helpers.aiohttp_client import async_get_clientsession + from custom_components.miner.api import ( MinerApiClient, ) -from homeassistant.helpers.aiohttp_client import async_get_clientsession async def test_api(hass, aioclient_mock, caplog): diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index ac2648c..8dad97e 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -2,6 +2,11 @@ from unittest.mock import patch import pytest +from homeassistant import config_entries +from homeassistant import data_entry_flow +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from .const import MOCK_CONFIG from custom_components.miner.const import ( BINARY_SENSOR, ) @@ -17,11 +22,6 @@ from custom_components.miner.const import ( SWITCH, ) -from homeassistant import config_entries -from homeassistant import data_entry_flow -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from .const import MOCK_CONFIG # This fixture bypasses the actual setup of the integration diff --git a/tests/test_init.py b/tests/test_init.py index a35ae25..f002c60 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,5 +1,9 @@ """Test miner setup process.""" import pytest +from homeassistant.exceptions import ConfigEntryNotReady +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from .const import MOCK_CONFIG from custom_components.miner import ( async_reload_entry, ) @@ -15,10 +19,6 @@ from custom_components.miner.const import ( DOMAIN, ) -from homeassistant.exceptions import ConfigEntryNotReady -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from .const import MOCK_CONFIG # We can pass fixtures as defined in conftest.py to tell pytest to use the fixture @@ -36,16 +36,12 @@ async def test_setup_unload_and_reload_entry(hass, bypass_get_data): # call, no code from custom_components/miner/api.py actually runs. assert await async_setup_entry(hass, config_entry) assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert ( - type(hass.data[DOMAIN][config_entry.entry_id]) == MinerDataUpdateCoordinator - ) + assert type(hass.data[DOMAIN][config_entry.entry_id]) == MinerDataUpdateCoordinator # Reload the entry and assert that the data from above is still there assert await async_reload_entry(hass, config_entry) is None assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert ( - type(hass.data[DOMAIN][config_entry.entry_id]) == MinerDataUpdateCoordinator - ) + assert type(hass.data[DOMAIN][config_entry.entry_id]) == MinerDataUpdateCoordinator # Unload the entry and verify that the data has been removed assert await async_unload_entry(hass, config_entry) diff --git a/tests/test_switch.py b/tests/test_switch.py index 062ca02..bc638ab 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -2,6 +2,12 @@ from unittest.mock import call from unittest.mock import patch +from homeassistant.components.switch import SERVICE_TURN_OFF +from homeassistant.components.switch import SERVICE_TURN_ON +from homeassistant.const import ATTR_ENTITY_ID +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from .const import MOCK_CONFIG from custom_components.miner import ( async_setup_entry, ) @@ -14,12 +20,6 @@ from custom_components.miner.const import ( SWITCH, ) -from homeassistant.components.switch import SERVICE_TURN_OFF -from homeassistant.components.switch import SERVICE_TURN_ON -from homeassistant.const import ATTR_ENTITY_ID -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from .const import MOCK_CONFIG async def test_switch_services(hass): @@ -31,9 +31,7 @@ async def test_switch_services(hass): # Functions/objects can be patched directly in test code as well and can be used to test # additional things, like whether a function was called or what arguments it was called with - with patch( - "custom_components.miner.MinerApiClient.async_set_title" - ) as title_func: + with patch("custom_components.miner.MinerApiClient.async_set_title") as title_func: await hass.services.async_call( SWITCH, SERVICE_TURN_OFF,