From 90a0981e0edb963593d448c04edc39a7c75152f0 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 7 Feb 2024 20:22:18 -0700 Subject: [PATCH 1/3] Update pyasic, and attempt to add discovery. --- custom_components/miner/config_flow.py | 29 +++++++++++++++++++------- custom_components/miner/coordinator.py | 19 ++++++++--------- custom_components/miner/manifest.json | 2 +- custom_components/miner/number.py | 15 +------------ requirements.txt | 2 +- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/custom_components/miner/config_flow.py b/custom_components/miner/config_flow.py index 91af602..5ef6fb2 100644 --- a/custom_components/miner/config_flow.py +++ b/custom_components/miner/config_flow.py @@ -4,9 +4,13 @@ import pyasic import voluptuous as vol from homeassistant import config_entries +from homeassistant.components import network +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_entry_flow from homeassistant.helpers.selector import TextSelector from homeassistant.helpers.selector import TextSelectorConfig from homeassistant.helpers.selector import TextSelectorType +from pyasic import MinerNetwork from .const import CONF_IP from .const import CONF_RPC_PASSWORD @@ -19,14 +23,23 @@ _LOGGER = logging.getLogger(__name__) -# async def _async_has_devices(hass: HomeAssistant) -> bool: -# """Return if there are devices that can be discovered.""" -# # TODO Check if there are any devices that can be discovered in the network. -# devices = await hass.async_add_executor_job(my_pypi_dependency.discover) -# return len(devices) > 0 +async def _async_has_devices(hass: HomeAssistant) -> bool: + """Return if there are devices that can be discovered.""" + adapters = await network.async_get_adapters(hass) -# config_entry_flow.register_discovery_flow(DOMAIN, "miner", _async_has_devices) + for adapter in adapters: + for ip_info in adapter["ipv4"]: + local_ip = ip_info["address"] + network_prefix = ip_info["network_prefix"] + miner_net = MinerNetwork.from_subnet(f"{local_ip}/{network_prefix}") + miners = await miner_net.scan() + if len(miners) > 0: + return True + return False + + +config_entry_flow.register_discovery_flow(DOMAIN, "miner", _async_has_devices) async def validate_ip_input( @@ -82,8 +95,8 @@ async def async_step_login(self, user_input=None): schema_data = {} - if self._miner.api is not None: - if self._miner.api.pwd is not None: + if self._miner.rpc is not None: + if self._miner.rpc.pwd is not None: schema_data[ vol.Optional( CONF_RPC_PASSWORD, diff --git a/custom_components/miner/coordinator.py b/custom_components/miner/coordinator.py index a52d142..101bcff 100644 --- a/custom_components/miner/coordinator.py +++ b/custom_components/miner/coordinator.py @@ -6,16 +6,15 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.debounce import Debouncer -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import UpdateFailed -from .const import ( - CONF_IP, - CONF_RPC_PASSWORD, - CONF_SSH_PASSWORD, - CONF_SSH_USERNAME, - CONF_WEB_PASSWORD, - CONF_WEB_USERNAME, -) +from .const import CONF_IP +from .const import CONF_RPC_PASSWORD +from .const import CONF_SSH_PASSWORD +from .const import CONF_SSH_USERNAME +from .const import CONF_WEB_PASSWORD +from .const import CONF_WEB_USERNAME _LOGGER = logging.getLogger(__name__) @@ -58,7 +57,7 @@ async def _async_update_data(self): if self.miner is None: raise UpdateFailed("Miner Offline") - _LOGGER.debug(f"Found miner :{self.miner}") + _LOGGER.debug(f"Found miner: {self.miner}") try: if self.miner.api is not None: diff --git a/custom_components/miner/manifest.json b/custom_components/miner/manifest.json index 77dc0f2..d44b52f 100644 --- a/custom_components/miner/manifest.json +++ b/custom_components/miner/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_polling", "issue_tracker": "https://github.com/Schnitzel/hass-miner/issues", - "requirements": ["pyasic==0.50.6"], + "requirements": ["pyasic==0.51.0"], "ssdp": [], "version": "1.1.2", "zeroconf": [] diff --git a/custom_components/miner/number.py b/custom_components/miner/number.py index ffac0f4..e5222cb 100644 --- a/custom_components/miner/number.py +++ b/custom_components/miner/number.py @@ -36,17 +36,6 @@ async def async_setup_entry( ] ) - # @callback - # def new_data_received(): - # """Check for new sensors.""" - # entities = [ - # _create_entity(key) for key in coordinator.data if key not in created - # ] - # if entities: - # async_add_entities(entities) - - # coordinator.async_add_listener(new_data_received) - class MinerPowerLimitNumber(CoordinatorEntity[MinerCoordinator], NumberEntity): """Defines a Miner Number to set the Power Limit of the Miner.""" @@ -107,9 +96,7 @@ async def async_set_native_value(self, value): ) if not miner.supports_autotuning: - raise TypeError( - f"{self.coordinator.entry.title}: Tuning not supported." - ) + raise TypeError(f"{self.coordinator.entry.title}: Tuning not supported.") result = await miner.set_power_limit(int(value)) diff --git a/requirements.txt b/requirements.txt index a292ef1..16b7ef2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,6 @@ colorlog==6.7.0 homeassistant>=2024.1.0 pip>=21.0,<23.2 ruff==0.0.267 -pyasic==0.50.6 +pyasic==0.51.0 setuptools==69.0.3 pre-commit From 3da91035137df5642d1ed0d85e2f6557f7654a29 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 7 Feb 2024 20:36:11 -0700 Subject: [PATCH 2/3] Update pyasic. --- custom_components/miner/manifest.json | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/miner/manifest.json b/custom_components/miner/manifest.json index d44b52f..e8219cc 100644 --- a/custom_components/miner/manifest.json +++ b/custom_components/miner/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_polling", "issue_tracker": "https://github.com/Schnitzel/hass-miner/issues", - "requirements": ["pyasic==0.51.0"], + "requirements": ["pyasic==0.51.1"], "ssdp": [], "version": "1.1.2", "zeroconf": [] diff --git a/requirements.txt b/requirements.txt index 16b7ef2..dd6a6d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,6 @@ colorlog==6.7.0 homeassistant>=2024.1.0 pip>=21.0,<23.2 ruff==0.0.267 -pyasic==0.51.0 +pyasic==0.51.1 setuptools==69.0.3 pre-commit From 66ac2443673c1e09f35176cda4cc28a816371a81 Mon Sep 17 00:00:00 2001 From: Brett Rowan <121075405+b-rowan@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:58:09 -0600 Subject: [PATCH 3/3] Update pyasic version, and add reboot and restart services. --- custom_components/miner/__init__.py | 3 +- custom_components/miner/config_flow.py | 4 +- custom_components/miner/const.py | 3 + custom_components/miner/coordinator.py | 6 +- custom_components/miner/device_action.py | 84 ++++++++++++++++++++++++ custom_components/miner/manifest.json | 2 +- custom_components/miner/service.py | 35 ++++++++++ requirements.txt | 4 +- 8 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 custom_components/miner/device_action.py create mode 100644 custom_components/miner/service.py diff --git a/custom_components/miner/__init__.py b/custom_components/miner/__init__.py index d4a0299..dfa28c8 100644 --- a/custom_components/miner/__init__.py +++ b/custom_components/miner/__init__.py @@ -7,7 +7,8 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from .const import DOMAIN, CONF_IP +from .const import CONF_IP +from .const import DOMAIN from .coordinator import MinerCoordinator PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.SWITCH, Platform.NUMBER] diff --git a/custom_components/miner/config_flow.py b/custom_components/miner/config_flow.py index 5743c26..6ed0f87 100644 --- a/custom_components/miner/config_flow.py +++ b/custom_components/miner/config_flow.py @@ -6,7 +6,7 @@ from homeassistant import config_entries from homeassistant.components import network from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_entry_flow +from homeassistant.helpers.config_entry_flow import register_discovery_flow from homeassistant.helpers.selector import TextSelector from homeassistant.helpers.selector import TextSelectorConfig from homeassistant.helpers.selector import TextSelectorType @@ -39,7 +39,7 @@ async def _async_has_devices(hass: HomeAssistant) -> bool: return False -config_entry_flow.register_discovery_flow(DOMAIN, "miner", _async_has_devices) +register_discovery_flow(DOMAIN, "miner", _async_has_devices) async def validate_ip_input( diff --git a/custom_components/miner/const.py b/custom_components/miner/const.py index 113db10..c09ffa4 100644 --- a/custom_components/miner/const.py +++ b/custom_components/miner/const.py @@ -10,5 +10,8 @@ CONF_WEB_PASSWORD = "web_password" CONF_WEB_USERNAME = "web_username" +SERVICE_REBOOT = "reboot" +SERVICE_RESTART_BACKEND = "restart_backend" + TERA_HASH_PER_SECOND = "TH/s" JOULES_PER_TERA_HASH = "J/TH" diff --git a/custom_components/miner/coordinator.py b/custom_components/miner/coordinator.py index 101bcff..7d6a4aa 100644 --- a/custom_components/miner/coordinator.py +++ b/custom_components/miner/coordinator.py @@ -104,8 +104,8 @@ async def _async_update_data(self): "is_mining": miner_data.is_mining, "fw_ver": miner_data.fw_ver, "miner_sensors": { - "hashrate": miner_data.hashrate, - "ideal_hashrate": miner_data.expected_hashrate, + "hashrate": round(float(miner_data.hashrate or 0), 2), + "ideal_hashrate": round(float(miner_data.expected_hashrate or 0), 2), "temperature": miner_data.temperature_avg, "power_limit": miner_data.wattage_limit, "miner_consumption": miner_data.wattage, @@ -115,7 +115,7 @@ async def _async_update_data(self): board.slot: { "board_temperature": board.temp, "chip_temperature": board.chip_temp, - "board_hashrate": board.hashrate, + "board_hashrate": round(float(board.hashrate or 0), 2), } for board in miner_data.hashboards }, diff --git a/custom_components/miner/device_action.py b/custom_components/miner/device_action.py new file mode 100644 index 0000000..aae76a8 --- /dev/null +++ b/custom_components/miner/device_action.py @@ -0,0 +1,84 @@ +"""Provides device actions for Miner.""" +from __future__ import annotations + +import logging + +import voluptuous as vol +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import CONF_DEVICE_ID +from homeassistant.const import CONF_DOMAIN +from homeassistant.const import CONF_ENTITY_ID +from homeassistant.const import CONF_TYPE +from homeassistant.core import Context +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import entity_registry as er + +from .const import DOMAIN +from .const import SERVICE_REBOOT +from .const import SERVICE_RESTART_BACKEND + +_LOGGER = logging.getLogger(__name__) + +ACTION_TYPES = {"reboot", "restart_backend"} + +ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): vol.In(ACTION_TYPES), + vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN), + } +) + + +async def async_get_actions( + hass: HomeAssistant, device_id: str +) -> list[dict[str, str]]: + """List device actions for Miner devices.""" + registry = er.async_get(hass) + actions = [] + + # Get all the integrations entities for this device + for entry in er.async_entries_for_device(registry, device_id): + if entry.domain != DOMAIN: + continue + + # Add actions for each entity that belongs to this integration + base_action = { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + } + for action_type in ACTION_TYPES: + try: + actions.append( + { + **base_action, + CONF_TYPE: action_type, + } + ) + except AttributeError: + _LOGGER.error( + "Failed to run device command for miner: Unable to access entry data." + ) + + return actions + + +async def async_call_action_from_config( + hass: HomeAssistant, config: dict, variables: dict, context: Context | None +) -> None: + """Execute a device action.""" + service = None + service_data = {ATTR_ENTITY_ID: config[CONF_ENTITY_ID]} + + if config[CONF_TYPE] == "reboot": + service = SERVICE_REBOOT + elif config[CONF_TYPE] == "restart_backend": + service = SERVICE_RESTART_BACKEND + + if service is None: + _LOGGER.error(f"Failed to call the service {config[CONF_TYPE]} for miner.") + return + await hass.services.async_call( + DOMAIN, service, service_data, blocking=True, context=context + ) diff --git a/custom_components/miner/manifest.json b/custom_components/miner/manifest.json index 1deed3a..a195e28 100644 --- a/custom_components/miner/manifest.json +++ b/custom_components/miner/manifest.json @@ -7,7 +7,7 @@ "homekit": {}, "iot_class": "local_polling", "issue_tracker": "https://github.com/Schnitzel/hass-miner/issues", - "requirements": ["pyasic==0.54.8"], + "requirements": ["pyasic==0.57.6"], "ssdp": [], "version": "1.1.4", "zeroconf": [] diff --git a/custom_components/miner/service.py b/custom_components/miner/service.py new file mode 100644 index 0000000..ca1ecd3 --- /dev/null +++ b/custom_components/miner/service.py @@ -0,0 +1,35 @@ +"""The Miner component services.""" +from __future__ import annotations + +import logging + +import pyasic +from homeassistant.core import HomeAssistant +from homeassistant.core import ServiceCall + +from .const import CONF_IP +from .const import DOMAIN +from .const import SERVICE_REBOOT +from .const import SERVICE_RESTART_BACKEND + +LOGGER = logging.getLogger(__name__) + + +async def async_setup_services(hass: HomeAssistant) -> None: + """Service handler setup.""" + + async def reboot(call: ServiceCall) -> None: + ip = call.data.get(CONF_IP) + + miner = await pyasic.get_miner(ip) + await miner.reboot() + + hass.services.async_register(DOMAIN, SERVICE_REBOOT, reboot) + + async def restart_backend(call: ServiceCall) -> None: + ip = call.data.get(CONF_IP) + + miner = await pyasic.get_miner(ip) + await miner.restart_backend() + + hass.services.async_register(DOMAIN, SERVICE_RESTART_BACKEND, restart_backend) diff --git a/requirements.txt b/requirements.txt index 6d665bd..3e53e5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ colorlog==6.7.0 -homeassistant>=2024.1.0 +homeassistant>=2024.6.3 pip>=21.0,<23.2 ruff==0.0.267 -pyasic==0.54.8 +pyasic==0.57.6 setuptools==69.0.3 pre-commit