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