From c96433177286f8ab97e99e6569aec3473b2a0f45 Mon Sep 17 00:00:00 2001 From: 1e9abhi1e10 <2311abhiptdr@gmail.com> Date: Tue, 30 Jul 2024 13:38:43 +0530 Subject: [PATCH 1/4] feat: Add update firmware for Avalon miner --- pyasic/miners/backends/avalonminer.py | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pyasic/miners/backends/avalonminer.py b/pyasic/miners/backends/avalonminer.py index a831ce41..839d67ac 100644 --- a/pyasic/miners/backends/avalonminer.py +++ b/pyasic/miners/backends/avalonminer.py @@ -16,6 +16,9 @@ import re from typing import List, Optional +import httpx +import aiofiles +import logging from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.errors import APIError @@ -108,6 +111,58 @@ async def reboot(self) -> bool: return False return False + async def upgrade_firmware(self, ip: str, port: int, file: str) -> str: + """ + Upgrade the firmware of an Avalon Miner. + + Parameters: + ip (str): The IP address of the Avalon Miner. + port (int): The port number of the Avalon Miner's web interface. + file (str): Path to the firmware file to be uploaded. + + Returns: + str: Result of the upgrade process. + """ + if not file: + raise ValueError("File location must be provided for firmware upgrade.") + + async with aiofiles.open(file, "rb") as f: + upgrade_contents = await f.read() + + url = f"http://{ip}:{port}/cgi-bin/upgrade" + data = {'version': self.get_fw_ver()} + + try: + async with httpx.AsyncClient() as client: + response = await client.post( + url, + files={'firmware': upgrade_contents}, + data=data, + auth=('root', 'root'), + timeout=60 + ) + + response_text = response.text + + if 'Upgrade success' in response_text: + logging.info("Firmware upgrade process completed successfully for Avalon Miner.") + return "Firmware upgrade to version {} successful.".format(self.get_fw_ver()) + else: + return f"Firmware upgrade failed. Response: {response_text}" + + except FileNotFoundError as e: + logging.error(f"File not found during the firmware upgrade process: {e}") + raise + except ValueError as e: + logging.error(f"Validation error occurred during the firmware upgrade process: {e}") + raise + except OSError as e: + logging.error(f"OS error occurred during the firmware upgrade process: {e}") + raise + except Exception as e: + logging.error(f"An unexpected error occurred during the firmware upgrade process: {e}", exc_info=True) + raise + @staticmethod def parse_stats(stats): _stats_items = re.findall(".+?\\[*?]", stats) From fd63933a155532362afa36bce0be79302b5e0c09 Mon Sep 17 00:00:00 2001 From: 1e9abhi1e10 <2311abhiptdr@gmail.com> Date: Mon, 5 Aug 2024 23:21:01 +0530 Subject: [PATCH 2/4] 808 --- pyasic/miners/backends/avalonminer.py | 54 ++++++----------- pyasic/web/avalon.py | 87 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 pyasic/web/avalon.py diff --git a/pyasic/miners/backends/avalonminer.py b/pyasic/miners/backends/avalonminer.py index 839d67ac..40697132 100644 --- a/pyasic/miners/backends/avalonminer.py +++ b/pyasic/miners/backends/avalonminer.py @@ -16,14 +16,14 @@ import re from typing import List, Optional -import httpx -import aiofiles import logging +from pathlib import Path from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit from pyasic.errors import APIError from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand +from pyasic.web.avalon import AvalonWebAPI AVALON_DATA_LOC = DataLocations( **{ @@ -78,6 +78,8 @@ class AvalonMiner(CGMiner): """Handler for Avalon Miners""" + _web_cls = AvalonWebAPI + web: AvalonWebAPI data_locations = AVALON_DATA_LOC async def fault_light_on(self) -> bool: @@ -111,54 +113,36 @@ async def reboot(self) -> bool: return False return False - async def upgrade_firmware(self, ip: str, port: int, file: str) -> str: + async def upgrade_firmware(self, ip: str, port: int, file: Path) -> str: """ Upgrade the firmware of an Avalon Miner. - + Parameters: ip (str): The IP address of the Avalon Miner. port (int): The port number of the Avalon Miner's web interface. - file (str): Path to the firmware file to be uploaded. - + file (Path): Path to the firmware file to be uploaded. + Returns: str: Result of the upgrade process. """ - if not file: - raise ValueError("File location must be provided for firmware upgrade.") - - async with aiofiles.open(file, "rb") as f: - upgrade_contents = await f.read() - - url = f"http://{ip}:{port}/cgi-bin/upgrade" - data = {'version': self.get_fw_ver()} - try: - async with httpx.AsyncClient() as client: - response = await client.post( - url, - files={'firmware': upgrade_contents}, - data=data, - auth=('root', 'root'), - timeout=60 - ) + if not file: + raise ValueError("File location must be provided for firmware upgrade.") - response_text = response.text + version = self.get_fw_ver() - if 'Upgrade success' in response_text: - logging.info("Firmware upgrade process completed successfully for Avalon Miner.") - return "Firmware upgrade to version {} successful.".format(self.get_fw_ver()) - else: - return f"Firmware upgrade failed. Response: {response_text}" + result = await self.web.update_firmware(ip=ip, port=port, version=version, file=file) + + if 'Success' in result: + logging.info("Firmware upgrade process completed successfully for Avalon Miner.") + return f"Firmware upgrade to version {version} successful." + else: + logging.error(f"Firmware upgrade failed. Response: {result}") + return f"Firmware upgrade failed. Response: {result}" - except FileNotFoundError as e: - logging.error(f"File not found during the firmware upgrade process: {e}") - raise except ValueError as e: logging.error(f"Validation error occurred during the firmware upgrade process: {e}") raise - except OSError as e: - logging.error(f"OS error occurred during the firmware upgrade process: {e}") - raise except Exception as e: logging.error(f"An unexpected error occurred during the firmware upgrade process: {e}", exc_info=True) raise diff --git a/pyasic/web/avalon.py b/pyasic/web/avalon.py new file mode 100644 index 00000000..81f7ce5e --- /dev/null +++ b/pyasic/web/avalon.py @@ -0,0 +1,87 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ + +from __future__ import annotations + +import aiofiles +import httpx +import json +from pathlib import Path +from typing import Any, Optional + +from pyasic import settings +from pyasic.web.base import BaseWebAPI + + +class AvalonWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + super().__init__(ip) + self.username = "admin" + self.pwd = settings.get("default_avalon_web_password", "admin") + self.port = 80 + self.token = None + + async def auth(self) -> None: + """Authenticate and get the token. Implement the actual authentication logic here.""" + self.token = "Success" + + async def send_command( + self, + command: str | bytes, + ignore_errors: bool = False, + allow_warning: bool = True, + privileged: bool = False, + upgrade_file: Optional[Path] = None, + **parameters: Any, + ) -> dict: + + if self.token is None: + await self.auth() + + async with httpx.AsyncClient(transport=settings.transport()) as client: + for i in range(settings.get("get_data_retries", 1)): + try: + if upgrade_file: + async with aiofiles.open(upgrade_file, "rb") as f: + upgrade_contents = await f.read() + + url = f"http://{self.ip}:{self.port}/cgi-bin/upgrade" + data = {'version': parameters.get('version', 'latest')} + response = await client.post( + url, + files={'firmware': upgrade_contents}, + data=data, + auth=(self.username, self.pwd), + timeout=60 + ) + return response.json() + else: + response = await client.get( + f"http://{self.ip}:{self.port}/{command}", + timeout=5, + ) + return response.json() + except (httpx.HTTPError, json.JSONDecodeError): + pass + return {} + + async def update_firmware(self, ip: str, port: int, version: str = "latest", file: Optional[Path] = None) -> dict: + """Perform a system update by uploading a firmware file and sending a + command to initiate the update.""" + return await self.send_command( + upgrade_file=file, + version=version + ) \ No newline at end of file From ff0155e252740c929ac89fe81b75a4c08ae0a4db Mon Sep 17 00:00:00 2001 From: 1e9abhi1e10 <2311abhiptdr@gmail.com> Date: Mon, 5 Aug 2024 23:35:00 +0530 Subject: [PATCH 3/4] Fix command error --- pyasic/web/avalon.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/pyasic/web/avalon.py b/pyasic/web/avalon.py index 81f7ce5e..810ad13e 100644 --- a/pyasic/web/avalon.py +++ b/pyasic/web/avalon.py @@ -39,25 +39,21 @@ async def auth(self) -> None: self.token = "Success" async def send_command( - self, - command: str | bytes, - ignore_errors: bool = False, - allow_warning: bool = True, - privileged: bool = False, - upgrade_file: Optional[Path] = None, - **parameters: Any, - ) -> dict: - - if self.token is None: - await self.auth() - - async with httpx.AsyncClient(transport=settings.transport()) as client: - for i in range(settings.get("get_data_retries", 1)): + self, + command: str, + ignore_errors: bool = False, + allow_warning: bool = True, + privileged: bool = False, + upgrade_file: Optional[Path] = None, + **parameters: Any, + ) -> dict: + if self.token is None: + await self.auth() + async with httpx.AsyncClient(transport=settings.transport()) as client: try: if upgrade_file: async with aiofiles.open(upgrade_file, "rb") as f: upgrade_contents = await f.read() - url = f"http://{self.ip}:{self.port}/cgi-bin/upgrade" data = {'version': parameters.get('version', 'latest')} response = await client.post( @@ -76,12 +72,13 @@ async def send_command( return response.json() except (httpx.HTTPError, json.JSONDecodeError): pass - return {} + return {} async def update_firmware(self, ip: str, port: int, version: str = "latest", file: Optional[Path] = None) -> dict: """Perform a system update by uploading a firmware file and sending a command to initiate the update.""" return await self.send_command( + command="upgrade", upgrade_file=file, version=version ) \ No newline at end of file From a32ef1f36c6904a0893ff0f62dbd9aba110f3f97 Mon Sep 17 00:00:00 2001 From: 1e9abhi1e10 <2311abhiptdr@gmail.com> Date: Wed, 7 Aug 2024 09:45:59 +0530 Subject: [PATCH 4/4] Made send_command more generic and modified parameters of upgrade_firmware --- pyasic/miners/backends/avalonminer.py | 16 ++--- pyasic/web/avalon.py | 85 ++++++++++++++------------- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/pyasic/miners/backends/avalonminer.py b/pyasic/miners/backends/avalonminer.py index 40697132..fa7d915b 100644 --- a/pyasic/miners/backends/avalonminer.py +++ b/pyasic/miners/backends/avalonminer.py @@ -113,29 +113,25 @@ async def reboot(self) -> bool: return False return False - async def upgrade_firmware(self, ip: str, port: int, file: Path) -> str: + async def upgrade_firmware(self, file: Path) -> str: """ Upgrade the firmware of an Avalon Miner. - Parameters: - ip (str): The IP address of the Avalon Miner. - port (int): The port number of the Avalon Miner's web interface. - file (Path): Path to the firmware file to be uploaded. + Args: + file (Path): Path to the firmware file to be uploaded. Returns: - str: Result of the upgrade process. + str: Result of the upgrade process. """ try: if not file: raise ValueError("File location must be provided for firmware upgrade.") - version = self.get_fw_ver() - - result = await self.web.update_firmware(ip=ip, port=port, version=version, file=file) + result = await self.web.update_firmware(file=file) if 'Success' in result: logging.info("Firmware upgrade process completed successfully for Avalon Miner.") - return f"Firmware upgrade to version {version} successful." + return "Firmware upgrade successful." else: logging.error(f"Firmware upgrade failed. Response: {result}") return f"Firmware upgrade failed. Response: {result}" diff --git a/pyasic/web/avalon.py b/pyasic/web/avalon.py index 810ad13e..dacb82b9 100644 --- a/pyasic/web/avalon.py +++ b/pyasic/web/avalon.py @@ -20,7 +20,7 @@ import httpx import json from pathlib import Path -from typing import Any, Optional +from typing import Any from pyasic import settings from pyasic.web.base import BaseWebAPI @@ -39,46 +39,49 @@ async def auth(self) -> None: self.token = "Success" async def send_command( - self, - command: str, - ignore_errors: bool = False, - allow_warning: bool = True, - privileged: bool = False, - upgrade_file: Optional[Path] = None, - **parameters: Any, - ) -> dict: - if self.token is None: - await self.auth() - async with httpx.AsyncClient(transport=settings.transport()) as client: - try: - if upgrade_file: - async with aiofiles.open(upgrade_file, "rb") as f: - upgrade_contents = await f.read() - url = f"http://{self.ip}:{self.port}/cgi-bin/upgrade" - data = {'version': parameters.get('version', 'latest')} - response = await client.post( - url, - files={'firmware': upgrade_contents}, - data=data, - auth=(self.username, self.pwd), - timeout=60 - ) - return response.json() - else: - response = await client.get( - f"http://{self.ip}:{self.port}/{command}", - timeout=5, - ) - return response.json() - except (httpx.HTTPError, json.JSONDecodeError): - pass - return {} + self, + command: str, + ignore_errors: bool = False, + allow_warning: bool = True, + privileged: bool = False, + **parameters: Any, + ) -> dict: + if self.token is None: + await self.auth() - async def update_firmware(self, ip: str, port: int, version: str = "latest", file: Optional[Path] = None) -> dict: - """Perform a system update by uploading a firmware file and sending a - command to initiate the update.""" + async with httpx.AsyncClient(transport=settings.transport()) as client: + try: + url = f"http://{self.ip}:{self.port}/{command}" + + if 'file' in parameters: + file = parameters.pop('file') + async with aiofiles.open(file, "rb") as f: + file_contents = await f.read() + files = {'firmware': file_contents} + response = await client.post( + url, + files=files, + data=parameters, + auth=(self.username, self.pwd), + timeout=60 + ) + else: + response = await client.get( + url, + params=parameters, + auth=(self.username, self.pwd), + timeout=5, + ) + + return response.json() + except (httpx.HTTPError, json.JSONDecodeError): + if not ignore_errors: + raise + return {} + + async def update_firmware(self, file: Path) -> dict: + """Perform a system update by uploading a firmware file and sending a command to initiate the update.""" return await self.send_command( - command="upgrade", - upgrade_file=file, - version=version + command="cgi-bin/upgrade", + file=file, ) \ No newline at end of file