From 5c904aced0d6e6940d263710de76b33ec7ac6bc6 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 22 Sep 2023 09:32:59 -0600 Subject: [PATCH 01/27] feature: refactor BOS web class into multiple classes. --- pyasic/web/bosminer.py | 144 ++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index ab1aa14b..bb1d40f0 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -25,8 +25,20 @@ from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: + self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) + self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) + self._pwd = PyasicSettings().global_bosminer_password super().__init__(ip) - self.pwd = PyasicSettings().global_bosminer_password + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + self.gql.pwd = other async def send_command( self, @@ -35,44 +47,10 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if isinstance(command, str): - return await self.send_luci_command(command) + if command.startswith("/cgi-bin/luci"): + return await self.luci.send_command(command) else: - return await self.send_gql_command(command) - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def send_gql_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}/graphql" - query = command - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient() as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass + return await self.gql.send_command(command) async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True @@ -80,13 +58,13 @@ class BOSMinerWebAPI(BaseWebAPI): luci_commands = [] gql_commands = [] for cmd in commands: + if cmd.startswith("/cgi-bin/luci"): + luci_commands.append(cmd) if isinstance(cmd, dict): gql_commands.append(cmd) - if isinstance(cmd, str): - luci_commands.append(cmd) - luci_data = await self.luci_multicommand(*luci_commands) - gql_data = await self.gql_multicommand(*gql_commands) + luci_data = await self.luci.multicommand(*luci_commands) + gql_data = await self.gql.multicommand(*gql_commands) if gql_data is None: gql_data = {} @@ -96,13 +74,14 @@ class BOSMinerWebAPI(BaseWebAPI): data = dict(**luci_data, **gql_data) return data - async def luci_multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_luci_command(command, ignore_errors=True) - return data - async def gql_multicommand(self, *commands: dict) -> dict: +class BOSMinerGQLAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: dict) -> dict: def merge(*d: dict): ret = {} for i in d: @@ -123,7 +102,7 @@ class BOSMinerWebAPI(BaseWebAPI): # noinspection PyTypeChecker commands.remove({"bos": {"faultLight": None}}) command = merge(*commands) - data = await self.send_gql_command(command) + data = await self.send_command(command) except (LookupError, ValueError): pass if not data: @@ -131,6 +110,40 @@ class BOSMinerWebAPI(BaseWebAPI): data["multicommand"] = False return data + async def send_command( + self, + command: dict, + ) -> dict: + url = f"http://{self.ip}/graphql" + query = command + if command.get("query") is None: + query = {"query": self.parse_command(command)} + try: + async with httpx.AsyncClient() as client: + await self.auth(client) + data = await client.post(url, json=query) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" + async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" await client.post( @@ -144,10 +157,23 @@ class BOSMinerWebAPI(BaseWebAPI): }, ) - async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: + +class BOSMinerLuCIAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def send_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.luci_auth(client) + await self.auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -163,7 +189,7 @@ class BOSMinerWebAPI(BaseWebAPI): return {} raise APIError(f"Web command failed: path={path}") - async def luci_auth(self, session: httpx.AsyncClient): + async def auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -173,23 +199,21 @@ class BOSMinerWebAPI(BaseWebAPI): await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_luci_command( - "/cgi-bin/luci/admin/network/iface_status/lan" - ) + return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") async def get_cfg_metadata(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_luci_command("/cgi-bin/luci/bos/info") + return await self.send_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_luci_command( + return await self.send_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") + return await self.send_command("/cgi-bin/luci/admin/miner/api_status") From 14f33a40c33f18199dcb10288330a1e0c1a32976 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 22 Sep 2023 09:44:25 -0600 Subject: [PATCH 02/27] feature: add grpc BOS class and add grpc requests to requirements. --- pyasic/web/bosminer.py | 6 ++++++ pyproject.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index bb1d40f0..357eff80 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -217,3 +217,9 @@ class BOSMinerLuCIAPI: async def get_api_status(self): return await self.send_command("/cgi-bin/luci/admin/miner/api_status") + + +class BOSMinerGRPCAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.pwd = pwd diff --git a/pyproject.toml b/pyproject.toml index 68e29308..106b6e00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ httpx = "^0.24.0" passlib = "^1.7.4" pyaml = "^23.5.9" toml = "^0.10.2" +grpc-requests = "^0.1.10" [tool.poetry.group.dev] optional = true From fa7544d052216afaf6186a7aeaf10c274db8eee2 Mon Sep 17 00:00:00 2001 From: UpstreamData <75442874+UpstreamData@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:50:56 -0600 Subject: [PATCH 03/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39f2bd9a..12fd5cca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # pyasic -*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.* +*A simplified and standardized interface for Bitcoin ASICs.* [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![pypi](https://img.shields.io/pypi/v/pyasic.svg)](https://pypi.org/project/pyasic/) From 45250e36e4e4d3f8dd335afba106d77af9ef63c9 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 28 Sep 2023 15:41:53 -0600 Subject: [PATCH 04/27] bug: fix whatsminer identification to work with backwards incompatible changes in API 2.0.5. --- pyasic/miners/miner_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 81e101d2..422280f8 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -753,7 +753,7 @@ class MinerFactory: async def get_miner_model_whatsminer(self, ip: str): sock_json_data = await self.send_api_command(ip, "devdetails") try: - miner_model = sock_json_data["DEVDETAILS"][0]["Model"] + miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "") return miner_model except (TypeError, LookupError): From 98c547e416f2e518d2d66d42187c62893fa4f786 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 28 Sep 2023 15:47:20 -0600 Subject: [PATCH 05/27] bug: fAdd new commands added in whatsminer API 2.0.5. --- pyasic/API/btminer.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pyasic/API/btminer.py b/pyasic/API/btminer.py index 686f9940..3ebf6954 100644 --- a/pyasic/API/btminer.py +++ b/pyasic/API/btminer.py @@ -735,6 +735,34 @@ class BTMinerAPI(BaseMinerAPI): ) ### ADDED IN V2.0.5 Whatsminer API ### + + @api_min_version("2.0.5") + async def set_power_pct_v2(self, percent: int) -> dict: + """Set the power percentage of the miner based on current power. Used for temporary adjustment. Added in API v2.0.5. + +
+ Expand + + Set the power percentage of the miner, only works after changing + the password of the miner using the Whatsminer tool. + + Parameters: + percent: The power percentage to set. + Returns: + A reply informing of the status of setting the power percentage. +
+ """ + + if not 0 < percent < 100: + raise APIError( + f"Power PCT % is outside of the allowed " + f"range. Please set a % between 0 and " + f"100" + ) + return await self.send_privileged_command( + "set_power_pct_v2", percent=str(percent) + ) + @api_min_version("2.0.5") async def set_temp_offset(self, temp_offset: int) -> dict: """Set the offset of miner hash board target temperature. From 75d6bc6808ba7ff1cf5dcaf72312d5c209ba0630 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 28 Sep 2023 15:47:49 -0600 Subject: [PATCH 06/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 106b6e00..f658771d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.38.8" +version = "0.38.9" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 8aeef4d5e77122dd055956d771b04af8d9c0b9ef Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 09:20:01 -0600 Subject: [PATCH 07/27] feature: add support for M20P, and add chips for M20SV30. --- pyasic/miners/miner_factory.py | 2 ++ pyasic/miners/types/whatsminer/M2X/M20P.py | 35 +++++++++++++++++++ pyasic/miners/types/whatsminer/M2X/M20S.py | 5 +-- .../miners/types/whatsminer/M2X/__init__.py | 1 + pyasic/miners/whatsminer/btminer/M2X/M20P.py | 26 ++++++++++++++ .../miners/whatsminer/btminer/M2X/__init__.py | 1 + 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 pyasic/miners/types/whatsminer/M2X/M20P.py create mode 100644 pyasic/miners/whatsminer/btminer/M2X/M20P.py diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 422280f8..31f343b1 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -101,6 +101,8 @@ MINER_CLASSES = { "M20SV10": BTMinerM20SV10, "M20SV20": BTMinerM20SV20, "M20SV30": BTMinerM20SV30, + "M20PV10": BTMinerM20PV10, + "M20PV30": BTMinerM20PV30, "M20S+V30": BTMinerM20SPlusV30, "M21V10": BTMinerM21V10, "M21SV20": BTMinerM21SV20, diff --git a/pyasic/miners/types/whatsminer/M2X/M20P.py b/pyasic/miners/types/whatsminer/M2X/M20P.py new file mode 100644 index 00000000..87bc028d --- /dev/null +++ b/pyasic/miners/types/whatsminer/M2X/M20P.py @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------------------ +# 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 pyasic.miners.makes import WhatsMiner + + +class M20PV10(WhatsMiner): # noqa - ignore ABC method implementation + def __init__(self, ip: str, api_ver: str = "0.0.0"): + super().__init__(ip, api_ver) + self.ip = ip + self.model = "M20P V10" + self.nominal_chips = 156 + self.fan_count = 2 + + +class M20PV30(WhatsMiner): # noqa - ignore ABC method implementation + def __init__(self, ip: str, api_ver: str = "0.0.0"): + super().__init__(ip, api_ver) + self.ip = ip + self.model = "M20P V30" + self.nominal_chips = 148 + self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M20S.py b/pyasic/miners/types/whatsminer/M2X/M20S.py index ebf65dd9..1f272e65 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20S.py +++ b/pyasic/miners/types/whatsminer/M2X/M20S.py @@ -42,8 +42,5 @@ class M20SV30(WhatsMiner): # noqa - ignore ABC method implementation super().__init__(ip, api_ver) self.ip = ip self.model = "M20S V30" - self.nominal_chips = 0 - warnings.warn( - "Unknown chip count for miner type M20SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." - ) + self.nominal_chips = 140 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/__init__.py b/pyasic/miners/types/whatsminer/M2X/__init__.py index f8e94a24..5a1f2a8d 100644 --- a/pyasic/miners/types/whatsminer/M2X/__init__.py +++ b/pyasic/miners/types/whatsminer/M2X/__init__.py @@ -15,6 +15,7 @@ # ------------------------------------------------------------------------------ from .M20 import M20V10 +from .M20P import M20PV10, M20PV30 from .M20S import M20SV10, M20SV20, M20SV30 from .M20S_Plus import M20SPlusV30 from .M21 import M21V10 diff --git a/pyasic/miners/whatsminer/btminer/M2X/M20P.py b/pyasic/miners/whatsminer/btminer/M2X/M20P.py new file mode 100644 index 00000000..276f1493 --- /dev/null +++ b/pyasic/miners/whatsminer/btminer/M2X/M20P.py @@ -0,0 +1,26 @@ +# ------------------------------------------------------------------------------ +# 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 pyasic.miners.backends import M2X +from pyasic.miners.types import M20PV10, M20PV30 + + +class BTMinerM20PV10(M2X, M20PV10): + pass + + +class BTMinerM20PV30(M2X, M20PV30): + pass diff --git a/pyasic/miners/whatsminer/btminer/M2X/__init__.py b/pyasic/miners/whatsminer/btminer/M2X/__init__.py index 6f3d33b5..976690e0 100644 --- a/pyasic/miners/whatsminer/btminer/M2X/__init__.py +++ b/pyasic/miners/whatsminer/btminer/M2X/__init__.py @@ -15,6 +15,7 @@ # ------------------------------------------------------------------------------ from .M20 import BTMinerM20V10 +from .M20P import BTMinerM20PV10, BTMinerM20PV30 from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30 from .M20S_Plus import BTMinerM20SPlusV30 from .M21 import BTMinerM21V10 From fd8cc7378c2ad59da183ed5f9318a3c70c807be5 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 09:20:24 -0600 Subject: [PATCH 08/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f658771d..9ecb05f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.38.9" +version = "0.38.10" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 7bab4747ad5742d788478c39ffbe04e2ddfe324c Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 13:13:31 -0600 Subject: [PATCH 09/27] refactor: improve settings handling to not use a dataclass, and not use singleton. --- pyasic/API/btminer.py | 4 +- pyasic/__init__.py | 4 +- pyasic/logger/__init__.py | 38 ++++--- pyasic/network/__init__.py | 14 +-- pyasic/settings/__init__.py | 37 ++++--- pyasic/web/antminer.py | 6 +- pyasic/web/bosminer.py | 201 +++++++++++++++--------------------- pyasic/web/goldshell.py | 6 +- pyasic/web/inno.py | 6 +- pyasic/web/vnish.py | 6 +- 10 files changed, 144 insertions(+), 178 deletions(-) diff --git a/pyasic/API/btminer.py b/pyasic/API/btminer.py index 3ebf6954..369d3363 100644 --- a/pyasic/API/btminer.py +++ b/pyasic/API/btminer.py @@ -27,10 +27,10 @@ from typing import Literal, Union from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from passlib.handlers.md5_crypt import md5_crypt +from pyasic import settings from pyasic.API import BaseMinerAPI from pyasic.errors import APIError from pyasic.misc import api_min_version -from pyasic.settings import PyasicSettings ### IMPORTANT ### # you need to change the password of the miners using the Whatsminer @@ -192,7 +192,7 @@ class BTMinerAPI(BaseMinerAPI): ip: str, api_ver: str = "0.0.0", port: int = 4028, - pwd: str = PyasicSettings().global_whatsminer_password, + pwd: str = settings.get("default_whatsminer_password", "admin"), ): super().__init__(ip, port) self.pwd = pwd diff --git a/pyasic/__init__.py b/pyasic/__init__.py index 72861c17..3803375e 100644 --- a/pyasic/__init__.py +++ b/pyasic/__init__.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from pyasic import settings from pyasic.API.bmminer import BMMinerAPI from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.btminer import BTMinerAPI @@ -32,7 +33,6 @@ from pyasic.miners.base import AnyMiner from pyasic.miners.miner_factory import MinerFactory from pyasic.miners.miner_listener import MinerListener from pyasic.network import MinerNetwork -from pyasic.settings import PyasicSettings __all__ = [ "BMMinerAPI", @@ -53,5 +53,5 @@ __all__ = [ "MinerFactory", "MinerListener", "MinerNetwork", - "PyasicSettings", + "settings", ] diff --git a/pyasic/logger/__init__.py b/pyasic/logger/__init__.py index 51c22e51..51e17c04 100644 --- a/pyasic/logger/__init__.py +++ b/pyasic/logger/__init__.py @@ -16,31 +16,29 @@ import logging -from pyasic.settings import PyasicSettings - def init_logger(): - if PyasicSettings().logfile: - logging.basicConfig( - filename="logfile.txt", - filemode="a", - format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s", - datefmt="%x %X", - ) - else: - logging.basicConfig( - format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s", - datefmt="%x %X", - ) + # if PyasicSettings().logfile: + # logging.basicConfig( + # filename="logfile.txt", + # filemode="a", + # format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s", + # datefmt="%x %X", + # ) + # else: + logging.basicConfig( + format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s", + datefmt="%x %X", + ) _logger = logging.getLogger() - if PyasicSettings().debug: - _logger.setLevel(logging.DEBUG) - logging.getLogger("asyncssh").setLevel(logging.DEBUG) - else: - _logger.setLevel(logging.WARNING) - logging.getLogger("asyncssh").setLevel(logging.WARNING) + # if PyasicSettings().debug: + # _logger.setLevel(logging.DEBUG) + # logging.getLogger("asyncssh").setLevel(logging.DEBUG) + # else: + _logger.setLevel(logging.WARNING) + logging.getLogger("asyncssh").setLevel(logging.WARNING) return _logger diff --git a/pyasic/network/__init__.py b/pyasic/network/__init__.py index d2cc781a..cd9fbc69 100644 --- a/pyasic/network/__init__.py +++ b/pyasic/network/__init__.py @@ -19,9 +19,9 @@ import ipaddress import logging from typing import AsyncIterator, List, Union +from pyasic import settings from pyasic.miners.miner_factory import AnyMiner, miner_factory from pyasic.network.net_range import MinerNetworkRange -from pyasic.settings import PyasicSettings class MinerNetwork: @@ -108,7 +108,7 @@ class MinerNetwork: # clear cached miners miner_factory.clear_cached_miners() - limit = asyncio.Semaphore(PyasicSettings().network_scan_threads) + limit = asyncio.Semaphore(settings.get("network_scan_threads", 300)) miners = await asyncio.gather( *[self.ping_and_get_miner(host, limit) for host in local_network.hosts()] ) @@ -136,7 +136,7 @@ class MinerNetwork: local_network = self.get_network() # create a list of scan tasks - limit = asyncio.Semaphore(PyasicSettings().network_scan_threads) + limit = asyncio.Semaphore(settings.get("network_scan_threads", 300)) miners = asyncio.as_completed( [ loop.create_task(self.ping_and_get_miner(host, limit)) @@ -191,12 +191,12 @@ class MinerNetwork: async def ping_miner( ip: ipaddress.ip_address, port=4028 ) -> Union[None, ipaddress.ip_address]: - for i in range(PyasicSettings().network_ping_retries): + for i in range(settings.get("network_ping_retries", 1)): try: connection_fut = asyncio.open_connection(str(ip), port) # get the read and write streams from the connection reader, writer = await asyncio.wait_for( - connection_fut, timeout=PyasicSettings().network_ping_timeout + connection_fut, timeout=settings.get("network_ping_timeout", 3) ) # immediately close connection, we know connection happened writer.close() @@ -220,12 +220,12 @@ async def ping_miner( async def ping_and_get_miner( ip: ipaddress.ip_address, port=4028 ) -> Union[None, AnyMiner]: - for i in range(PyasicSettings().network_ping_retries): + for i in range(settings.get("network_ping_retries", 1)): try: connection_fut = asyncio.open_connection(str(ip), port) # get the read and write streams from the connection reader, writer = await asyncio.wait_for( - connection_fut, timeout=PyasicSettings().network_ping_timeout + connection_fut, timeout=settings.get("network_ping_timeout", 3) ) # immediately close connection, we know connection happened writer.close() diff --git a/pyasic/settings/__init__.py b/pyasic/settings/__init__.py index dfe49aa5..e7773d10 100644 --- a/pyasic/settings/__init__.py +++ b/pyasic/settings/__init__.py @@ -14,27 +14,26 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from dataclasses import dataclass +from typing import Any -from pyasic.misc import Singleton +_settings = { # defaults + "network_ping_retries": 1, + "network_ping_timeout": 3, + "network_scan_threads": 300, + "factory_get_retries": 1, + "get_data_retries": 1, + "default_whatsminer_password": "admin", + "default_innosilicon_password": "admin", + "default_antminer_password": "root", + "default_bosminer_password": "root", + "default_vnish_password": "admin", + "default_goldshell_password": "123456789", +} -@dataclass -class PyasicSettings(metaclass=Singleton): - network_ping_retries: int = 1 - network_ping_timeout: int = 3 - network_scan_threads: int = 300 +def get(key: str, other: Any = None) -> Any: + return _settings.get(key, other) - miner_factory_get_version_retries: int = 1 - miner_get_data_retries: int = 1 - - global_whatsminer_password = "admin" - global_innosilicon_password = "admin" - global_antminer_password = "root" - global_bosminer_password = "root" - global_vnish_password = "admin" - global_goldshell_password = "123456789" - - debug: bool = False - logfile: bool = False +def update(key: str, val: Any) -> Any: + _settings[key] = val diff --git a/pyasic/web/antminer.py b/pyasic/web/antminer.py index 364e3ae4..f2c70c53 100644 --- a/pyasic/web/antminer.py +++ b/pyasic/web/antminer.py @@ -19,14 +19,14 @@ from typing import Union import httpx -from pyasic.settings import PyasicSettings +from pyasic import settings from pyasic.web import BaseWebAPI class AntminerModernWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) - self.pwd = PyasicSettings().global_antminer_password + self.pwd = settings.get("default_antminer_password", "root") async def send_command( self, @@ -137,7 +137,7 @@ class AntminerModernWebAPI(BaseWebAPI): class AntminerOldWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) - self.pwd = PyasicSettings().global_antminer_password + self.pwd = settings.get("default_antminer_password", "root") async def send_command( self, diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index 357eff80..c82bf8d8 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -18,27 +18,14 @@ from typing import Union import httpx -from pyasic import APIError -from pyasic.settings import PyasicSettings +from pyasic import APIError, settings from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: - self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) - self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) - self._pwd = PyasicSettings().global_bosminer_password super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - self.gql.pwd = other + self.pwd = settings.get("default_bosminer_password", "root") async def send_command( self, @@ -47,70 +34,25 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if command.startswith("/cgi-bin/luci"): - return await self.luci.send_command(command) + if isinstance(command, str): + return await self.send_luci_command(command) else: - return await self.gql.send_command(command) + return await self.send_gql_command(command) - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - luci_commands = [] - gql_commands = [] - for cmd in commands: - if cmd.startswith("/cgi-bin/luci"): - luci_commands.append(cmd) - if isinstance(cmd, dict): - gql_commands.append(cmd) + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" - luci_data = await self.luci.multicommand(*luci_commands) - gql_data = await self.gql.multicommand(*gql_commands) - - if gql_data is None: - gql_data = {} - if luci_data is None: - luci_data = {} - - data = dict(**luci_data, **gql_data) - return data - - -class BOSMinerGQLAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if not k in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def send_command( + async def send_gql_command( self, command: dict, ) -> dict: @@ -131,18 +73,62 @@ class BOSMinerGQLAPI: except json.decoder.JSONDecodeError: pass - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + luci_commands = [] + gql_commands = [] + for cmd in commands: + if isinstance(cmd, dict): + gql_commands.append(cmd) + if isinstance(cmd, str): + luci_commands.append(cmd) + + luci_data = await self.luci_multicommand(*luci_commands) + gql_data = await self.gql_multicommand(*gql_commands) + + if gql_data is None: + gql_data = {} + if luci_data is None: + luci_data = {} + + data = dict(**luci_data, **gql_data) + return data + + async def luci_multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_luci_command(command, ignore_errors=True) + return data + + async def gql_multicommand(self, *commands: dict) -> dict: + def merge(*d: dict): + ret = {} + for i in d: + if i: + for k in i: + if not k in ret: + ret[k] = i[k] + else: + ret[k] = merge(ret[k], i[k]) + return None if ret == {} else ret + + command = merge(*commands) + data = await self.send_command(command) + if data is not None: + if data.get("data") is None: + try: + commands = list(commands) + # noinspection PyTypeChecker + commands.remove({"bos": {"faultLight": None}}) + command = merge(*commands) + data = await self.send_gql_command(command) + except (LookupError, ValueError): + pass + if not data: + data = {} + data["multicommand"] = False + return data async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" @@ -157,23 +143,10 @@ class BOSMinerGQLAPI: }, ) - -class BOSMinerLuCIAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_command(command, ignore_errors=True) - return data - - async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.auth(client) + await self.luci_auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -189,7 +162,7 @@ class BOSMinerLuCIAPI: return {} raise APIError(f"Web command failed: path={path}") - async def auth(self, session: httpx.AsyncClient): + async def luci_auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -199,27 +172,23 @@ class BOSMinerLuCIAPI: await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") + return await self.send_luci_command( + "/cgi-bin/luci/admin/network/iface_status/lan" + ) async def get_cfg_metadata(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_command("/cgi-bin/luci/bos/info") + return await self.send_luci_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_command( + return await self.send_luci_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_command("/cgi-bin/luci/admin/miner/api_status") - - -class BOSMinerGRPCAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.pwd = pwd + return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") diff --git a/pyasic/web/goldshell.py b/pyasic/web/goldshell.py index 0bbfd952..3a4e3ebb 100644 --- a/pyasic/web/goldshell.py +++ b/pyasic/web/goldshell.py @@ -19,7 +19,7 @@ from typing import Union import httpx -from pyasic.settings import PyasicSettings +from pyasic import settings from pyasic.web import BaseWebAPI @@ -27,7 +27,7 @@ class GoldshellWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) self.username = "admin" - self.pwd = PyasicSettings().global_goldshell_password + self.pwd = settings.get("default_goldshell_password", "123456789") self.jwt = None async def auth(self): @@ -72,7 +72,7 @@ class GoldshellWebAPI(BaseWebAPI): if not self.jwt: await self.auth() async with httpx.AsyncClient() as client: - for i in range(PyasicSettings().miner_get_data_retries): + for i in range(settings.get("get_data_retries", 1)): try: if parameters: response = await client.put( diff --git a/pyasic/web/inno.py b/pyasic/web/inno.py index 5c573d54..a95935ef 100644 --- a/pyasic/web/inno.py +++ b/pyasic/web/inno.py @@ -19,8 +19,8 @@ from typing import Union import httpx +from pyasic import settings from pyasic.errors import APIError -from pyasic.settings import PyasicSettings from pyasic.web import BaseWebAPI @@ -28,7 +28,7 @@ class InnosiliconWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) self.username = "admin" - self.pwd = PyasicSettings().global_innosilicon_password + self.pwd = settings.get("default_innosilicon_password", "admin") self.jwt = None async def auth(self): @@ -55,7 +55,7 @@ class InnosiliconWebAPI(BaseWebAPI): if not self.jwt: await self.auth() async with httpx.AsyncClient() as client: - for i in range(PyasicSettings().miner_get_data_retries): + for i in range(settings.get("get_data_retries", 1)): try: response = await client.post( f"http://{self.ip}/api/{command}", diff --git a/pyasic/web/vnish.py b/pyasic/web/vnish.py index fa88983e..a2b81c47 100644 --- a/pyasic/web/vnish.py +++ b/pyasic/web/vnish.py @@ -19,7 +19,7 @@ from typing import Union import httpx -from pyasic.settings import PyasicSettings +from pyasic import settings from pyasic.web import BaseWebAPI @@ -27,7 +27,7 @@ class VNishWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) self.username = "admin" - self.pwd = PyasicSettings().global_vnish_password + self.pwd = settings.get("default_vnish_password", "admin") self.token = None async def auth(self): @@ -59,7 +59,7 @@ class VNishWebAPI(BaseWebAPI): if not self.token: await self.auth() async with httpx.AsyncClient() as client: - for i in range(PyasicSettings().miner_get_data_retries): + for i in range(settings.get("get_data_retries", 1)): try: auth = self.token if command.startswith("system"): From 4c09ba55291da12b0b8dc8acace7378924caafb1 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 13:14:21 -0600 Subject: [PATCH 10/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9ecb05f9..eed059b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.38.10" +version = "0.39.0" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 1e37418909d4cb01ca2677ea8942b92306be4381 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Tue, 3 Oct 2023 11:11:32 -0600 Subject: [PATCH 11/27] bug: fix some issues with early version of whatsminers, and handle some possible errors with BOS. --- pyasic/miners/backends/bosminer.py | 2 +- pyasic/miners/backends/btminer.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyasic/miners/backends/bosminer.py b/pyasic/miners/backends/bosminer.py index 516a83b5..b13fbd6f 100644 --- a/pyasic/miners/backends/bosminer.py +++ b/pyasic/miners/backends/bosminer.py @@ -1043,7 +1043,7 @@ class BOSMiner(BaseMiner): if data == "50": self.light = True return self.light - except TypeError: + except (TypeError, AttributeError): return self.light async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index e1026b26..6cb76160 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -442,7 +442,8 @@ class BTMiner(BaseMiner): if api_summary: try: - return api_summary["SUMMARY"][0]["Power"] + wattage = api_summary["SUMMARY"][0]["Power"] + return wattage if not wattage == -1 else None except (KeyError, IndexError): pass From 91f207316abf9c04d37cd1d0a46f5e4a286a5d6a Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Tue, 3 Oct 2023 11:12:11 -0600 Subject: [PATCH 12/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eed059b6..a223e0fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.39.0" +version = "0.39.1" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 53eaccaa9bce6baa5c7e407b8d42b3552ab2c7cc Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Tue, 3 Oct 2023 14:59:25 -0600 Subject: [PATCH 13/27] docs: update documentation. --- docs/index.md | 26 +++++++++++++++++++++++++- docs/miners/miner_factory.md | 8 ++++++++ docs/settings/settings.md | 23 +++++++++++++++++++++++ mkdocs.yml | 3 ++- pyasic/__init__.py | 3 ++- pyasic/miners/miner_factory.py | 13 ++++++------- 6 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 docs/settings/settings.md diff --git a/docs/index.md b/docs/index.md index 0c687ef5..74677c8f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -263,7 +263,7 @@ Pyasic implements a few dataclasses as helpers to make data return types consist [`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns. -You can call [`MinerData.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats. +You can call [`MinerData.as_dict()`][pyasic.data.MinerData.as_dict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats. [`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing - ```python @@ -288,3 +288,27 @@ It is the return from [`get_config()`](#get-config). Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class. In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you. + +## Settings +`pyasic` has settings designed to make using large groups of miners easier. You can set the default password for all types of miners using the [`pyasic.settings`][pyasic.settings] module, used as follows: + +```python +from pyasic import settings + +settings.update("default_antminer_password", "my_pwd") +``` + +Here are of all the settings, and their default values: +``` +"network_ping_retries": 1, +"network_ping_timeout": 3, +"network_scan_threads": 300, +"factory_get_retries": 1, +"get_data_retries": 1, +"default_whatsminer_password": "admin", +"default_innosilicon_password": "admin", +"default_antminer_password": "root", +"default_bosminer_password": "root", +"default_vnish_password": "admin", +"default_goldshell_password": "123456789", +``` diff --git a/docs/miners/miner_factory.md b/docs/miners/miner_factory.md index b47a4266..d3ae33e3 100644 --- a/docs/miners/miner_factory.md +++ b/docs/miners/miner_factory.md @@ -1,6 +1,14 @@ # pyasic ## Miner Factory +[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there. + +The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`. + +[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`. + +Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`. + ::: pyasic.miners.miner_factory.MinerFactory handler: python options: diff --git a/docs/settings/settings.md b/docs/settings/settings.md new file mode 100644 index 00000000..058b263a --- /dev/null +++ b/docs/settings/settings.md @@ -0,0 +1,23 @@ +# pyasic +## settings + +::: pyasic.settings + handler: python + options: + show_root_heading: false + heading_level: 4 + + +### get +::: pyasic.settings.get + handler: python + options: + show_root_heading: false + heading_level: 4 + +### update +::: pyasic.settings.update + handler: python + options: + show_root_heading: false + heading_level: 4 diff --git a/mkdocs.yml b/mkdocs.yml index f44f65a9..e0c8273f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,8 @@ nav: - Goldshell X5: "miners/goldshell/X5.md" - Goldshell XMax: "miners/goldshell/XMax.md" - Base Miner: "miners/base_miner.md" - +- Settings: + - Settings: "settings/settings.md" plugins: - mkdocstrings diff --git a/pyasic/__init__.py b/pyasic/__init__.py index 3803375e..14b24259 100644 --- a/pyasic/__init__.py +++ b/pyasic/__init__.py @@ -30,7 +30,7 @@ from pyasic.data import ( from pyasic.errors import APIError, APIWarning from pyasic.miners import get_miner from pyasic.miners.base import AnyMiner -from pyasic.miners.miner_factory import MinerFactory +from pyasic.miners.miner_factory import MinerFactory, miner_factory from pyasic.miners.miner_listener import MinerListener from pyasic.network import MinerNetwork @@ -51,6 +51,7 @@ __all__ = [ "get_miner", "AnyMiner", "MinerFactory", + "miner_factory", "MinerListener", "MinerNetwork", "settings", diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 31f343b1..1f311aaf 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -20,7 +20,7 @@ import enum import ipaddress import json import re -from typing import Callable, List, Optional, Tuple, Union +from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union import httpx @@ -379,7 +379,9 @@ class MinerFactory: def clear_cached_miners(self): self.cache = {} - async def get_multiple_miners(self, ips: List[str], limit: int = 200): + async def get_multiple_miners( + self, ips: List[str], limit: int = 200 + ) -> List[AnyMiner]: results = [] async for miner in self.get_miner_generator(ips, limit): @@ -387,7 +389,7 @@ class MinerFactory: return results - async def get_miner_generator(self, ips: list, limit: int = 200): + async def get_miner_generator(self, ips: list, limit: int = 200) -> AsyncGenerator: tasks = [] semaphore = asyncio.Semaphore(limit) @@ -395,13 +397,10 @@ class MinerFactory: tasks.append(asyncio.create_task(self.get_miner(ip))) for task in tasks: - await semaphore.acquire() - try: + async with semaphore: result = await task if result is not None: yield result - finally: - semaphore.release() async def get_miner(self, ip: str): ip = str(ip) From b50dd26e6f2a3df2b355af27930620f837a88ef7 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 22 Sep 2023 09:32:59 -0600 Subject: [PATCH 14/27] feature: refactor BOS web class into multiple classes. --- pyasic/web/bosminer.py | 144 ++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index c82bf8d8..1cf754ce 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -24,8 +24,20 @@ from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: + self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) + self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) + self._pwd = settings.get("default_bosminer_password", "root") super().__init__(ip) - self.pwd = settings.get("default_bosminer_password", "root") + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + self.gql.pwd = other async def send_command( self, @@ -34,44 +46,10 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if isinstance(command, str): - return await self.send_luci_command(command) + if command.startswith("/cgi-bin/luci"): + return await self.luci.send_command(command) else: - return await self.send_gql_command(command) - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def send_gql_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}/graphql" - query = command - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient() as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass + return await self.gql.send_command(command) async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True @@ -79,13 +57,13 @@ class BOSMinerWebAPI(BaseWebAPI): luci_commands = [] gql_commands = [] for cmd in commands: + if cmd.startswith("/cgi-bin/luci"): + luci_commands.append(cmd) if isinstance(cmd, dict): gql_commands.append(cmd) - if isinstance(cmd, str): - luci_commands.append(cmd) - luci_data = await self.luci_multicommand(*luci_commands) - gql_data = await self.gql_multicommand(*gql_commands) + luci_data = await self.luci.multicommand(*luci_commands) + gql_data = await self.gql.multicommand(*gql_commands) if gql_data is None: gql_data = {} @@ -95,13 +73,14 @@ class BOSMinerWebAPI(BaseWebAPI): data = dict(**luci_data, **gql_data) return data - async def luci_multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_luci_command(command, ignore_errors=True) - return data - async def gql_multicommand(self, *commands: dict) -> dict: +class BOSMinerGQLAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: dict) -> dict: def merge(*d: dict): ret = {} for i in d: @@ -122,7 +101,7 @@ class BOSMinerWebAPI(BaseWebAPI): # noinspection PyTypeChecker commands.remove({"bos": {"faultLight": None}}) command = merge(*commands) - data = await self.send_gql_command(command) + data = await self.send_command(command) except (LookupError, ValueError): pass if not data: @@ -130,6 +109,40 @@ class BOSMinerWebAPI(BaseWebAPI): data["multicommand"] = False return data + async def send_command( + self, + command: dict, + ) -> dict: + url = f"http://{self.ip}/graphql" + query = command + if command.get("query") is None: + query = {"query": self.parse_command(command)} + try: + async with httpx.AsyncClient() as client: + await self.auth(client) + data = await client.post(url, json=query) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" + async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" await client.post( @@ -143,10 +156,23 @@ class BOSMinerWebAPI(BaseWebAPI): }, ) - async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: + +class BOSMinerLuCIAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def send_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.luci_auth(client) + await self.auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -162,7 +188,7 @@ class BOSMinerWebAPI(BaseWebAPI): return {} raise APIError(f"Web command failed: path={path}") - async def luci_auth(self, session: httpx.AsyncClient): + async def auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -172,23 +198,21 @@ class BOSMinerWebAPI(BaseWebAPI): await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_luci_command( - "/cgi-bin/luci/admin/network/iface_status/lan" - ) + return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") async def get_cfg_metadata(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_luci_command("/cgi-bin/luci/bos/info") + return await self.send_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_luci_command( + return await self.send_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") + return await self.send_command("/cgi-bin/luci/admin/miner/api_status") From c16bc37aff0314985f8b43026ea6c7bd6a7d0f2d Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 13:13:31 -0600 Subject: [PATCH 15/27] refactor: improve settings handling to not use a dataclass, and not use singleton. --- pyasic/web/bosminer.py | 192 ++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 108 deletions(-) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index 1cf754ce..c82bf8d8 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -24,20 +24,8 @@ from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: - self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) - self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) - self._pwd = settings.get("default_bosminer_password", "root") super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - self.gql.pwd = other + self.pwd = settings.get("default_bosminer_password", "root") async def send_command( self, @@ -46,70 +34,25 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if command.startswith("/cgi-bin/luci"): - return await self.luci.send_command(command) + if isinstance(command, str): + return await self.send_luci_command(command) else: - return await self.gql.send_command(command) + return await self.send_gql_command(command) - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - luci_commands = [] - gql_commands = [] - for cmd in commands: - if cmd.startswith("/cgi-bin/luci"): - luci_commands.append(cmd) - if isinstance(cmd, dict): - gql_commands.append(cmd) + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" - luci_data = await self.luci.multicommand(*luci_commands) - gql_data = await self.gql.multicommand(*gql_commands) - - if gql_data is None: - gql_data = {} - if luci_data is None: - luci_data = {} - - data = dict(**luci_data, **gql_data) - return data - - -class BOSMinerGQLAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if not k in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def send_command( + async def send_gql_command( self, command: dict, ) -> dict: @@ -130,18 +73,62 @@ class BOSMinerGQLAPI: except json.decoder.JSONDecodeError: pass - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + luci_commands = [] + gql_commands = [] + for cmd in commands: + if isinstance(cmd, dict): + gql_commands.append(cmd) + if isinstance(cmd, str): + luci_commands.append(cmd) + + luci_data = await self.luci_multicommand(*luci_commands) + gql_data = await self.gql_multicommand(*gql_commands) + + if gql_data is None: + gql_data = {} + if luci_data is None: + luci_data = {} + + data = dict(**luci_data, **gql_data) + return data + + async def luci_multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_luci_command(command, ignore_errors=True) + return data + + async def gql_multicommand(self, *commands: dict) -> dict: + def merge(*d: dict): + ret = {} + for i in d: + if i: + for k in i: + if not k in ret: + ret[k] = i[k] + else: + ret[k] = merge(ret[k], i[k]) + return None if ret == {} else ret + + command = merge(*commands) + data = await self.send_command(command) + if data is not None: + if data.get("data") is None: + try: + commands = list(commands) + # noinspection PyTypeChecker + commands.remove({"bos": {"faultLight": None}}) + command = merge(*commands) + data = await self.send_gql_command(command) + except (LookupError, ValueError): + pass + if not data: + data = {} + data["multicommand"] = False + return data async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" @@ -156,23 +143,10 @@ class BOSMinerGQLAPI: }, ) - -class BOSMinerLuCIAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_command(command, ignore_errors=True) - return data - - async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.auth(client) + await self.luci_auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -188,7 +162,7 @@ class BOSMinerLuCIAPI: return {} raise APIError(f"Web command failed: path={path}") - async def auth(self, session: httpx.AsyncClient): + async def luci_auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -198,21 +172,23 @@ class BOSMinerLuCIAPI: await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") + return await self.send_luci_command( + "/cgi-bin/luci/admin/network/iface_status/lan" + ) async def get_cfg_metadata(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_command("/cgi-bin/luci/bos/info") + return await self.send_luci_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_command( + return await self.send_luci_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_command("/cgi-bin/luci/admin/miner/api_status") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") From eae433d2bdee3b47e5cf6ab5cd692eac33d90d4e Mon Sep 17 00:00:00 2001 From: UpstreamData <75442874+UpstreamData@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:17:45 -0600 Subject: [PATCH 16/27] bug: update `get_miner` to work with latest whatsminer version. --- pyasic/miners/miner_factory.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 1f311aaf..4f02a46d 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -755,6 +755,7 @@ class MinerFactory: sock_json_data = await self.send_api_command(ip, "devdetails") try: miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "") + miner_model = miner_model[:-1] + "0" return miner_model except (TypeError, LookupError): From da11c0bb1fd36c875b4e05ac6fa755f66a6c3dd6 Mon Sep 17 00:00:00 2001 From: UpstreamData <75442874+UpstreamData@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:18:10 -0600 Subject: [PATCH 17/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a223e0fc..f5476427 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.39.1" +version = "0.39.2" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From b7d7b33ab968805037e77ad3364d209ba7599c6f Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Tue, 10 Oct 2023 13:59:28 -0600 Subject: [PATCH 18/27] bug: round hashrate data in MinerData, and remove some unused imports. --- pyasic/API/__init__.py | 2 +- pyasic/data/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyasic/API/__init__.py b/pyasic/API/__init__.py index d79af860..97d8049a 100644 --- a/pyasic/API/__init__.py +++ b/pyasic/API/__init__.py @@ -20,7 +20,7 @@ import json import logging import re import warnings -from typing import Tuple, Union +from typing import Union from pyasic.errors import APIError, APIWarning diff --git a/pyasic/data/__init__.py b/pyasic/data/__init__.py index a3657467..ce97144b 100644 --- a/pyasic/data/__init__.py +++ b/pyasic/data/__init__.py @@ -241,7 +241,7 @@ class MinerData: if item.hashrate is not None: hr_data.append(item.hashrate) if len(hr_data) > 0: - return sum(hr_data) + return round(sum(hr_data), 2) return self._hashrate @hashrate.setter From 7f472f6f4f9d9ca5f123531f64a85ebf20a688b6 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 12 Oct 2023 13:19:11 -0600 Subject: [PATCH 19/27] bug: fix possible missing value for bitmain work mode when checking `is_mining`. --- pyasic/miners/backends/antminer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index 43fb1668..6921caed 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -274,7 +274,11 @@ class AntminerModern(BMMiner): if web_get_conf: try: - return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True + if web_get_conf["bitmain-work-mode"].isdigit(): + return ( + False if int(web_get_conf["bitmain-work-mode"]) == 1 else True + ) + return False except LookupError: pass From 6e0b9a0a7b0d50d3180114f7fd45077b81369522 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 12 Oct 2023 13:19:38 -0600 Subject: [PATCH 20/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f5476427..6e73ace2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.39.2" +version = "0.39.3" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 9fbbef9b18414cdd574ee5eca4846649f7ebe43e Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 23 Oct 2023 12:59:52 -0600 Subject: [PATCH 21/27] bug: fix an issue with bosminer not responding correctly on older models with fans. --- pyasic/miners/backends/bosminer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyasic/miners/backends/bosminer.py b/pyasic/miners/backends/bosminer.py index b13fbd6f..be85132e 100644 --- a/pyasic/miners/backends/bosminer.py +++ b/pyasic/miners/backends/bosminer.py @@ -787,7 +787,7 @@ class BOSMiner(BaseMiner): ) except APIError: pass - if graphql_fans: + if graphql_fans.get("data"): fans = [] for n in range(self.fan_count): try: From 683fcb2138a08c34fb6a0a1a72a657ac5cc81a68 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 23 Oct 2023 13:01:44 -0600 Subject: [PATCH 22/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6e73ace2..26c09951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.39.3" +version = "0.39.4" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From aac1be056597aa7b112bd351a7169f4df115e521 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 22 Sep 2023 09:32:59 -0600 Subject: [PATCH 23/27] feature: refactor BOS web class into multiple classes. --- pyasic/web/bosminer.py | 147 ++++++++++++++++++++++++----------------- 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index c82bf8d8..bb1d40f0 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -18,14 +18,27 @@ from typing import Union import httpx -from pyasic import APIError, settings +from pyasic import APIError +from pyasic.settings import PyasicSettings from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: + self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) + self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) + self._pwd = PyasicSettings().global_bosminer_password super().__init__(ip) - self.pwd = settings.get("default_bosminer_password", "root") + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + self.gql.pwd = other async def send_command( self, @@ -34,44 +47,10 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if isinstance(command, str): - return await self.send_luci_command(command) + if command.startswith("/cgi-bin/luci"): + return await self.luci.send_command(command) else: - return await self.send_gql_command(command) - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def send_gql_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}/graphql" - query = command - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient() as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass + return await self.gql.send_command(command) async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True @@ -79,13 +58,13 @@ class BOSMinerWebAPI(BaseWebAPI): luci_commands = [] gql_commands = [] for cmd in commands: + if cmd.startswith("/cgi-bin/luci"): + luci_commands.append(cmd) if isinstance(cmd, dict): gql_commands.append(cmd) - if isinstance(cmd, str): - luci_commands.append(cmd) - luci_data = await self.luci_multicommand(*luci_commands) - gql_data = await self.gql_multicommand(*gql_commands) + luci_data = await self.luci.multicommand(*luci_commands) + gql_data = await self.gql.multicommand(*gql_commands) if gql_data is None: gql_data = {} @@ -95,13 +74,14 @@ class BOSMinerWebAPI(BaseWebAPI): data = dict(**luci_data, **gql_data) return data - async def luci_multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_luci_command(command, ignore_errors=True) - return data - async def gql_multicommand(self, *commands: dict) -> dict: +class BOSMinerGQLAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: dict) -> dict: def merge(*d: dict): ret = {} for i in d: @@ -122,7 +102,7 @@ class BOSMinerWebAPI(BaseWebAPI): # noinspection PyTypeChecker commands.remove({"bos": {"faultLight": None}}) command = merge(*commands) - data = await self.send_gql_command(command) + data = await self.send_command(command) except (LookupError, ValueError): pass if not data: @@ -130,6 +110,40 @@ class BOSMinerWebAPI(BaseWebAPI): data["multicommand"] = False return data + async def send_command( + self, + command: dict, + ) -> dict: + url = f"http://{self.ip}/graphql" + query = command + if command.get("query") is None: + query = {"query": self.parse_command(command)} + try: + async with httpx.AsyncClient() as client: + await self.auth(client) + data = await client.post(url, json=query) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" + async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" await client.post( @@ -143,10 +157,23 @@ class BOSMinerWebAPI(BaseWebAPI): }, ) - async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: + +class BOSMinerLuCIAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def send_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.luci_auth(client) + await self.auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -162,7 +189,7 @@ class BOSMinerWebAPI(BaseWebAPI): return {} raise APIError(f"Web command failed: path={path}") - async def luci_auth(self, session: httpx.AsyncClient): + async def auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -172,23 +199,21 @@ class BOSMinerWebAPI(BaseWebAPI): await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_luci_command( - "/cgi-bin/luci/admin/network/iface_status/lan" - ) + return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") async def get_cfg_metadata(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_luci_command("/cgi-bin/luci/bos/info") + return await self.send_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_luci_command( + return await self.send_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") + return await self.send_command("/cgi-bin/luci/admin/miner/api_status") From e39a6921d065f2449279c16038712f5a5bc30e8a Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 2 Oct 2023 13:13:31 -0600 Subject: [PATCH 24/27] refactor: improve settings handling to not use a dataclass, and not use singleton. --- pyasic/web/bosminer.py | 195 ++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 110 deletions(-) diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py index bb1d40f0..c82bf8d8 100644 --- a/pyasic/web/bosminer.py +++ b/pyasic/web/bosminer.py @@ -18,27 +18,14 @@ from typing import Union import httpx -from pyasic import APIError -from pyasic.settings import PyasicSettings +from pyasic import APIError, settings from pyasic.web import BaseWebAPI class BOSMinerWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: - self.gql = BOSMinerGQLAPI(ip, PyasicSettings().global_bosminer_password) - self.luci = BOSMinerLuCIAPI(ip, PyasicSettings().global_bosminer_password) - self._pwd = PyasicSettings().global_bosminer_password super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - self.gql.pwd = other + self.pwd = settings.get("default_bosminer_password", "root") async def send_command( self, @@ -47,70 +34,25 @@ class BOSMinerWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - if command.startswith("/cgi-bin/luci"): - return await self.luci.send_command(command) + if isinstance(command, str): + return await self.send_luci_command(command) else: - return await self.gql.send_command(command) + return await self.send_gql_command(command) - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - luci_commands = [] - gql_commands = [] - for cmd in commands: - if cmd.startswith("/cgi-bin/luci"): - luci_commands.append(cmd) - if isinstance(cmd, dict): - gql_commands.append(cmd) + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" - luci_data = await self.luci.multicommand(*luci_commands) - gql_data = await self.gql.multicommand(*gql_commands) - - if gql_data is None: - gql_data = {} - if luci_data is None: - luci_data = {} - - data = dict(**luci_data, **gql_data) - return data - - -class BOSMinerGQLAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if not k in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def send_command( + async def send_gql_command( self, command: dict, ) -> dict: @@ -131,18 +73,62 @@ class BOSMinerGQLAPI: except json.decoder.JSONDecodeError: pass - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + luci_commands = [] + gql_commands = [] + for cmd in commands: + if isinstance(cmd, dict): + gql_commands.append(cmd) + if isinstance(cmd, str): + luci_commands.append(cmd) + + luci_data = await self.luci_multicommand(*luci_commands) + gql_data = await self.gql_multicommand(*gql_commands) + + if gql_data is None: + gql_data = {} + if luci_data is None: + luci_data = {} + + data = dict(**luci_data, **gql_data) + return data + + async def luci_multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_luci_command(command, ignore_errors=True) + return data + + async def gql_multicommand(self, *commands: dict) -> dict: + def merge(*d: dict): + ret = {} + for i in d: + if i: + for k in i: + if not k in ret: + ret[k] = i[k] + else: + ret[k] = merge(ret[k], i[k]) + return None if ret == {} else ret + + command = merge(*commands) + data = await self.send_command(command) + if data is not None: + if data.get("data") is None: + try: + commands = list(commands) + # noinspection PyTypeChecker + commands.remove({"bos": {"faultLight": None}}) + command = merge(*commands) + data = await self.send_gql_command(command) + except (LookupError, ValueError): + pass + if not data: + data = {} + data["multicommand"] = False + return data async def auth(self, client: httpx.AsyncClient) -> None: url = f"http://{self.ip}/graphql" @@ -157,23 +143,10 @@ class BOSMinerGQLAPI: }, ) - -class BOSMinerLuCIAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_command(command, ignore_errors=True) - return data - - async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: try: async with httpx.AsyncClient() as client: - await self.auth(client) + await self.luci_auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} ) @@ -189,7 +162,7 @@ class BOSMinerLuCIAPI: return {} raise APIError(f"Web command failed: path={path}") - async def auth(self, session: httpx.AsyncClient): + async def luci_auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}/cgi-bin/luci" headers = { @@ -199,21 +172,23 @@ class BOSMinerLuCIAPI: await session.post(url, headers=headers, data=login) async def get_net_conf(self): - return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") + return await self.send_luci_command( + "/cgi-bin/luci/admin/network/iface_status/lan" + ) async def get_cfg_metadata(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") async def get_cfg_data(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") async def get_bos_info(self): - return await self.send_command("/cgi-bin/luci/bos/info") + return await self.send_luci_command("/cgi-bin/luci/bos/info") async def get_overview(self): - return await self.send_command( + return await self.send_luci_command( "/cgi-bin/luci/admin/status/overview?status=1" ) # needs status=1 or it fails async def get_api_status(self): - return await self.send_command("/cgi-bin/luci/admin/miner/api_status") + return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") From b0e18ab7665c87c184557226cab9829e33988d06 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 27 Oct 2023 16:35:09 -0600 Subject: [PATCH 25/27] feature: implement most of the GRPC functions for BOS, except for some configuration options which will require complex enums. --- pyasic/web/bosminer.py | 194 ------- pyasic/web/bosminer/__init__.py | 541 ++++++++++++++++++ pyasic/web/bosminer/proto/__init__.py | 52 ++ .../web/bosminer/proto/bos/v1/actions_pb2.py | 56 ++ .../proto/bos/v1/authentication_pb2.py | 36 ++ .../web/bosminer/proto/bos/v1/common_pb2.py | 26 + .../proto/bos/v1/configuration_pb2.py | 40 ++ .../bosminer/proto/bos/v1/constraints_pb2.py | 44 ++ .../web/bosminer/proto/bos/v1/cooling_pb2.py | 56 ++ .../web/bosminer/proto/bos/v1/license_pb2.py | 42 ++ pyasic/web/bosminer/proto/bos/v1/miner_pb2.py | 84 +++ .../bosminer/proto/bos/v1/performance_pb2.py | 112 ++++ pyasic/web/bosminer/proto/bos/v1/pool_pb2.py | 75 +++ pyasic/web/bosminer/proto/bos/v1/units_pb2.py | 61 ++ pyasic/web/bosminer/proto/bos/v1/work_pb2.py | 47 ++ pyasic/web/bosminer/proto/bos/version_pb2.py | 47 ++ pyproject.toml | 2 +- 17 files changed, 1320 insertions(+), 195 deletions(-) delete mode 100644 pyasic/web/bosminer.py create mode 100644 pyasic/web/bosminer/__init__.py create mode 100644 pyasic/web/bosminer/proto/__init__.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/actions_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/authentication_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/common_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/configuration_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/constraints_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/cooling_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/license_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/miner_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/performance_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/pool_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/units_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/v1/work_pb2.py create mode 100644 pyasic/web/bosminer/proto/bos/version_pb2.py diff --git a/pyasic/web/bosminer.py b/pyasic/web/bosminer.py deleted file mode 100644 index c82bf8d8..00000000 --- a/pyasic/web/bosminer.py +++ /dev/null @@ -1,194 +0,0 @@ -# ------------------------------------------------------------------------------ -# 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. - -# ------------------------------------------------------------------------------ -import json -from typing import Union - -import httpx - -from pyasic import APIError, settings -from pyasic.web import BaseWebAPI - - -class BOSMinerWebAPI(BaseWebAPI): - def __init__(self, ip: str) -> None: - super().__init__(ip) - self.pwd = settings.get("default_bosminer_password", "root") - - async def send_command( - self, - command: Union[str, dict], - ignore_errors: bool = False, - allow_warning: bool = True, - **parameters: Union[str, int, bool], - ) -> dict: - if isinstance(command, str): - return await self.send_luci_command(command) - else: - return await self.send_gql_command(command) - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def send_gql_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}/graphql" - query = command - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient() as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass - - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - luci_commands = [] - gql_commands = [] - for cmd in commands: - if isinstance(cmd, dict): - gql_commands.append(cmd) - if isinstance(cmd, str): - luci_commands.append(cmd) - - luci_data = await self.luci_multicommand(*luci_commands) - gql_data = await self.gql_multicommand(*gql_commands) - - if gql_data is None: - gql_data = {} - if luci_data is None: - luci_data = {} - - data = dict(**luci_data, **gql_data) - return data - - async def luci_multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_luci_command(command, ignore_errors=True) - return data - - async def gql_multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if not k in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_gql_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def auth(self, client: httpx.AsyncClient) -> None: - url = f"http://{self.ip}/graphql" - await client.post( - url, - json={ - "query": 'mutation{auth{login(username:"' - + "root" - + '", password:"' - + self.pwd - + '"){__typename}}}' - }, - ) - - async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict: - try: - async with httpx.AsyncClient() as client: - await self.luci_auth(client) - data = await client.get( - f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} - ) - if data.status_code == 200: - return data.json() - if ignore_errors: - return {} - raise APIError( - f"Web command failed: path={path}, code={data.status_code}" - ) - except (httpx.HTTPError, json.JSONDecodeError): - if ignore_errors: - return {} - raise APIError(f"Web command failed: path={path}") - - async def luci_auth(self, session: httpx.AsyncClient): - login = {"luci_username": self.username, "luci_password": self.pwd} - url = f"http://{self.ip}/cgi-bin/luci" - headers = { - "User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set - "Content-Type": "application/x-www-form-urlencoded", - } - await session.post(url, headers=headers, data=login) - - async def get_net_conf(self): - return await self.send_luci_command( - "/cgi-bin/luci/admin/network/iface_status/lan" - ) - - async def get_cfg_metadata(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata") - - async def get_cfg_data(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data") - - async def get_bos_info(self): - return await self.send_luci_command("/cgi-bin/luci/bos/info") - - async def get_overview(self): - return await self.send_luci_command( - "/cgi-bin/luci/admin/status/overview?status=1" - ) # needs status=1 or it fails - - async def get_api_status(self): - return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status") diff --git a/pyasic/web/bosminer/__init__.py b/pyasic/web/bosminer/__init__.py new file mode 100644 index 00000000..aa70a8aa --- /dev/null +++ b/pyasic/web/bosminer/__init__.py @@ -0,0 +1,541 @@ +# ------------------------------------------------------------------------------ +# 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. - +# ------------------------------------------------------------------------------ +import asyncio +import json +import warnings +from datetime import datetime, timedelta +from enum import Enum +from typing import List, Union + +import grpc_requests +import httpx +from grpc import RpcError + +from pyasic import APIError, settings +from pyasic.web import BaseWebAPI +from pyasic.web.bosminer.proto import ( + get_auth_service_descriptors, + get_service_descriptors, +) + + +class SaveAction(Enum): + UNSPECIFIED = "SaveAction.SAVE_ACTION_UNSPECIFIED" + SAVE = "SaveAction.SAVE_ACTION_SAVE" + SAVE_AND_APPLY = "SaveAction.SAVE_ACTION_SAVE_AND_APPLY" + SAVE_AND_FORCE_APPLY = "SaveAction.SAVE_ACTION_SAVE_AND_FORCE_APPLY" + + +class BOSMinerWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + self.gql = BOSMinerGQLAPI(ip, settings.get("default_bosminer_password", "root")) + self.luci = BOSMinerLuCIAPI( + ip, settings.get("default_bosminer_password", "root") + ) + self.grpc = BOSMinerGRPCAPI( + ip, settings.get("default_bosminer_password", "root") + ) + self._pwd = settings.get("default_bosminer_password", "root") + super().__init__(ip) + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + self.gql.pwd = other + + async def send_command( + self, + command: Union[str, dict], + ignore_errors: bool = False, + allow_warning: bool = True, + **parameters: Union[str, int, bool], + ) -> dict: + if isinstance(command, dict): + return await self.gql.send_command(command) + elif command.startswith("/cgi-bin/luci"): + return await self.gql.send_command(command) + + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + luci_commands = [] + gql_commands = [] + for cmd in commands: + if isinstance(cmd, dict): + gql_commands.append(cmd) + elif cmd.startswith("/cgi-bin/luci"): + luci_commands.append(cmd) + + luci_data = await self.luci.multicommand(*luci_commands) + gql_data = await self.gql.multicommand(*gql_commands) + + if gql_data is None: + gql_data = {} + if luci_data is None: + luci_data = {} + + data = dict(**luci_data, **gql_data) + return data + + +class BOSMinerGQLAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: dict) -> dict: + def merge(*d: dict): + ret = {} + for i in d: + if i: + for k in i: + if not k in ret: + ret[k] = i[k] + else: + ret[k] = merge(ret[k], i[k]) + return None if ret == {} else ret + + command = merge(*commands) + data = await self.send_command(command) + if data is not None: + if data.get("data") is None: + try: + commands = list(commands) + # noinspection PyTypeChecker + commands.remove({"bos": {"faultLight": None}}) + command = merge(*commands) + data = await self.send_command(command) + except (LookupError, ValueError): + pass + if not data: + data = {} + data["multicommand"] = False + return data + + async def send_command( + self, + command: dict, + ) -> dict: + url = f"http://{self.ip}/graphql" + query = command + if command.get("query") is None: + query = {"query": self.parse_command(command)} + try: + async with httpx.AsyncClient() as client: + await self.auth(client) + data = await client.post(url, json=query) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" + + async def auth(self, client: httpx.AsyncClient) -> None: + url = f"http://{self.ip}/graphql" + await client.post( + url, + json={ + "query": 'mutation{auth{login(username:"' + + "root" + + '", password:"' + + self.pwd + + '"){__typename}}}' + }, + ) + + +class BOSMinerLuCIAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + try: + async with httpx.AsyncClient() as client: + await self.auth(client) + data = await client.get( + f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} + ) + if data.status_code == 200: + return data.json() + if ignore_errors: + return {} + raise APIError( + f"Web command failed: path={path}, code={data.status_code}" + ) + except (httpx.HTTPError, json.JSONDecodeError): + if ignore_errors: + return {} + raise APIError(f"Web command failed: path={path}") + + async def auth(self, session: httpx.AsyncClient): + login = {"luci_username": self.username, "luci_password": self.pwd} + url = f"http://{self.ip}/cgi-bin/luci" + headers = { + "User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set + "Content-Type": "application/x-www-form-urlencoded", + } + await session.post(url, headers=headers, data=login) + + async def get_net_conf(self): + return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") + + async def get_cfg_metadata(self): + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") + + async def get_cfg_data(self): + return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") + + async def get_bos_info(self): + return await self.send_command("/cgi-bin/luci/bos/info") + + async def get_overview(self): + return await self.send_command( + "/cgi-bin/luci/admin/status/overview?status=1" + ) # needs status=1 or it fails + + async def get_api_status(self): + return await self.send_command("/cgi-bin/luci/admin/miner/api_status") + + +class BOSMinerGRPCAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + self._auth = None + self._auth_time = datetime.now() + + @property + def commands(self) -> list: + return self.get_commands() + + def get_commands(self) -> list: + return [ + func + for func in + # each function in self + dir(self) + if func + not in ["send_command", "multicommand", "auth", "commands", "get_commands"] + if callable(getattr(self, func)) and + # no __ or _ methods + not func.startswith("__") and not func.startswith("_") + ] + + async def multicommand(self, *commands: str) -> dict: + pass + + async def send_command( + self, command: str, ignore_errors: bool = False, auth: bool = True, **parameters + ) -> dict: + service, method = command.split("/") + metadata = [] + if auth: + metadata.append(("authorization", await self.auth())) + async with grpc_requests.StubAsyncClient( + f"{self.ip}:50051", service_descriptors=get_service_descriptors() + ) as client: + await client.register_all_service() + try: + return await client.request( + service, + method, + request=parameters, + metadata=metadata, + ) + except RpcError as e: + if ignore_errors: + return {} + raise APIError(e._details) + + async def auth(self): + if self._auth is not None and self._auth_time - datetime.now() < timedelta( + seconds=3540 + ): + return self._auth + await self._get_auth() + return self._auth + + async def _get_auth(self): + async with grpc_requests.StubAsyncClient( + f"{self.ip}:50051", service_descriptors=get_auth_service_descriptors() + ) as client: + await client.register_all_service() + method_meta = client.get_method_meta( + "braiins.bos.v1.AuthenticationService", "Login" + ) + _request = method_meta.method_type.request_parser( + {"username": self.username, "password": self.pwd}, + method_meta.input_type, + ) + metadata = await method_meta.handler(_request).initial_metadata() + + for key, value in metadata: + if key == "authorization": + self._auth = value + self._auth_time = datetime.now() + return self._auth + + async def get_api_version(self): + return await self.send_command( + "braiins.bos.ApiVersionService/GetApiVersion", auth=False + ) + + async def start(self): + return await self.send_command("braiins.bos.v1.ActionsService/Start") + + async def stop(self): + return await self.send_command("braiins.bos.v1.ActionsService/Stop") + + async def pause_mining(self): + return await self.send_command("braiins.bos.v1.ActionsService/PauseMining") + + async def resume_mining(self): + return await self.send_command("braiins.bos.v1.ActionsService/ResumeMining") + + async def restart(self): + return await self.send_command("braiins.bos.v1.ActionsService/Restart") + + async def reboot(self): + return await self.send_command("braiins.bos.v1.ActionsService/Reboot") + + async def set_locate_device_status(self, enable: bool): + return await self.send_command( + "braiins.bos.v1.ActionsService/SetLocateDeviceStatus", enable=enable + ) + + async def get_locate_device_status(self): + return await self.send_command( + "braiins.bos.v1.ActionsService/GetLocateDeviceStatus" + ) + + async def set_password(self, password: str = None): + kwargs = {} + if password: + kwargs["password"] = password + return await self.send_command( + "braiins.bos.v1.AuthenticationService/SetPassword", **kwargs + ) + + async def get_cooling_state(self): + return await self.send_command("braiins.bos.v1.CoolingService/GetCoolingState") + + async def set_immersion_mode( + self, enable: bool, save_action: SaveAction = SaveAction.SAVE_AND_APPLY + ): + return await self.send_command( + "braiins.bos.v1.CoolingService/SetImmersionMode", + save_action=save_action, + enable_immersion_mode=enable, + ) + + async def get_tuner_state(self): + return await self.send_command( + "braiins.bos.v1.PerformanceService/GetTunerState" + ) + + async def list_target_profiles(self): + return await self.send_command( + "braiins.bos.v1.PerformanceService/ListTargetProfiles" + ) + + async def set_default_power_target( + self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetDefaultPowerTarget", + save_action=save_action, + ) + + async def set_power_target( + self, power_target: int, save_action: SaveAction = SaveAction.SAVE_AND_APPLY + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetPowerTarget", + save_action=save_action, + power_target=power_target, + ) + + async def increment_power_target( + self, + power_target_increment: int, + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/IncrementPowerTarget", + save_action=save_action, + power_target_increment=power_target_increment, + ) + + async def decrement_power_target( + self, + power_target_decrement: int, + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/DecrementPowerTarget", + save_action=save_action, + power_target_decrement=power_target_decrement, + ) + + async def set_default_hashrate_target( + self, save_action: SaveAction = SaveAction.SAVE_AND_APPLY + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetDefaultHashrateTarget", + save_action=save_action, + ) + + async def set_hashrate_target( + self, hashrate_target: int, save_action: SaveAction = SaveAction.SAVE_AND_APPLY + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetHashrateTarget", + save_action=save_action, + hashrate_target=hashrate_target, + ) + + async def increment_hashrate_target( + self, + hashrate_target_increment: int, + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/IncrementHashrateTarget", + save_action=save_action, + hashrate_target_increment=hashrate_target_increment, + ) + + async def decrement_hashrate_target( + self, + hashrate_target_decrement: int, + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.PerformanceService/DecrementHashrateTarget", + save_action=save_action, + hashrate_target_decrement=hashrate_target_decrement, + ) + + async def set_dps(self): + raise NotImplementedError + return await self.send_command("braiins.bos.v1.PerformanceService/SetDPS") + + async def set_performance_mode(self): + raise NotImplementedError + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetPerformanceMode" + ) + + async def get_active_performance_mode(self): + return await self.send_command( + "braiins.bos.v1.PerformanceService/GetActivePerformanceMode" + ) + + async def get_pool_groups(self): + return await self.send_command("braiins.bos.v1.PoolService/GetPoolGroups") + + async def create_pool_group(self): + raise NotImplementedError + return await self.send_command("braiins.bos.v1.PoolService/CreatePoolGroup") + + async def update_pool_group(self): + raise NotImplementedError + return await self.send_command("braiins.bos.v1.PoolService/UpdatePoolGroup") + + async def remove_pool_group(self): + raise NotImplementedError + return await self.send_command("braiins.bos.v1.PoolService/RemovePoolGroup") + + async def get_miner_configuration(self): + return await self.send_command( + "braiins.bos.v1.ConfigurationService/GetMinerConfiguration" + ) + + async def get_constraints(self): + return await self.send_command( + "braiins.bos.v1.ConfigurationService/GetConstraints" + ) + + async def get_license_state(self): + return await self.send_command("braiins.bos.v1.LicenseService/GetLicenseState") + + async def get_miner_status(self): + return await self.send_command("braiins.bos.v1.MinerService/GetMinerStatus") + + async def get_miner_details(self): + return await self.send_command("braiins.bos.v1.MinerService/GetMinerDetails") + + async def get_miner_stats(self): + return await self.send_command("braiins.bos.v1.MinerService/GetMinerStats") + + async def get_hashboards(self): + return await self.send_command("braiins.bos.v1.MinerService/GetHashboards") + + async def get_support_archive(self): + return await self.send_command("braiins.bos.v1.MinerService/GetSupportArchive") + + async def enable_hashboards( + self, + hashboard_ids: List[str], + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.MinerService/EnableHashboards", + hashboard_ids=hashboard_ids, + save_action=save_action, + ) + + async def disable_hashboards( + self, + hashboard_ids: List[str], + save_action: SaveAction = SaveAction.SAVE_AND_APPLY, + ): + return await self.send_command( + "braiins.bos.v1.MinerService/DisableHashboards", + hashboard_ids=hashboard_ids, + save_action=save_action, + ) diff --git a/pyasic/web/bosminer/proto/__init__.py b/pyasic/web/bosminer/proto/__init__.py new file mode 100644 index 00000000..5538f2b5 --- /dev/null +++ b/pyasic/web/bosminer/proto/__init__.py @@ -0,0 +1,52 @@ +# ------------------------------------------------------------------------------ +# 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 .bos import version_pb2 +from .bos.v1 import ( + actions_pb2, + authentication_pb2, + common_pb2, + configuration_pb2, + constraints_pb2, + cooling_pb2, + license_pb2, + miner_pb2, + performance_pb2, + pool_pb2, + units_pb2, + work_pb2, +) + + +def get_service_descriptors(): + return [ + *version_pb2.DESCRIPTOR.services_by_name.values(), + *authentication_pb2.DESCRIPTOR.services_by_name.values(), + *actions_pb2.DESCRIPTOR.services_by_name.values(), + *common_pb2.DESCRIPTOR.services_by_name.values(), + *configuration_pb2.DESCRIPTOR.services_by_name.values(), + *constraints_pb2.DESCRIPTOR.services_by_name.values(), + *cooling_pb2.DESCRIPTOR.services_by_name.values(), + *license_pb2.DESCRIPTOR.services_by_name.values(), + *miner_pb2.DESCRIPTOR.services_by_name.values(), + *performance_pb2.DESCRIPTOR.services_by_name.values(), + *pool_pb2.DESCRIPTOR.services_by_name.values(), + *units_pb2.DESCRIPTOR.services_by_name.values(), + *work_pb2.DESCRIPTOR.services_by_name.values(), + ] + + +def get_auth_service_descriptors(): + return authentication_pb2.DESCRIPTOR.services_by_name.values() diff --git a/pyasic/web/bosminer/proto/bos/v1/actions_pb2.py b/pyasic/web/bosminer/proto/bos/v1/actions_pb2.py new file mode 100644 index 00000000..4c36707d --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/actions_pb2.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/actions.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x14\x62os/v1/actions.proto\x12\x0e\x62raiins.bos.v1"\x0e\n\x0cStartRequest"(\n\rStartResponse\x12\x17\n\x0f\x61lready_running\x18\x01 \x01(\x08"\x10\n\x0eRestartRequest"*\n\x0fRestartResponse\x12\x17\n\x0f\x61lready_running\x18\x01 \x01(\x08"\x0f\n\rRebootRequest"\x10\n\x0eRebootResponse"\r\n\x0bStopRequest"\'\n\x0cStopResponse\x12\x17\n\x0f\x61lready_stopped\x18\x01 \x01(\x08"\x14\n\x12PauseMiningRequest"-\n\x13PauseMiningResponse\x12\x16\n\x0e\x61lready_paused\x18\x01 \x01(\x08"\x15\n\x13ResumeMiningRequest".\n\x14ResumeMiningResponse\x12\x16\n\x0e\x61lready_mining\x18\x01 \x01(\x08".\n\x1cSetLocateDeviceStatusRequest\x12\x0e\n\x06\x65nable\x18\x01 \x01(\x08"-\n\x1aLocateDeviceStatusResponse\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08"\x1e\n\x1cGetLocateDeviceStatusRequest2\xc7\x05\n\x0e\x41\x63tionsService\x12\x44\n\x05Start\x12\x1c.braiins.bos.v1.StartRequest\x1a\x1d.braiins.bos.v1.StartResponse\x12\x41\n\x04Stop\x12\x1b.braiins.bos.v1.StopRequest\x1a\x1c.braiins.bos.v1.StopResponse\x12V\n\x0bPauseMining\x12".braiins.bos.v1.PauseMiningRequest\x1a#.braiins.bos.v1.PauseMiningResponse\x12Y\n\x0cResumeMining\x12#.braiins.bos.v1.ResumeMiningRequest\x1a$.braiins.bos.v1.ResumeMiningResponse\x12J\n\x07Restart\x12\x1e.braiins.bos.v1.RestartRequest\x1a\x1f.braiins.bos.v1.RestartResponse\x12G\n\x06Reboot\x12\x1d.braiins.bos.v1.RebootRequest\x1a\x1e.braiins.bos.v1.RebootResponse\x12q\n\x15SetLocateDeviceStatus\x12,.braiins.bos.v1.SetLocateDeviceStatusRequest\x1a*.braiins.bos.v1.LocateDeviceStatusResponse\x12q\n\x15GetLocateDeviceStatus\x12,.braiins.bos.v1.GetLocateDeviceStatusRequest\x1a*.braiins.bos.v1.LocateDeviceStatusResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.actions_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_STARTREQUEST"]._serialized_start = 40 + _globals["_STARTREQUEST"]._serialized_end = 54 + _globals["_STARTRESPONSE"]._serialized_start = 56 + _globals["_STARTRESPONSE"]._serialized_end = 96 + _globals["_RESTARTREQUEST"]._serialized_start = 98 + _globals["_RESTARTREQUEST"]._serialized_end = 114 + _globals["_RESTARTRESPONSE"]._serialized_start = 116 + _globals["_RESTARTRESPONSE"]._serialized_end = 158 + _globals["_REBOOTREQUEST"]._serialized_start = 160 + _globals["_REBOOTREQUEST"]._serialized_end = 175 + _globals["_REBOOTRESPONSE"]._serialized_start = 177 + _globals["_REBOOTRESPONSE"]._serialized_end = 193 + _globals["_STOPREQUEST"]._serialized_start = 195 + _globals["_STOPREQUEST"]._serialized_end = 208 + _globals["_STOPRESPONSE"]._serialized_start = 210 + _globals["_STOPRESPONSE"]._serialized_end = 249 + _globals["_PAUSEMININGREQUEST"]._serialized_start = 251 + _globals["_PAUSEMININGREQUEST"]._serialized_end = 271 + _globals["_PAUSEMININGRESPONSE"]._serialized_start = 273 + _globals["_PAUSEMININGRESPONSE"]._serialized_end = 318 + _globals["_RESUMEMININGREQUEST"]._serialized_start = 320 + _globals["_RESUMEMININGREQUEST"]._serialized_end = 341 + _globals["_RESUMEMININGRESPONSE"]._serialized_start = 343 + _globals["_RESUMEMININGRESPONSE"]._serialized_end = 389 + _globals["_SETLOCATEDEVICESTATUSREQUEST"]._serialized_start = 391 + _globals["_SETLOCATEDEVICESTATUSREQUEST"]._serialized_end = 437 + _globals["_LOCATEDEVICESTATUSRESPONSE"]._serialized_start = 439 + _globals["_LOCATEDEVICESTATUSRESPONSE"]._serialized_end = 484 + _globals["_GETLOCATEDEVICESTATUSREQUEST"]._serialized_start = 486 + _globals["_GETLOCATEDEVICESTATUSREQUEST"]._serialized_end = 516 + _globals["_ACTIONSSERVICE"]._serialized_start = 519 + _globals["_ACTIONSSERVICE"]._serialized_end = 1230 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/authentication_pb2.py b/pyasic/web/bosminer/proto/bos/v1/authentication_pb2.py new file mode 100644 index 00000000..245a60cd --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/authentication_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/authentication.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x1b\x62os/v1/authentication.proto\x12\x0e\x62raiins.bos.v1"2\n\x0cLoginRequest\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t"\x0f\n\rLoginResponse"8\n\x12SetPasswordRequest\x12\x15\n\x08password\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_password"\x15\n\x13SetPasswordResponse2\xb5\x01\n\x15\x41uthenticationService\x12\x44\n\x05Login\x12\x1c.braiins.bos.v1.LoginRequest\x1a\x1d.braiins.bos.v1.LoginResponse\x12V\n\x0bSetPassword\x12".braiins.bos.v1.SetPasswordRequest\x1a#.braiins.bos.v1.SetPasswordResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, "bos.v1.authentication_pb2", _globals +) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_LOGINREQUEST"]._serialized_start = 47 + _globals["_LOGINREQUEST"]._serialized_end = 97 + _globals["_LOGINRESPONSE"]._serialized_start = 99 + _globals["_LOGINRESPONSE"]._serialized_end = 114 + _globals["_SETPASSWORDREQUEST"]._serialized_start = 116 + _globals["_SETPASSWORDREQUEST"]._serialized_end = 172 + _globals["_SETPASSWORDRESPONSE"]._serialized_start = 174 + _globals["_SETPASSWORDRESPONSE"]._serialized_end = 195 + _globals["_AUTHENTICATIONSERVICE"]._serialized_start = 198 + _globals["_AUTHENTICATIONSERVICE"]._serialized_end = 379 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/common_pb2.py b/pyasic/web/bosminer/proto/bos/v1/common_pb2.py new file mode 100644 index 00000000..9dd91256 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/common_pb2.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/common.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b"\n\x13\x62os/v1/common.proto\x12\x0e\x62raiins.bos.v1*\x85\x01\n\nSaveAction\x12\x1b\n\x17SAVE_ACTION_UNSPECIFIED\x10\x00\x12\x14\n\x10SAVE_ACTION_SAVE\x10\x01\x12\x1e\n\x1aSAVE_ACTION_SAVE_AND_APPLY\x10\x02\x12$\n SAVE_ACTION_SAVE_AND_FORCE_APPLY\x10\x03\x62\x06proto3" +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.common_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_SAVEACTION"]._serialized_start = 40 + _globals["_SAVEACTION"]._serialized_end = 173 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/configuration_pb2.py b/pyasic/web/bosminer/proto/bos/v1/configuration_pb2.py new file mode 100644 index 00000000..f44c4dd8 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/configuration_pb2.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/configuration.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import cooling_pb2 as bos_dot_v1_dot_cooling__pb2 +from ...bos.v1 import performance_pb2 as bos_dot_v1_dot_performance__pb2 +from ...bos.v1 import pool_pb2 as bos_dot_v1_dot_pool__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x1a\x62os/v1/configuration.proto\x12\x0e\x62raiins.bos.v1\x1a\x14\x62os/v1/cooling.proto\x1a\x18\x62os/v1/performance.proto\x1a\x11\x62os/v1/pool.proto"\x1e\n\x1cGetMinerConfigurationRequest"\xc6\x02\n\x1dGetMinerConfigurationResponse\x12;\n\x0bpool_groups\x18\x01 \x03(\x0b\x32&.braiins.bos.v1.PoolGroupConfiguration\x12\x39\n\x0btemperature\x18\x02 \x01(\x0b\x32$.braiins.bos.v1.CoolingConfiguration\x12\x31\n\x05tuner\x18\x03 \x01(\x0b\x32".braiins.bos.v1.TunerConfiguration\x12-\n\x03\x64ps\x18\x04 \x01(\x0b\x32 .braiins.bos.v1.DPSConfiguration\x12K\n\x10hashboard_config\x18\x05 \x01(\x0b\x32\x31.braiins.bos.v1.HashboardPerformanceConfiguration"\x17\n\x15GetConstraintsRequest"\x95\x02\n\x16GetConstraintsResponse\x12;\n\x11tuner_constraints\x18\x01 \x01(\x0b\x32 .braiins.bos.v1.TunerConstraints\x12?\n\x13\x63ooling_constraints\x18\x02 \x01(\x0b\x32".braiins.bos.v1.CoolingConstraints\x12\x37\n\x0f\x64ps_constraints\x18\x03 \x01(\x0b\x32\x1e.braiins.bos.v1.DPSConstraints\x12\x44\n\x16hashboards_constraints\x18\x04 \x01(\x0b\x32$.braiins.bos.v1.HashboardConstraints2\xed\x01\n\x14\x43onfigurationService\x12t\n\x15GetMinerConfiguration\x12,.braiins.bos.v1.GetMinerConfigurationRequest\x1a-.braiins.bos.v1.GetMinerConfigurationResponse\x12_\n\x0eGetConstraints\x12%.braiins.bos.v1.GetConstraintsRequest\x1a&.braiins.bos.v1.GetConstraintsResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, "bos.v1.configuration_pb2", _globals +) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_GETMINERCONFIGURATIONREQUEST"]._serialized_start = 113 + _globals["_GETMINERCONFIGURATIONREQUEST"]._serialized_end = 143 + _globals["_GETMINERCONFIGURATIONRESPONSE"]._serialized_start = 146 + _globals["_GETMINERCONFIGURATIONRESPONSE"]._serialized_end = 472 + _globals["_GETCONSTRAINTSREQUEST"]._serialized_start = 474 + _globals["_GETCONSTRAINTSREQUEST"]._serialized_end = 497 + _globals["_GETCONSTRAINTSRESPONSE"]._serialized_start = 500 + _globals["_GETCONSTRAINTSRESPONSE"]._serialized_end = 777 + _globals["_CONFIGURATIONSERVICE"]._serialized_start = 780 + _globals["_CONFIGURATIONSERVICE"]._serialized_end = 1017 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/constraints_pb2.py b/pyasic/web/bosminer/proto/bos/v1/constraints_pb2.py new file mode 100644 index 00000000..58ee8800 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/constraints_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/constraints.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x18\x62os/v1/constraints.proto\x12\x0e\x62raiins.bos.v1\x1a\x12\x62os/v1/units.proto">\n\x11UInt32Constraints\x12\x0f\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\r\x12\x0b\n\x03min\x18\x02 \x01(\r\x12\x0b\n\x03max\x18\x03 \x01(\r">\n\x11\x44oubleConstraints\x12\x0f\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x01\x12\x0b\n\x03min\x18\x02 \x01(\x01\x12\x0b\n\x03max\x18\x03 \x01(\x01"\x82\x01\n\x10PowerConstraints\x12&\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12"\n\x03min\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12"\n\x03max\x18\x03 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x9a\x01\n\x13HashrateConstraints\x12-\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12)\n\x03min\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12)\n\x03max\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x9a\x01\n\x16TemperatureConstraints\x12,\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12(\n\x03min\x18\x02 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12(\n\x03max\x18\x03 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature"$\n\x11\x42ooleanConstraint\x12\x0f\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x08"\x85\x01\n\x13\x44urationConstraints\x12&\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Hours\x12"\n\x03min\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Hours\x12"\n\x03max\x18\x03 \x01(\x0b\x32\x15.braiins.bos.v1.Hours"\x92\x01\n\x14\x46requencyConstraints\x12*\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12&\n\x03min\x18\x02 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12&\n\x03max\x18\x03 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency"\x8a\x01\n\x12VoltageConstraints\x12(\n\x07\x64\x65\x66\x61ult\x18\x01 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage\x12$\n\x03min\x18\x02 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage\x12$\n\x03max\x18\x03 \x01(\x0b\x32\x17.braiins.bos.v1.Voltageb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.constraints_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_UINT32CONSTRAINTS"]._serialized_start = 64 + _globals["_UINT32CONSTRAINTS"]._serialized_end = 126 + _globals["_DOUBLECONSTRAINTS"]._serialized_start = 128 + _globals["_DOUBLECONSTRAINTS"]._serialized_end = 190 + _globals["_POWERCONSTRAINTS"]._serialized_start = 193 + _globals["_POWERCONSTRAINTS"]._serialized_end = 323 + _globals["_HASHRATECONSTRAINTS"]._serialized_start = 326 + _globals["_HASHRATECONSTRAINTS"]._serialized_end = 480 + _globals["_TEMPERATURECONSTRAINTS"]._serialized_start = 483 + _globals["_TEMPERATURECONSTRAINTS"]._serialized_end = 637 + _globals["_BOOLEANCONSTRAINT"]._serialized_start = 639 + _globals["_BOOLEANCONSTRAINT"]._serialized_end = 675 + _globals["_DURATIONCONSTRAINTS"]._serialized_start = 678 + _globals["_DURATIONCONSTRAINTS"]._serialized_end = 811 + _globals["_FREQUENCYCONSTRAINTS"]._serialized_start = 814 + _globals["_FREQUENCYCONSTRAINTS"]._serialized_end = 960 + _globals["_VOLTAGECONSTRAINTS"]._serialized_start = 963 + _globals["_VOLTAGECONSTRAINTS"]._serialized_end = 1101 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/cooling_pb2.py b/pyasic/web/bosminer/proto/bos/v1/cooling_pb2.py new file mode 100644 index 00000000..d72f2ea0 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/cooling_pb2.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/cooling.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import common_pb2 as bos_dot_v1_dot_common__pb2 +from ...bos.v1 import constraints_pb2 as bos_dot_v1_dot_constraints__pb2 +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x14\x62os/v1/cooling.proto\x12\x0e\x62raiins.bos.v1\x1a\x13\x62os/v1/common.proto\x1a\x18\x62os/v1/constraints.proto\x1a\x12\x62os/v1/units.proto"\xbc\x01\n\x0f\x43oolingAutoMode\x12\x37\n\x12target_temperature\x18\x01 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12\x34\n\x0fhot_temperature\x18\x02 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12:\n\x15\x64\x61ngerous_temperature\x18\x03 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature"\xb7\x01\n\x11\x43oolingManualMode\x12\x1c\n\x0f\x66\x61n_speed_ratio\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12\x34\n\x0fhot_temperature\x18\x02 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12:\n\x15\x64\x61ngerous_temperature\x18\x03 \x01(\x0b\x32\x1b.braiins.bos.v1.TemperatureB\x12\n\x10_fan_speed_ratio"G\n\x13\x43oolingDisabledMode\x12\x1c\n\x0f\x66\x61n_speed_ratio\x18\x01 \x01(\x01H\x00\x88\x01\x01\x42\x12\n\x10_fan_speed_ratio"\xfb\x01\n\x14\x43oolingConfiguration\x12"\n\x15minimum_required_fans\x18\x01 \x01(\rH\x01\x88\x01\x01\x12/\n\x04\x61uto\x18\x02 \x01(\x0b\x32\x1f.braiins.bos.v1.CoolingAutoModeH\x00\x12\x33\n\x06manual\x18\x03 \x01(\x0b\x32!.braiins.bos.v1.CoolingManualModeH\x00\x12\x37\n\x08\x64isabled\x18\x04 \x01(\x0b\x32#.braiins.bos.v1.CoolingDisabledModeH\x00\x42\x06\n\x04modeB\x18\n\x16_minimum_required_fans"\x99\x03\n\x12\x43oolingConstraints\x12\x39\n\x14\x64\x65\x66\x61ult_cooling_mode\x18\x01 \x01(\x0e\x32\x1b.braiins.bos.v1.CoolingMode\x12\x42\n\x12target_temperature\x18\x02 \x01(\x0b\x32&.braiins.bos.v1.TemperatureConstraints\x12?\n\x0fhot_temperature\x18\x03 \x01(\x0b\x32&.braiins.bos.v1.TemperatureConstraints\x12\x45\n\x15\x64\x61ngerous_temperature\x18\x04 \x01(\x0b\x32&.braiins.bos.v1.TemperatureConstraints\x12:\n\x0f\x66\x61n_speed_ratio\x18\x05 \x01(\x0b\x32!.braiins.bos.v1.DoubleConstraints\x12@\n\x15minimum_required_fans\x18\x06 \x01(\x0b\x32!.braiins.bos.v1.UInt32Constraints"s\n\x08\x46\x61nState\x12\x15\n\x08position\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x0b\n\x03rpm\x18\x02 \x01(\r\x12\x1f\n\x12target_speed_ratio\x18\x03 \x01(\x01H\x01\x88\x01\x01\x42\x0b\n\t_positionB\x15\n\x13_target_speed_ratio"\x8f\x01\n\x11TemperatureSensor\x12\x0f\n\x02id\x18\x01 \x01(\rH\x00\x88\x01\x01\x12\x30\n\x08location\x18\x02 \x01(\x0e\x32\x1e.braiins.bos.v1.SensorLocation\x12\x30\n\x0btemperature\x18\x03 \x01(\x0b\x32\x1b.braiins.bos.v1.TemperatureB\x05\n\x03_id"\x18\n\x16GetCoolingStateRequest"\x81\x01\n\x17GetCoolingStateResponse\x12&\n\x04\x66\x61ns\x18\x01 \x03(\x0b\x32\x18.braiins.bos.v1.FanState\x12>\n\x13highest_temperature\x18\x02 \x01(\x0b\x32!.braiins.bos.v1.TemperatureSensor"i\n\x17SetImmersionModeRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x1d\n\x15\x65nable_immersion_mode\x18\x02 \x01(\x08"2\n\x18SetImmersionModeResponse\x12\x16\n\x0eimmersion_mode\x18\x01 \x01(\x08*v\n\x0b\x43oolingMode\x12\x1c\n\x18\x43OOLING_MODE_UNSPECIFIED\x10\x00\x12\x15\n\x11\x43OOLING_MODE_AUTO\x10\x01\x12\x17\n\x13\x43OOLING_MODE_MANUAL\x10\x02\x12\x19\n\x15\x43OOLING_MODE_DISABLED\x10\x03*d\n\x0eSensorLocation\x12\x1f\n\x1bSENSOR_LOCATION_UNSPECIFIED\x10\x00\x12\x18\n\x14SENSOR_LOCATION_CHIP\x10\x01\x12\x17\n\x13SENSOR_LOCATION_PCB\x10\x02\x32\xdb\x01\n\x0e\x43oolingService\x12\x62\n\x0fGetCoolingState\x12&.braiins.bos.v1.GetCoolingStateRequest\x1a\'.braiins.bos.v1.GetCoolingStateResponse\x12\x65\n\x10SetImmersionMode\x12\'.braiins.bos.v1.SetImmersionModeRequest\x1a(.braiins.bos.v1.SetImmersionModeResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.cooling_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_COOLINGMODE"]._serialized_start = 1803 + _globals["_COOLINGMODE"]._serialized_end = 1921 + _globals["_SENSORLOCATION"]._serialized_start = 1923 + _globals["_SENSORLOCATION"]._serialized_end = 2023 + _globals["_COOLINGAUTOMODE"]._serialized_start = 108 + _globals["_COOLINGAUTOMODE"]._serialized_end = 296 + _globals["_COOLINGMANUALMODE"]._serialized_start = 299 + _globals["_COOLINGMANUALMODE"]._serialized_end = 482 + _globals["_COOLINGDISABLEDMODE"]._serialized_start = 484 + _globals["_COOLINGDISABLEDMODE"]._serialized_end = 555 + _globals["_COOLINGCONFIGURATION"]._serialized_start = 558 + _globals["_COOLINGCONFIGURATION"]._serialized_end = 809 + _globals["_COOLINGCONSTRAINTS"]._serialized_start = 812 + _globals["_COOLINGCONSTRAINTS"]._serialized_end = 1221 + _globals["_FANSTATE"]._serialized_start = 1223 + _globals["_FANSTATE"]._serialized_end = 1338 + _globals["_TEMPERATURESENSOR"]._serialized_start = 1341 + _globals["_TEMPERATURESENSOR"]._serialized_end = 1484 + _globals["_GETCOOLINGSTATEREQUEST"]._serialized_start = 1486 + _globals["_GETCOOLINGSTATEREQUEST"]._serialized_end = 1510 + _globals["_GETCOOLINGSTATERESPONSE"]._serialized_start = 1513 + _globals["_GETCOOLINGSTATERESPONSE"]._serialized_end = 1642 + _globals["_SETIMMERSIONMODEREQUEST"]._serialized_start = 1644 + _globals["_SETIMMERSIONMODEREQUEST"]._serialized_end = 1749 + _globals["_SETIMMERSIONMODERESPONSE"]._serialized_start = 1751 + _globals["_SETIMMERSIONMODERESPONSE"]._serialized_end = 1801 + _globals["_COOLINGSERVICE"]._serialized_start = 2026 + _globals["_COOLINGSERVICE"]._serialized_end = 2245 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/license_pb2.py b/pyasic/web/bosminer/proto/bos/v1/license_pb2.py new file mode 100644 index 00000000..f70f9ae1 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/license_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/license.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x14\x62os/v1/license.proto\x12\x0e\x62raiins.bos.v1\x1a\x12\x62os/v1/units.proto")\n\x0bNoneLicense\x12\x1a\n\x12time_to_restricted\x18\x01 \x01(\r"\x10\n\x0eLimitedLicense"\x9a\x01\n\x0cValidLicense\x12)\n\x04type\x18\x01 \x01(\x0e\x32\x1b.braiins.bos.v1.LicenseType\x12\x15\n\rcontract_name\x18\x02 \x01(\t\x12\x1a\n\x12time_to_restricted\x18\x03 \x01(\r\x12,\n\x07\x64\x65v_fee\x18\x04 \x01(\x0b\x32\x1b.braiins.bos.v1.BasesPoints"\x80\x01\n\x0e\x45xpiredLicense\x12)\n\x04type\x18\x01 \x01(\x0e\x32\x1b.braiins.bos.v1.LicenseType\x12\x15\n\rcontract_name\x18\x02 \x01(\t\x12,\n\x07\x64\x65v_fee\x18\x03 \x01(\x0b\x32\x1b.braiins.bos.v1.BasesPoints"\x18\n\x16GetLicenseStateRequest"\xe4\x01\n\x17GetLicenseStateResponse\x12+\n\x04none\x18\x01 \x01(\x0b\x32\x1b.braiins.bos.v1.NoneLicenseH\x00\x12\x31\n\x07limited\x18\x02 \x01(\x0b\x32\x1e.braiins.bos.v1.LimitedLicenseH\x00\x12-\n\x05valid\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.ValidLicenseH\x00\x12\x31\n\x07\x65xpired\x18\x04 \x01(\x0b\x32\x1e.braiins.bos.v1.ExpiredLicenseH\x00\x42\x07\n\x05state*_\n\x0bLicenseType\x12\x1c\n\x18LICENSE_TYPE_UNSPECIFIED\x10\x00\x12\x19\n\x15LICENSE_TYPE_STANDARD\x10\x01\x12\x17\n\x13LICENSE_TYPE_CUSTOM\x10\x02\x32t\n\x0eLicenseService\x12\x62\n\x0fGetLicenseState\x12&.braiins.bos.v1.GetLicenseStateRequest\x1a\'.braiins.bos.v1.GetLicenseStateResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.license_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_LICENSETYPE"]._serialized_start = 666 + _globals["_LICENSETYPE"]._serialized_end = 761 + _globals["_NONELICENSE"]._serialized_start = 60 + _globals["_NONELICENSE"]._serialized_end = 101 + _globals["_LIMITEDLICENSE"]._serialized_start = 103 + _globals["_LIMITEDLICENSE"]._serialized_end = 119 + _globals["_VALIDLICENSE"]._serialized_start = 122 + _globals["_VALIDLICENSE"]._serialized_end = 276 + _globals["_EXPIREDLICENSE"]._serialized_start = 279 + _globals["_EXPIREDLICENSE"]._serialized_end = 407 + _globals["_GETLICENSESTATEREQUEST"]._serialized_start = 409 + _globals["_GETLICENSESTATEREQUEST"]._serialized_end = 433 + _globals["_GETLICENSESTATERESPONSE"]._serialized_start = 436 + _globals["_GETLICENSESTATERESPONSE"]._serialized_end = 664 + _globals["_LICENSESERVICE"]._serialized_start = 763 + _globals["_LICENSESERVICE"]._serialized_end = 879 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/miner_pb2.py b/pyasic/web/bosminer/proto/bos/v1/miner_pb2.py new file mode 100644 index 00000000..314b07bd --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/miner_pb2.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/miner.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 + +from ...bos.v1 import common_pb2 as bos_dot_v1_dot_common__pb2 +from ...bos.v1 import cooling_pb2 as bos_dot_v1_dot_cooling__pb2 +from ...bos.v1 import pool_pb2 as bos_dot_v1_dot_pool__pb2 +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 +from ...bos.v1 import work_pb2 as bos_dot_v1_dot_work__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x12\x62os/v1/miner.proto\x12\x0e\x62raiins.bos.v1\x1a\x13\x62os/v1/common.proto\x1a\x14\x62os/v1/cooling.proto\x1a\x11\x62os/v1/pool.proto\x1a\x12\x62os/v1/units.proto\x1a\x11\x62os/v1/work.proto\x1a\x1egoogle/protobuf/wrappers.proto"s\n\rMinerIdentity\x12)\n\x05\x62rand\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.MinerBrand\x12)\n\x05model\x18\x02 \x01(\x0e\x32\x1a.braiins.bos.v1.MinerModel\x12\x0c\n\x04name\x18\x03 \x01(\t">\n\nBosVersion\x12\x0f\n\x07\x63urrent\x18\x01 \x01(\t\x12\r\n\x05major\x18\x02 \x01(\t\x12\x10\n\x08\x62os_plus\x18\x03 \x01(\x08"\x17\n\x15GetMinerStatusRequest"E\n\x16GetMinerStatusResponse\x12+\n\x06status\x18\x01 \x01(\x0e\x32\x1b.braiins.bos.v1.MinerStatus"\x18\n\x16GetMinerDetailsRequest"\xdb\x02\n\x17GetMinerDetailsResponse\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\x35\n\x0eminer_identity\x18\x02 \x01(\x0b\x32\x1d.braiins.bos.v1.MinerIdentity\x12*\n\x08platform\x18\x03 \x01(\x0e\x32\x18.braiins.bos.v1.Platform\x12)\n\x08\x62os_mode\x18\x04 \x01(\x0e\x32\x17.braiins.bos.v1.BosMode\x12/\n\x0b\x62os_version\x18\x05 \x01(\x0b\x32\x1a.braiins.bos.v1.BosVersion\x12\x10\n\x08hostname\x18\x06 \x01(\t\x12\x13\n\x0bmac_address\x18\x07 \x01(\t\x12\x15\n\rsystem_uptime\x18\x08 \x01(\x04\x12\x36\n\x10sticker_hashrate\x18\t \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate"\x7f\n\x0fMinerPowerStats\x12\x37\n\x18\x61pproximated_consumption\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12\x33\n\nefficiency\x18\x02 \x01(\x0b\x32\x1f.braiins.bos.v1.PowerEfficiency"\x16\n\x14GetMinerStatsRequest"\xb2\x01\n\x15GetMinerStatsResponse\x12-\n\npool_stats\x18\x01 \x01(\x0b\x32\x19.braiins.bos.v1.PoolStats\x12\x34\n\x0bminer_stats\x18\x02 \x01(\x0b\x32\x1f.braiins.bos.v1.WorkSolverStats\x12\x34\n\x0bpower_stats\x18\x03 \x01(\x0b\x32\x1f.braiins.bos.v1.MinerPowerStats"\xe2\x02\n\tHashboard\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x02 \x01(\x08\x12\x31\n\x0b\x63hips_count\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x30\n\x0f\x63urrent_voltage\x18\x04 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage\x12\x34\n\x11\x63urrent_frequency\x18\x05 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12<\n\x11highest_chip_temp\x18\x06 \x01(\x0b\x32!.braiins.bos.v1.TemperatureSensor\x12/\n\nboard_temp\x18\x07 \x01(\x0b\x32\x1b.braiins.bos.v1.Temperature\x12.\n\x05stats\x18\x08 \x01(\x0b\x32\x1f.braiins.bos.v1.WorkSolverStats"P\n\x18GetSupportArchiveRequest\x12\x34\n\x06\x66ormat\x18\x01 \x01(\x0e\x32$.braiins.bos.v1.SupportArchiveFormat"/\n\x19GetSupportArchiveResponse\x12\x12\n\nchunk_data\x18\x01 \x01(\x0c"\x16\n\x14GetHashboardsRequest"F\n\x15GetHashboardsResponse\x12-\n\nhashboards\x18\x01 \x03(\x0b\x32\x19.braiins.bos.v1.Hashboard"a\n\x17\x45nableHashboardsRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x15\n\rhashboard_ids\x18\x02 \x03(\t"T\n\x18\x45nableHashboardsResponse\x12\x38\n\nhashboards\x18\x01 \x03(\x0b\x32$.braiins.bos.v1.HashboardEnableState"b\n\x18\x44isableHashboardsRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x15\n\rhashboard_ids\x18\x02 \x03(\t"U\n\x19\x44isableHashboardsResponse\x12\x38\n\nhashboards\x18\x01 \x03(\x0b\x32$.braiins.bos.v1.HashboardEnableState"6\n\x14HashboardEnableState\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nis_enabled\x18\x02 \x01(\x08*\xba\x01\n\x08Platform\x12\x18\n\x14PLATFORM_UNSPECIFIED\x10\x00\x12\x13\n\x0fPLATFORM_AM1_S9\x10\x01\x12\x14\n\x10PLATFORM_AM2_S17\x10\x02\x12\x14\n\x10PLATFORM_AM3_BBB\x10\x03\x12\x14\n\x10PLATFORM_AM3_AML\x10\x04\x12 \n\x1cPLATFORM_STM32MP157C_II1_AM2\x10\x05\x12\x1b\n\x17PLATFORM_CVITEK_BM1_AM2\x10\x06*\x87\x01\n\x07\x42osMode\x12\x18\n\x14\x42OS_MODE_UNSPECIFIED\x10\x00\x12\x14\n\x10\x42OS_MODE_UPGRADE\x10\x01\x12\x15\n\x11\x42OS_MODE_RECOVERY\x10\x02\x12\x0f\n\x0b\x42OS_MODE_SD\x10\x03\x12\x11\n\rBOS_MODE_NAND\x10\x04\x12\x11\n\rBOS_MODE_EMMC\x10\x05*_\n\nMinerBrand\x12\x1b\n\x17MINER_BRAND_UNSPECIFIED\x10\x00\x12\x18\n\x14MINER_BRAND_ANTMINER\x10\x01\x12\x1a\n\x16MINER_BRAND_WHATSMINER\x10\x02*\x89\x05\n\nMinerModel\x12\x1b\n\x17MINER_MODEL_UNSPECIFIED\x10\x00\x12\x1b\n\x17MINER_MODEL_ANTMINER_S9\x10\x01\x12\x1c\n\x18MINER_MODEL_ANTMINER_X17\x10\x02\x12\x1c\n\x18MINER_MODEL_ANTMINER_S17\x10\x03\x12!\n\x1dMINER_MODEL_ANTMINER_S17_PLUS\x10\x04\x12 \n\x1cMINER_MODEL_ANTMINER_S17_PRO\x10\x05\x12\x1d\n\x19MINER_MODEL_ANTMINER_S17E\x10\x06\x12\x1c\n\x18MINER_MODEL_ANTMINER_T17\x10\x07\x12\x1d\n\x19MINER_MODEL_ANTMINER_T17E\x10\x08\x12!\n\x1dMINER_MODEL_ANTMINER_T17_PLUS\x10\t\x12\x1c\n\x18MINER_MODEL_ANTMINER_X19\x10\n\x12\x1c\n\x18MINER_MODEL_ANTMINER_S19\x10\x0b\x12 \n\x1cMINER_MODEL_ANTMINER_S19_PRO\x10\x0c\x12!\n\x1dMINER_MODEL_ANTMINER_S19_PLUS\x10\r\x12\x1d\n\x19MINER_MODEL_ANTMINER_S19J\x10\x0e\x12!\n\x1dMINER_MODEL_ANTMINER_S19J_PRO\x10\x0f\x12\x1d\n\x19MINER_MODEL_ANTMINER_S19A\x10\x10\x12!\n\x1dMINER_MODEL_ANTMINER_S19A_PRO\x10\x11\x12\x1e\n\x1aMINER_MODEL_ANTMINER_S19XP\x10\x12\x12\x1c\n\x18MINER_MODEL_ANTMINER_T19\x10\x13*\xb4\x01\n\x0bMinerStatus\x12\x1c\n\x18MINER_STATUS_UNSPECIFIED\x10\x00\x12\x1c\n\x18MINER_STATUS_NOT_STARTED\x10\x01\x12\x17\n\x13MINER_STATUS_NORMAL\x10\x02\x12\x17\n\x13MINER_STATUS_PAUSED\x10\x03\x12\x1a\n\x16MINER_STATUS_SUSPENDED\x10\x04\x12\x1b\n\x17MINER_STATUS_RESTRICTED\x10\x05*~\n\x14SupportArchiveFormat\x12&\n"SUPPORT_ARCHIVE_FORMAT_UNSPECIFIED\x10\x00\x12\x1e\n\x1aSUPPORT_ARCHIVE_FORMAT_ZIP\x10\x01\x12\x1e\n\x1aSUPPORT_ARCHIVE_FORMAT_BOS\x10\x02\x32\xce\x05\n\x0cMinerService\x12\x61\n\x0eGetMinerStatus\x12%.braiins.bos.v1.GetMinerStatusRequest\x1a&.braiins.bos.v1.GetMinerStatusResponse0\x01\x12\x62\n\x0fGetMinerDetails\x12&.braiins.bos.v1.GetMinerDetailsRequest\x1a\'.braiins.bos.v1.GetMinerDetailsResponse\x12\\\n\rGetMinerStats\x12$.braiins.bos.v1.GetMinerStatsRequest\x1a%.braiins.bos.v1.GetMinerStatsResponse\x12\\\n\rGetHashboards\x12$.braiins.bos.v1.GetHashboardsRequest\x1a%.braiins.bos.v1.GetHashboardsResponse\x12j\n\x11GetSupportArchive\x12(.braiins.bos.v1.GetSupportArchiveRequest\x1a).braiins.bos.v1.GetSupportArchiveResponse0\x01\x12\x65\n\x10\x45nableHashboards\x12\'.braiins.bos.v1.EnableHashboardsRequest\x1a(.braiins.bos.v1.EnableHashboardsResponse\x12h\n\x11\x44isableHashboards\x12(.braiins.bos.v1.DisableHashboardsRequest\x1a).braiins.bos.v1.DisableHashboardsResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.miner_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_PLATFORM"]._serialized_start = 2171 + _globals["_PLATFORM"]._serialized_end = 2357 + _globals["_BOSMODE"]._serialized_start = 2360 + _globals["_BOSMODE"]._serialized_end = 2495 + _globals["_MINERBRAND"]._serialized_start = 2497 + _globals["_MINERBRAND"]._serialized_end = 2592 + _globals["_MINERMODEL"]._serialized_start = 2595 + _globals["_MINERMODEL"]._serialized_end = 3244 + _globals["_MINERSTATUS"]._serialized_start = 3247 + _globals["_MINERSTATUS"]._serialized_end = 3427 + _globals["_SUPPORTARCHIVEFORMAT"]._serialized_start = 3429 + _globals["_SUPPORTARCHIVEFORMAT"]._serialized_end = 3555 + _globals["_MINERIDENTITY"]._serialized_start = 171 + _globals["_MINERIDENTITY"]._serialized_end = 286 + _globals["_BOSVERSION"]._serialized_start = 288 + _globals["_BOSVERSION"]._serialized_end = 350 + _globals["_GETMINERSTATUSREQUEST"]._serialized_start = 352 + _globals["_GETMINERSTATUSREQUEST"]._serialized_end = 375 + _globals["_GETMINERSTATUSRESPONSE"]._serialized_start = 377 + _globals["_GETMINERSTATUSRESPONSE"]._serialized_end = 446 + _globals["_GETMINERDETAILSREQUEST"]._serialized_start = 448 + _globals["_GETMINERDETAILSREQUEST"]._serialized_end = 472 + _globals["_GETMINERDETAILSRESPONSE"]._serialized_start = 475 + _globals["_GETMINERDETAILSRESPONSE"]._serialized_end = 822 + _globals["_MINERPOWERSTATS"]._serialized_start = 824 + _globals["_MINERPOWERSTATS"]._serialized_end = 951 + _globals["_GETMINERSTATSREQUEST"]._serialized_start = 953 + _globals["_GETMINERSTATSREQUEST"]._serialized_end = 975 + _globals["_GETMINERSTATSRESPONSE"]._serialized_start = 978 + _globals["_GETMINERSTATSRESPONSE"]._serialized_end = 1156 + _globals["_HASHBOARD"]._serialized_start = 1159 + _globals["_HASHBOARD"]._serialized_end = 1513 + _globals["_GETSUPPORTARCHIVEREQUEST"]._serialized_start = 1515 + _globals["_GETSUPPORTARCHIVEREQUEST"]._serialized_end = 1595 + _globals["_GETSUPPORTARCHIVERESPONSE"]._serialized_start = 1597 + _globals["_GETSUPPORTARCHIVERESPONSE"]._serialized_end = 1644 + _globals["_GETHASHBOARDSREQUEST"]._serialized_start = 1646 + _globals["_GETHASHBOARDSREQUEST"]._serialized_end = 1668 + _globals["_GETHASHBOARDSRESPONSE"]._serialized_start = 1670 + _globals["_GETHASHBOARDSRESPONSE"]._serialized_end = 1740 + _globals["_ENABLEHASHBOARDSREQUEST"]._serialized_start = 1742 + _globals["_ENABLEHASHBOARDSREQUEST"]._serialized_end = 1839 + _globals["_ENABLEHASHBOARDSRESPONSE"]._serialized_start = 1841 + _globals["_ENABLEHASHBOARDSRESPONSE"]._serialized_end = 1925 + _globals["_DISABLEHASHBOARDSREQUEST"]._serialized_start = 1927 + _globals["_DISABLEHASHBOARDSREQUEST"]._serialized_end = 2025 + _globals["_DISABLEHASHBOARDSRESPONSE"]._serialized_start = 2027 + _globals["_DISABLEHASHBOARDSRESPONSE"]._serialized_end = 2112 + _globals["_HASHBOARDENABLESTATE"]._serialized_start = 2114 + _globals["_HASHBOARDENABLESTATE"]._serialized_end = 2168 + _globals["_MINERSERVICE"]._serialized_start = 3558 + _globals["_MINERSERVICE"]._serialized_end = 4276 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/performance_pb2.py b/pyasic/web/bosminer/proto/bos/v1/performance_pb2.py new file mode 100644 index 00000000..02fbc0cd --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/performance_pb2.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/performance.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + +from ...bos.v1 import common_pb2 as bos_dot_v1_dot_common__pb2 +from ...bos.v1 import constraints_pb2 as bos_dot_v1_dot_constraints__pb2 +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x18\x62os/v1/performance.proto\x12\x0e\x62raiins.bos.v1\x1a\x13\x62os/v1/common.proto\x1a\x18\x62os/v1/constraints.proto\x1a\x12\x62os/v1/units.proto\x1a\x1fgoogle/protobuf/timestamp.proto"\xdd\x01\n\x12TunerConfiguration\x12\x14\n\x07\x65nabled\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x32\n\ntuner_mode\x18\x02 \x01(\x0e\x32\x19.braiins.bos.v1.TunerModeH\x01\x88\x01\x01\x12+\n\x0cpower_target\x18\x03 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12\x35\n\x0fhashrate_target\x18\x04 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrateB\n\n\x08_enabledB\r\n\x0b_tuner_mode"\x88\x01\n\x10TunerConstraints\x12\x36\n\x0cpower_target\x18\x01 \x01(\x0b\x32 .braiins.bos.v1.PowerConstraints\x12<\n\x0fhashrate_target\x18\x02 \x01(\x0b\x32#.braiins.bos.v1.HashrateConstraints"\xe6\x02\n\x10\x44PSConfiguration\x12\x14\n\x07\x65nabled\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12)\n\npower_step\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12\x33\n\rhashrate_step\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12/\n\x10min_power_target\x18\x04 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12\x39\n\x13min_hashrate_target\x18\x05 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12\x1d\n\x10shutdown_enabled\x18\x06 \x01(\x08H\x01\x88\x01\x01\x12\x30\n\x11shutdown_duration\x18\x07 \x01(\x0b\x32\x15.braiins.bos.v1.HoursB\n\n\x08_enabledB\x13\n\x11_shutdown_enabled"\xbe\x01\n!HashboardPerformanceConfiguration\x12\x33\n\x10global_frequency\x18\x01 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12/\n\x0eglobal_voltage\x18\x02 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage\x12\x33\n\nhashboards\x18\x03 \x03(\x0b\x32\x1f.braiins.bos.v1.HashboardConfig"\xfd\x02\n\x0e\x44PSConstraints\x12\x34\n\npower_step\x18\x01 \x01(\x0b\x32 .braiins.bos.v1.PowerConstraints\x12:\n\rhashrate_step\x18\x02 \x01(\x0b\x32#.braiins.bos.v1.HashrateConstraints\x12:\n\x10min_power_target\x18\x03 \x01(\x0b\x32 .braiins.bos.v1.PowerConstraints\x12@\n\x13min_hashrate_target\x18\x04 \x01(\x0b\x32#.braiins.bos.v1.HashrateConstraints\x12;\n\x10shutdown_enabled\x18\x05 \x01(\x0b\x32!.braiins.bos.v1.BooleanConstraint\x12>\n\x11shutdown_duration\x18\x06 \x01(\x0b\x32#.braiins.bos.v1.DurationConstraints"\xcf\x01\n\x14HashboardConstraints\x12\x15\n\rhashboard_ids\x18\x01 \x03(\t\x12\x32\n\x07\x65nabled\x18\x02 \x01(\x0b\x32!.braiins.bos.v1.BooleanConstraint\x12\x37\n\tfrequency\x18\x03 \x01(\x0b\x32$.braiins.bos.v1.FrequencyConstraints\x12\x33\n\x07voltage\x18\x04 \x01(\x0b\x32".braiins.bos.v1.VoltageConstraints"\xdd\x01\n\x12PowerTargetProfile\x12+\n\x07\x63reated\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12%\n\x06target\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12\x37\n\x11measured_hashrate\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12:\n\x1b\x65stimated_power_consumption\x18\x04 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\xe7\x01\n\x15HashrateTargetProfile\x12+\n\x07\x63reated\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12,\n\x06target\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12\x37\n\x11measured_hashrate\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12:\n\x1b\x65stimated_power_consumption\x18\x04 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x16\n\x14GetTunerStateRequest"\xf6\x01\n\x15GetTunerStateResponse\x12\x37\n\x13overall_tuner_state\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.TunerState\x12G\n\x17power_target_mode_state\x18\x02 \x01(\x0b\x32$.braiins.bos.v1.PowerTargetModeStateH\x00\x12M\n\x1ahashrate_target_mode_state\x18\x03 \x01(\x0b\x32\'.braiins.bos.v1.HashrateTargetModeStateH\x00\x42\x0c\n\nmode_state"z\n\x14PowerTargetModeState\x12\x33\n\x07profile\x18\x01 \x01(\x0b\x32".braiins.bos.v1.PowerTargetProfile\x12-\n\x0e\x63urrent_target\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x87\x01\n\x17HashrateTargetModeState\x12\x36\n\x07profile\x18\x01 \x01(\x0b\x32%.braiins.bos.v1.HashrateTargetProfile\x12\x34\n\x0e\x63urrent_target\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x1b\n\x19ListTargetProfilesRequest"\xa8\x01\n\x1aListTargetProfilesResponse\x12\x41\n\x15power_target_profiles\x18\x01 \x03(\x0b\x32".braiins.bos.v1.PowerTargetProfile\x12G\n\x18hashrate_target_profiles\x18\x02 \x03(\x0b\x32%.braiins.bos.v1.HashrateTargetProfile"O\n\x1cSetDefaultPowerTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction"u\n\x15SetPowerTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12+\n\x0cpower_target\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x85\x01\n\x1bIncrementPowerTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x35\n\x16power_target_increment\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x85\x01\n\x1b\x44\x65\x63rementPowerTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x35\n\x16power_target_decrement\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power"E\n\x16SetPowerTargetResponse\x12+\n\x0cpower_target\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Power"R\n\x1fSetDefaultHashrateTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction"\x82\x01\n\x18SetHashrateTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x35\n\x0fhashrate_target\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x92\x01\n\x1eIncrementHashrateTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12?\n\x19hashrate_target_increment\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x92\x01\n\x1e\x44\x65\x63rementHashrateTargetRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12?\n\x19hashrate_target_decrement\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"R\n\x19SetHashrateTargetResponse\x12\x35\n\x0fhashrate_target\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"l\n\x0e\x44PSPowerTarget\x12)\n\npower_step\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Power\x12/\n\x10min_power_target\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.Power"\x83\x01\n\x11\x44PSHashrateTarget\x12\x33\n\rhashrate_step\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate\x12\x39\n\x13min_hashrate_target\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x8b\x01\n\tDPSTarget\x12\x36\n\x0cpower_target\x18\x01 \x01(\x0b\x32\x1e.braiins.bos.v1.DPSPowerTargetH\x00\x12<\n\x0fhashrate_target\x18\x02 \x01(\x0b\x32!.braiins.bos.v1.DPSHashrateTargetH\x00\x42\x08\n\x06target"\x8a\x02\n\rSetDPSRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x13\n\x06\x65nable\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x1c\n\x0f\x65nable_shutdown\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12\x35\n\x11shutdown_duration\x18\x04 \x01(\x0b\x32\x15.braiins.bos.v1.HoursH\x02\x88\x01\x01\x12)\n\x06target\x18\x05 \x01(\x0b\x32\x19.braiins.bos.v1.DPSTargetB\t\n\x07_enableB\x12\n\x10_enable_shutdownB\x14\n\x12_shutdown_duration"\xa5\x02\n\x0eSetDPSResponse\x12\x14\n\x07\x65nabled\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x1d\n\x10shutdown_enabled\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x35\n\x11shutdown_duration\x18\x03 \x01(\x0b\x32\x15.braiins.bos.v1.HoursH\x02\x88\x01\x01\x12\x34\n\x0cpower_target\x18\x04 \x01(\x0b\x32\x1e.braiins.bos.v1.DPSPowerTarget\x12:\n\x0fhashrate_target\x18\x05 \x01(\x0b\x32!.braiins.bos.v1.DPSHashrateTargetB\n\n\x08_enabledB\x13\n\x11_shutdown_enabledB\x14\n\x12_shutdown_duration"\x82\x01\n\x1cHashboardPerformanceSettings\x12\n\n\x02id\x18\x01 \x01(\t\x12,\n\tfrequency\x18\x02 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12(\n\x07voltage\x18\x03 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage"\x97\x01\n\x0fHashboardConfig\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x07\x65nabled\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12,\n\tfrequency\x18\x03 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12(\n\x07voltage\x18\x04 \x01(\x0b\x32\x17.braiins.bos.v1.VoltageB\n\n\x08_enabled"\xbf\x01\n\x15ManualPerformanceMode\x12\x33\n\x10global_frequency\x18\x01 \x01(\x0b\x32\x19.braiins.bos.v1.Frequency\x12/\n\x0eglobal_voltage\x18\x02 \x01(\x0b\x32\x17.braiins.bos.v1.Voltage\x12@\n\nhashboards\x18\x03 \x03(\x0b\x32,.braiins.bos.v1.HashboardPerformanceSettings">\n\x0fPowerTargetMode\x12+\n\x0cpower_target\x18\x01 \x01(\x0b\x32\x15.braiins.bos.v1.Power"K\n\x12HashrateTargetMode\x12\x35\n\x0fhashrate_target\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.TeraHashrate"\x98\x01\n\x14TunerPerformanceMode\x12\x37\n\x0cpower_target\x18\x01 \x01(\x0b\x32\x1f.braiins.bos.v1.PowerTargetModeH\x00\x12=\n\x0fhashrate_target\x18\x02 \x01(\x0b\x32".braiins.bos.v1.HashrateTargetModeH\x00\x42\x08\n\x06target"{\n\x19SetPerformanceModeRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12-\n\x04mode\x18\x02 \x01(\x0b\x32\x1f.braiins.bos.v1.PerformanceMode"\x93\x01\n\x0fPerformanceMode\x12<\n\x0bmanual_mode\x18\x01 \x01(\x0b\x32%.braiins.bos.v1.ManualPerformanceModeH\x00\x12:\n\ntuner_mode\x18\x02 \x01(\x0b\x32$.braiins.bos.v1.TunerPerformanceModeH\x00\x42\x06\n\x04mode"\x1b\n\x19GetPerformanceModeRequest*d\n\tTunerMode\x12\x1a\n\x16TUNER_MODE_UNSPECIFIED\x10\x00\x12\x1b\n\x17TUNER_MODE_POWER_TARGET\x10\x01\x12\x1e\n\x1aTUNER_MODE_HASHRATE_TARGET\x10\x02*\x8a\x01\n\nTunerState\x12\x1b\n\x17TUNER_STATE_UNSPECIFIED\x10\x00\x12\x18\n\x14TUNER_STATE_DISABLED\x10\x01\x12\x16\n\x12TUNER_STATE_STABLE\x10\x02\x12\x16\n\x12TUNER_STATE_TUNING\x10\x03\x12\x15\n\x11TUNER_STATE_ERROR\x10\x04\x32\xea\n\n\x12PerformanceService\x12\\\n\rGetTunerState\x12$.braiins.bos.v1.GetTunerStateRequest\x1a%.braiins.bos.v1.GetTunerStateResponse\x12k\n\x12ListTargetProfiles\x12).braiins.bos.v1.ListTargetProfilesRequest\x1a*.braiins.bos.v1.ListTargetProfilesResponse\x12m\n\x15SetDefaultPowerTarget\x12,.braiins.bos.v1.SetDefaultPowerTargetRequest\x1a&.braiins.bos.v1.SetPowerTargetResponse\x12_\n\x0eSetPowerTarget\x12%.braiins.bos.v1.SetPowerTargetRequest\x1a&.braiins.bos.v1.SetPowerTargetResponse\x12k\n\x14IncrementPowerTarget\x12+.braiins.bos.v1.IncrementPowerTargetRequest\x1a&.braiins.bos.v1.SetPowerTargetResponse\x12k\n\x14\x44\x65\x63rementPowerTarget\x12+.braiins.bos.v1.DecrementPowerTargetRequest\x1a&.braiins.bos.v1.SetPowerTargetResponse\x12v\n\x18SetDefaultHashrateTarget\x12/.braiins.bos.v1.SetDefaultHashrateTargetRequest\x1a).braiins.bos.v1.SetHashrateTargetResponse\x12h\n\x11SetHashrateTarget\x12(.braiins.bos.v1.SetHashrateTargetRequest\x1a).braiins.bos.v1.SetHashrateTargetResponse\x12t\n\x17IncrementHashrateTarget\x12..braiins.bos.v1.IncrementHashrateTargetRequest\x1a).braiins.bos.v1.SetHashrateTargetResponse\x12t\n\x17\x44\x65\x63rementHashrateTarget\x12..braiins.bos.v1.DecrementHashrateTargetRequest\x1a).braiins.bos.v1.SetHashrateTargetResponse\x12G\n\x06SetDPS\x12\x1d.braiins.bos.v1.SetDPSRequest\x1a\x1e.braiins.bos.v1.SetDPSResponse\x12`\n\x12SetPerformanceMode\x12).braiins.bos.v1.SetPerformanceModeRequest\x1a\x1f.braiins.bos.v1.PerformanceMode\x12\x66\n\x18GetActivePerformanceMode\x12).braiins.bos.v1.GetPerformanceModeRequest\x1a\x1f.braiins.bos.v1.PerformanceModeb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.performance_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_TUNERMODE"]._serialized_start = 6022 + _globals["_TUNERMODE"]._serialized_end = 6122 + _globals["_TUNERSTATE"]._serialized_start = 6125 + _globals["_TUNERSTATE"]._serialized_end = 6263 + _globals["_TUNERCONFIGURATION"]._serialized_start = 145 + _globals["_TUNERCONFIGURATION"]._serialized_end = 366 + _globals["_TUNERCONSTRAINTS"]._serialized_start = 369 + _globals["_TUNERCONSTRAINTS"]._serialized_end = 505 + _globals["_DPSCONFIGURATION"]._serialized_start = 508 + _globals["_DPSCONFIGURATION"]._serialized_end = 866 + _globals["_HASHBOARDPERFORMANCECONFIGURATION"]._serialized_start = 869 + _globals["_HASHBOARDPERFORMANCECONFIGURATION"]._serialized_end = 1059 + _globals["_DPSCONSTRAINTS"]._serialized_start = 1062 + _globals["_DPSCONSTRAINTS"]._serialized_end = 1443 + _globals["_HASHBOARDCONSTRAINTS"]._serialized_start = 1446 + _globals["_HASHBOARDCONSTRAINTS"]._serialized_end = 1653 + _globals["_POWERTARGETPROFILE"]._serialized_start = 1656 + _globals["_POWERTARGETPROFILE"]._serialized_end = 1877 + _globals["_HASHRATETARGETPROFILE"]._serialized_start = 1880 + _globals["_HASHRATETARGETPROFILE"]._serialized_end = 2111 + _globals["_GETTUNERSTATEREQUEST"]._serialized_start = 2113 + _globals["_GETTUNERSTATEREQUEST"]._serialized_end = 2135 + _globals["_GETTUNERSTATERESPONSE"]._serialized_start = 2138 + _globals["_GETTUNERSTATERESPONSE"]._serialized_end = 2384 + _globals["_POWERTARGETMODESTATE"]._serialized_start = 2386 + _globals["_POWERTARGETMODESTATE"]._serialized_end = 2508 + _globals["_HASHRATETARGETMODESTATE"]._serialized_start = 2511 + _globals["_HASHRATETARGETMODESTATE"]._serialized_end = 2646 + _globals["_LISTTARGETPROFILESREQUEST"]._serialized_start = 2648 + _globals["_LISTTARGETPROFILESREQUEST"]._serialized_end = 2675 + _globals["_LISTTARGETPROFILESRESPONSE"]._serialized_start = 2678 + _globals["_LISTTARGETPROFILESRESPONSE"]._serialized_end = 2846 + _globals["_SETDEFAULTPOWERTARGETREQUEST"]._serialized_start = 2848 + _globals["_SETDEFAULTPOWERTARGETREQUEST"]._serialized_end = 2927 + _globals["_SETPOWERTARGETREQUEST"]._serialized_start = 2929 + _globals["_SETPOWERTARGETREQUEST"]._serialized_end = 3046 + _globals["_INCREMENTPOWERTARGETREQUEST"]._serialized_start = 3049 + _globals["_INCREMENTPOWERTARGETREQUEST"]._serialized_end = 3182 + _globals["_DECREMENTPOWERTARGETREQUEST"]._serialized_start = 3185 + _globals["_DECREMENTPOWERTARGETREQUEST"]._serialized_end = 3318 + _globals["_SETPOWERTARGETRESPONSE"]._serialized_start = 3320 + _globals["_SETPOWERTARGETRESPONSE"]._serialized_end = 3389 + _globals["_SETDEFAULTHASHRATETARGETREQUEST"]._serialized_start = 3391 + _globals["_SETDEFAULTHASHRATETARGETREQUEST"]._serialized_end = 3473 + _globals["_SETHASHRATETARGETREQUEST"]._serialized_start = 3476 + _globals["_SETHASHRATETARGETREQUEST"]._serialized_end = 3606 + _globals["_INCREMENTHASHRATETARGETREQUEST"]._serialized_start = 3609 + _globals["_INCREMENTHASHRATETARGETREQUEST"]._serialized_end = 3755 + _globals["_DECREMENTHASHRATETARGETREQUEST"]._serialized_start = 3758 + _globals["_DECREMENTHASHRATETARGETREQUEST"]._serialized_end = 3904 + _globals["_SETHASHRATETARGETRESPONSE"]._serialized_start = 3906 + _globals["_SETHASHRATETARGETRESPONSE"]._serialized_end = 3988 + _globals["_DPSPOWERTARGET"]._serialized_start = 3990 + _globals["_DPSPOWERTARGET"]._serialized_end = 4098 + _globals["_DPSHASHRATETARGET"]._serialized_start = 4101 + _globals["_DPSHASHRATETARGET"]._serialized_end = 4232 + _globals["_DPSTARGET"]._serialized_start = 4235 + _globals["_DPSTARGET"]._serialized_end = 4374 + _globals["_SETDPSREQUEST"]._serialized_start = 4377 + _globals["_SETDPSREQUEST"]._serialized_end = 4643 + _globals["_SETDPSRESPONSE"]._serialized_start = 4646 + _globals["_SETDPSRESPONSE"]._serialized_end = 4939 + _globals["_HASHBOARDPERFORMANCESETTINGS"]._serialized_start = 4942 + _globals["_HASHBOARDPERFORMANCESETTINGS"]._serialized_end = 5072 + _globals["_HASHBOARDCONFIG"]._serialized_start = 5075 + _globals["_HASHBOARDCONFIG"]._serialized_end = 5226 + _globals["_MANUALPERFORMANCEMODE"]._serialized_start = 5229 + _globals["_MANUALPERFORMANCEMODE"]._serialized_end = 5420 + _globals["_POWERTARGETMODE"]._serialized_start = 5422 + _globals["_POWERTARGETMODE"]._serialized_end = 5484 + _globals["_HASHRATETARGETMODE"]._serialized_start = 5486 + _globals["_HASHRATETARGETMODE"]._serialized_end = 5561 + _globals["_TUNERPERFORMANCEMODE"]._serialized_start = 5564 + _globals["_TUNERPERFORMANCEMODE"]._serialized_end = 5716 + _globals["_SETPERFORMANCEMODEREQUEST"]._serialized_start = 5718 + _globals["_SETPERFORMANCEMODEREQUEST"]._serialized_end = 5841 + _globals["_PERFORMANCEMODE"]._serialized_start = 5844 + _globals["_PERFORMANCEMODE"]._serialized_end = 5991 + _globals["_GETPERFORMANCEMODEREQUEST"]._serialized_start = 5993 + _globals["_GETPERFORMANCEMODEREQUEST"]._serialized_end = 6020 + _globals["_PERFORMANCESERVICE"]._serialized_start = 6266 + _globals["_PERFORMANCESERVICE"]._serialized_end = 7652 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/pool_pb2.py b/pyasic/web/bosminer/proto/bos/v1/pool_pb2.py new file mode 100644 index 00000000..55913f07 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/pool_pb2.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# 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. - +# ------------------------------------------------------------------------------ + +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/pool.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import common_pb2 as bos_dot_v1_dot_common__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x11\x62os/v1/pool.proto\x12\x0e\x62raiins.bos.v1\x1a\x13\x62os/v1/common.proto"\x16\n\x05Quota\x12\r\n\x05value\x18\x01 \x01(\r" \n\x0f\x46ixedShareRatio\x12\r\n\x05value\x18\x01 \x01(\x01"\xe4\x01\n\x16PoolGroupConfiguration\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12&\n\x05quota\x18\x03 \x01(\x0b\x32\x15.braiins.bos.v1.QuotaH\x00\x12<\n\x11\x66ixed_share_ratio\x18\x04 \x01(\x0b\x32\x1f.braiins.bos.v1.FixedShareRatioH\x00\x12\x30\n\x05pools\x18\x05 \x03(\x0b\x32!.braiins.bos.v1.PoolConfigurationB\x17\n\x15load_balance_strategy"\x81\x01\n\x11PoolConfiguration\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0c\n\x04user\x18\x03 \x01(\t\x12\x15\n\x08password\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x65nabled\x18\x05 \x01(\x08H\x01\x88\x01\x01\x42\x0b\n\t_passwordB\n\n\x08_enabled"\xb0\x01\n\tPoolGroup\x12\x0c\n\x04name\x18\x01 \x01(\t\x12&\n\x05quota\x18\x02 \x01(\x0b\x32\x15.braiins.bos.v1.QuotaH\x00\x12<\n\x11\x66ixed_share_ratio\x18\x03 \x01(\x0b\x32\x1f.braiins.bos.v1.FixedShareRatioH\x00\x12#\n\x05pools\x18\x04 \x03(\x0b\x32\x14.braiins.bos.v1.PoolB\n\n\x08strategy"\x88\x01\n\x04Pool\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0c\n\x04user\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\x12\r\n\x05\x61live\x18\x05 \x01(\x08\x12\x0e\n\x06\x61\x63tive\x18\x06 \x01(\x08\x12(\n\x05stats\x18\x07 \x01(\x0b\x32\x19.braiins.bos.v1.PoolStats"\x98\x01\n\tPoolStats\x12\x17\n\x0f\x61\x63\x63\x65pted_shares\x18\x01 \x01(\x04\x12\x17\n\x0frejected_shares\x18\x02 \x01(\x04\x12\x14\n\x0cstale_shares\x18\x03 \x01(\x04\x12\x17\n\x0flast_difficulty\x18\x04 \x01(\x04\x12\x12\n\nbest_share\x18\x05 \x01(\x04\x12\x16\n\x0egenerated_work\x18\x06 \x01(\x04"\x16\n\x14GetPoolGroupsRequest"G\n\x15GetPoolGroupsResponse\x12.\n\x0bpool_groups\x18\x01 \x03(\x0b\x32\x19.braiins.bos.v1.PoolGroup"\x80\x01\n\x16\x43reatePoolGroupRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x35\n\x05group\x18\x02 \x01(\x0b\x32&.braiins.bos.v1.PoolGroupConfiguration"P\n\x17\x43reatePoolGroupResponse\x12\x35\n\x05group\x18\x01 \x01(\x0b\x32&.braiins.bos.v1.PoolGroupConfiguration"\x80\x01\n\x16UpdatePoolGroupRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x35\n\x05group\x18\x02 \x01(\x0b\x32&.braiins.bos.v1.PoolGroupConfiguration"P\n\x17UpdatePoolGroupResponse\x12\x35\n\x05group\x18\x01 \x01(\x0b\x32&.braiins.bos.v1.PoolGroupConfiguration"V\n\x16RemovePoolGroupRequest\x12/\n\x0bsave_action\x18\x01 \x01(\x0e\x32\x1a.braiins.bos.v1.SaveAction\x12\x0b\n\x03uid\x18\x02 \x01(\t"\x19\n\x17RemovePoolGroupResponse2\x97\x03\n\x0bPoolService\x12\\\n\rGetPoolGroups\x12$.braiins.bos.v1.GetPoolGroupsRequest\x1a%.braiins.bos.v1.GetPoolGroupsResponse\x12\x62\n\x0f\x43reatePoolGroup\x12&.braiins.bos.v1.CreatePoolGroupRequest\x1a\'.braiins.bos.v1.CreatePoolGroupResponse\x12\x62\n\x0fUpdatePoolGroup\x12&.braiins.bos.v1.UpdatePoolGroupRequest\x1a\'.braiins.bos.v1.UpdatePoolGroupResponse\x12\x62\n\x0fRemovePoolGroup\x12&.braiins.bos.v1.RemovePoolGroupRequest\x1a\'.braiins.bos.v1.RemovePoolGroupResponseb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.pool_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_QUOTA"]._serialized_start = 58 + _globals["_QUOTA"]._serialized_end = 80 + _globals["_FIXEDSHARERATIO"]._serialized_start = 82 + _globals["_FIXEDSHARERATIO"]._serialized_end = 114 + _globals["_POOLGROUPCONFIGURATION"]._serialized_start = 117 + _globals["_POOLGROUPCONFIGURATION"]._serialized_end = 345 + _globals["_POOLCONFIGURATION"]._serialized_start = 348 + _globals["_POOLCONFIGURATION"]._serialized_end = 477 + _globals["_POOLGROUP"]._serialized_start = 480 + _globals["_POOLGROUP"]._serialized_end = 656 + _globals["_POOL"]._serialized_start = 659 + _globals["_POOL"]._serialized_end = 795 + _globals["_POOLSTATS"]._serialized_start = 798 + _globals["_POOLSTATS"]._serialized_end = 950 + _globals["_GETPOOLGROUPSREQUEST"]._serialized_start = 952 + _globals["_GETPOOLGROUPSREQUEST"]._serialized_end = 974 + _globals["_GETPOOLGROUPSRESPONSE"]._serialized_start = 976 + _globals["_GETPOOLGROUPSRESPONSE"]._serialized_end = 1047 + _globals["_CREATEPOOLGROUPREQUEST"]._serialized_start = 1050 + _globals["_CREATEPOOLGROUPREQUEST"]._serialized_end = 1178 + _globals["_CREATEPOOLGROUPRESPONSE"]._serialized_start = 1180 + _globals["_CREATEPOOLGROUPRESPONSE"]._serialized_end = 1260 + _globals["_UPDATEPOOLGROUPREQUEST"]._serialized_start = 1263 + _globals["_UPDATEPOOLGROUPREQUEST"]._serialized_end = 1391 + _globals["_UPDATEPOOLGROUPRESPONSE"]._serialized_start = 1393 + _globals["_UPDATEPOOLGROUPRESPONSE"]._serialized_end = 1473 + _globals["_REMOVEPOOLGROUPREQUEST"]._serialized_start = 1475 + _globals["_REMOVEPOOLGROUPREQUEST"]._serialized_end = 1561 + _globals["_REMOVEPOOLGROUPRESPONSE"]._serialized_start = 1563 + _globals["_REMOVEPOOLGROUPRESPONSE"]._serialized_end = 1588 + _globals["_POOLSERVICE"]._serialized_start = 1591 + _globals["_POOLSERVICE"]._serialized_end = 1998 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/units_pb2.py b/pyasic/web/bosminer/proto/bos/v1/units_pb2.py new file mode 100644 index 00000000..5ba78648 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/units_pb2.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# 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. - +# ------------------------------------------------------------------------------ + +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/units.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x12\x62os/v1/units.proto\x12\x0e\x62raiins.bos.v1"+\n\x0cMegaHashrate\x12\x1b\n\x13megahash_per_second\x18\x01 \x01(\x01"+\n\x0cGigaHashrate\x12\x1b\n\x13gigahash_per_second\x18\x01 \x01(\x01"+\n\x0cTeraHashrate\x12\x1b\n\x13terahash_per_second\x18\x01 \x01(\x01"\x1a\n\tFrequency\x12\r\n\x05hertz\x18\x01 \x01(\x01"\x17\n\x07Voltage\x12\x0c\n\x04volt\x18\x01 \x01(\x01"\x15\n\x05Power\x12\x0c\n\x04watt\x18\x01 \x01(\x04"-\n\x0fPowerEfficiency\x12\x1a\n\x12joule_per_terahash\x18\x01 \x01(\x01"\x1f\n\x0bTemperature\x12\x10\n\x08\x64\x65gree_c\x18\x01 \x01(\x01"\x1a\n\x0b\x42\x61sesPoints\x12\x0b\n\x03\x62sp\x18\x01 \x01(\r"\x16\n\x05Hours\x12\r\n\x05hours\x18\x01 \x01(\rb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.units_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_MEGAHASHRATE"]._serialized_start = 38 + _globals["_MEGAHASHRATE"]._serialized_end = 81 + _globals["_GIGAHASHRATE"]._serialized_start = 83 + _globals["_GIGAHASHRATE"]._serialized_end = 126 + _globals["_TERAHASHRATE"]._serialized_start = 128 + _globals["_TERAHASHRATE"]._serialized_end = 171 + _globals["_FREQUENCY"]._serialized_start = 173 + _globals["_FREQUENCY"]._serialized_end = 199 + _globals["_VOLTAGE"]._serialized_start = 201 + _globals["_VOLTAGE"]._serialized_end = 224 + _globals["_POWER"]._serialized_start = 226 + _globals["_POWER"]._serialized_end = 247 + _globals["_POWEREFFICIENCY"]._serialized_start = 249 + _globals["_POWEREFFICIENCY"]._serialized_end = 294 + _globals["_TEMPERATURE"]._serialized_start = 296 + _globals["_TEMPERATURE"]._serialized_end = 327 + _globals["_BASESPOINTS"]._serialized_start = 329 + _globals["_BASESPOINTS"]._serialized_end = 355 + _globals["_HOURS"]._serialized_start = 357 + _globals["_HOURS"]._serialized_end = 379 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/v1/work_pb2.py b/pyasic/web/bosminer/proto/bos/v1/work_pb2.py new file mode 100644 index 00000000..9bff6586 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/v1/work_pb2.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# 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. - +# ------------------------------------------------------------------------------ + +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/v1/work.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from ...bos.v1 import units_pb2 as bos_dot_v1_dot_units__pb2 + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x11\x62os/v1/work.proto\x12\x0e\x62raiins.bos.v1\x1a\x12\x62os/v1/units.proto"\xef\x03\n\x0cRealHashrate\x12-\n\x07last_5s\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12.\n\x08last_15s\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12.\n\x08last_30s\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12-\n\x07last_1m\x18\x04 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12-\n\x07last_5m\x18\x05 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12.\n\x08last_15m\x18\x06 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12.\n\x08last_30m\x18\x07 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12-\n\x07last_1h\x18\x08 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12.\n\x08last_24h\x18\t \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12\x33\n\rsince_restart\x18\n \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate"\xde\x01\n\x0fWorkSolverStats\x12\x33\n\rreal_hashrate\x18\x01 \x01(\x0b\x32\x1c.braiins.bos.v1.RealHashrate\x12\x36\n\x10nominal_hashrate\x18\x02 \x01(\x0b\x32\x1c.braiins.bos.v1.GigaHashrate\x12\x34\n\x0e\x65rror_hashrate\x18\x03 \x01(\x0b\x32\x1c.braiins.bos.v1.MegaHashrate\x12\x14\n\x0c\x66ound_blocks\x18\x04 \x01(\r\x12\x12\n\nbest_share\x18\x05 \x01(\x04\x62\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.v1.work_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_REALHASHRATE"]._serialized_start = 58 + _globals["_REALHASHRATE"]._serialized_end = 553 + _globals["_WORKSOLVERSTATS"]._serialized_start = 556 + _globals["_WORKSOLVERSTATS"]._serialized_end = 778 +# @@protoc_insertion_point(module_scope) diff --git a/pyasic/web/bosminer/proto/bos/version_pb2.py b/pyasic/web/bosminer/proto/bos/version_pb2.py new file mode 100644 index 00000000..aed5ad35 --- /dev/null +++ b/pyasic/web/bosminer/proto/bos/version_pb2.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# 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. - +# ------------------------------------------------------------------------------ + +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: bos/version.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x11\x62os/version.proto\x12\x0b\x62raiins.bos"U\n\nApiVersion\x12\r\n\x05major\x18\x01 \x01(\x04\x12\r\n\x05minor\x18\x02 \x01(\x04\x12\r\n\x05patch\x18\x03 \x01(\x04\x12\x0b\n\x03pre\x18\x04 \x01(\t\x12\r\n\x05\x62uild\x18\x05 \x01(\t"\x13\n\x11\x41piVersionRequest2]\n\x11\x41piVersionService\x12H\n\rGetApiVersion\x12\x1e.braiins.bos.ApiVersionRequest\x1a\x17.braiins.bos.ApiVersionb\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "bos.version_pb2", _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals["_APIVERSION"]._serialized_start = 34 + _globals["_APIVERSION"]._serialized_end = 119 + _globals["_APIVERSIONREQUEST"]._serialized_start = 121 + _globals["_APIVERSIONREQUEST"]._serialized_end = 140 + _globals["_APIVERSIONSERVICE"]._serialized_start = 142 + _globals["_APIVERSIONSERVICE"]._serialized_end = 235 +# @@protoc_insertion_point(module_scope) diff --git a/pyproject.toml b/pyproject.toml index 26c09951..291a29e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ httpx = "^0.24.0" passlib = "^1.7.4" pyaml = "^23.5.9" toml = "^0.10.2" -grpc-requests = "^0.1.10" +grpc-requests = "^0.1.11" [tool.poetry.group.dev] optional = true From 697991f28fe7487562c9f00672fdd68e1cf91e54 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 30 Oct 2023 16:33:01 -0600 Subject: [PATCH 26/27] bug: fix some cases where a warning could still be passed when it was unexpected. --- pyasic/API/__init__.py | 27 ++++++++++++++------------- pyasic/miners/base.py | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pyasic/API/__init__.py b/pyasic/API/__init__.py index 97d8049a..c748784e 100644 --- a/pyasic/API/__init__.py +++ b/pyasic/API/__init__.py @@ -83,15 +83,15 @@ class BaseMinerAPI: data = self._load_api_data(data) # check for if the user wants to allow errors to return - if not ignore_errors: - # validate the command succeeded - validation = self._validate_command_output(data) - if not validation[0]: - if allow_warning: - logging.warning( - f"{self.ip}: API Command Error: {command}: {validation[1]}" - ) + validation = self._validate_command_output(data) + if not validation[0]: + if not ignore_errors: + # validate the command succeeded raise APIError(validation[1]) + if allow_warning: + logging.warning( + f"{self.ip}: API Command Error: {command}: {validation[1]}" + ) logging.debug(f"{self} - (Send Command) - Received data.") return data @@ -118,11 +118,12 @@ class BaseMinerAPI: data = await self.send_command(command, allow_warning=allow_warning) except APIError as e: # try to identify the error - if ":" in e.message: - err_command = e.message.split(":")[0] - if err_command in commands: - commands.remove(err_command) - continue + if e.message is not None: + if ":" in e.message: + err_command = e.message.split(":")[0] + if err_command in commands: + commands.remove(err_command) + continue return {command: [{}] for command in commands} logging.debug(f"{self} - (Multicommand) - Received data") data["multicommand"] = True diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index 359b9317..24e5f490 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -541,7 +541,7 @@ class BaseMiner(ABC): ) gathered_data = await self._get_data( - allow_warning, include=include, exclude=exclude + allow_warning=allow_warning, include=include, exclude=exclude ) for item in gathered_data: if gathered_data[item] is not None: From d2f71e8c941b22c5601c91b775fe07933f0ff4fa Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 30 Oct 2023 16:34:05 -0600 Subject: [PATCH 27/27] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 291a29e1..deeed047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.39.4" +version = "0.40.0" description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic"