From 6159a72d460ef87a35611d368f05b4e3cea0a201 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 3 Mar 2023 13:15:22 -0700 Subject: [PATCH] feature: add support for antminer HS3. --- pyasic/miners/hns/__init__.py | 1 + pyasic/miners/hns/_backends/X3.py | 205 ++++++++++++ pyasic/miners/hns/_backends/__init__.py | 2 + pyasic/miners/hns/_backends/cgminer.py | 304 ++++++++++++++++++ pyasic/miners/hns/_types/__init__.py | 1 + pyasic/miners/hns/_types/antminer/X3/HS3.py | 27 ++ .../miners/hns/_types/antminer/X3/__init__.py | 16 + pyasic/miners/hns/_types/antminer/__init__.py | 16 + pyasic/miners/hns/antminer/__init__.py | 16 + pyasic/miners/hns/antminer/cgminer/X3/HS3.py | 22 ++ .../hns/antminer/cgminer/X3/__init__.py | 16 + .../miners/hns/antminer/cgminer/__init__.py | 16 + pyasic/miners/miner_factory.py | 4 + pyasic/web/X3.py | 72 +++++ 14 files changed, 718 insertions(+) create mode 100644 pyasic/miners/hns/_backends/X3.py create mode 100644 pyasic/miners/hns/_backends/cgminer.py create mode 100644 pyasic/miners/hns/_types/antminer/X3/HS3.py create mode 100644 pyasic/miners/hns/_types/antminer/X3/__init__.py create mode 100644 pyasic/miners/hns/_types/antminer/__init__.py create mode 100644 pyasic/miners/hns/antminer/__init__.py create mode 100644 pyasic/miners/hns/antminer/cgminer/X3/HS3.py create mode 100644 pyasic/miners/hns/antminer/cgminer/X3/__init__.py create mode 100644 pyasic/miners/hns/antminer/cgminer/__init__.py create mode 100644 pyasic/web/X3.py diff --git a/pyasic/miners/hns/__init__.py b/pyasic/miners/hns/__init__.py index d6d7eef1..3eb794c3 100644 --- a/pyasic/miners/hns/__init__.py +++ b/pyasic/miners/hns/__init__.py @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from .antminer import * from .goldshell import * diff --git a/pyasic/miners/hns/_backends/X3.py b/pyasic/miners/hns/_backends/X3.py new file mode 100644 index 00000000..1be35471 --- /dev/null +++ b/pyasic/miners/hns/_backends/X3.py @@ -0,0 +1,205 @@ +# ------------------------------------------------------------------------------ +# 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 typing import List, Optional, Union + +from pyasic.config import MinerConfig +from pyasic.data import Fan, HashBoard +from pyasic.errors import APIError +from pyasic.miners.hns._backends import CGMiner # noqa - Ignore access to _module +from pyasic.web.X3 import X3WebAPI + + +class X3(CGMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip, api_ver=api_ver) + self.ip = ip + self.web = X3WebAPI(ip) + + async def get_config(self) -> MinerConfig: + data = await self.web.get_miner_conf() + if data: + self.config = MinerConfig().from_raw(data) + return self.config + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + await self.web.set_miner_conf(config.as_x5(user_suffix=user_suffix)) + + async def get_mac(self) -> Union[str, None]: + try: + data = await self.web.get_system_info() + if data: + return data["macaddr"] + except KeyError: + pass + + async def restart_backend(self) -> bool: + return False + + async def fault_light_on(self) -> bool: + # this should time out, after it does do a check + await self.web.blink(blink=True) + try: + data = await self.web.get_blink_status() + if data: + if data["isBlinking"]: + self.light = True + except KeyError: + pass + return self.light + + async def fault_light_off(self) -> bool: + await self.web.blink(blink=False) + try: + data = await self.web.get_blink_status() + if data: + if not data["isBlinking"]: + self.light = False + except KeyError: + pass + return self.light + + async def reboot(self) -> bool: + data = await self.web.reboot() + if data: + return True + return False + + async def get_fault_light(self, web_get_blink_status: dict = None) -> bool: + if self.light: + return self.light + + if not web_get_blink_status: + try: + web_get_blink_status = await self.web.get_blink_status() + except APIError: + pass + + if web_get_blink_status: + try: + self.light = web_get_blink_status["isBlinking"] + except KeyError: + pass + return self.light + + async def get_hostname(self, web_get_system_info: dict = None) -> Optional[str]: + if not web_get_system_info: + try: + web_get_system_info = await self.web.get_system_info() + except APIError: + pass + + if web_get_system_info: + try: + return web_get_system_info["hostname"] + except KeyError: + pass + + async def get_model(self, web_get_system_info: dict = None) -> Optional[str]: + if self.model: + return self.model + + if not web_get_system_info: + try: + web_get_system_info = await self.web.get_system_info() + except APIError: + pass + + if web_get_system_info: + try: + return web_get_system_info["minertype"] + except APIError: + pass + + async def get_fans(self, api_stats: dict = None) -> List[Fan]: + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + fans_data = [Fan(), Fan(), Fan(), Fan()] + if api_stats: + try: + fan_offset = -1 + + for fan_num in range(1, 8, 4): + for _f_num in range(4): + f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}") + if f and not f == 0 and fan_offset == -1: + fan_offset = fan_num + 2 + if fan_offset == -1: + fan_offset = 3 + + for fan in range(self.fan_count): + fans_data[fan] = Fan( + api_stats["STATS"][1].get(f"fan{fan_offset+fan}") + ) + except (KeyError, IndexError): + pass + return fans_data + + async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + hashboards = [] + + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + if api_stats: + try: + board_offset = -1 + boards = api_stats["STATS"] + if len(boards) > 1: + for board_num in range(1, 16, 5): + for _b_num in range(5): + b = boards[1].get(f"chain_acn{board_num + _b_num}") + + if b and not b == 0 and board_offset == -1: + board_offset = board_num + if board_offset == -1: + board_offset = 1 + + for i in range(board_offset, board_offset + self.ideal_hashboards): + hashboard = HashBoard( + slot=i - board_offset, expected_chips=self.nominal_chips + ) + + chip_temp = boards[1].get(f"temp{i}") + if chip_temp: + hashboard.chip_temp = round(chip_temp) + + temp = boards[1].get(f"temp2_{i}") + if temp: + hashboard.temp = round(temp) + + hashrate = boards[1].get(f"chain_rate{i}") + if hashrate: + hashboard.hashrate = round(float(hashrate) / 1000, 2) + + chips = boards[1].get(f"chain_acn{i}") + if chips: + hashboard.chips = chips + hashboard.missing = False + if (not chips) or (not chips > 0): + hashboard.missing = True + hashboards.append(hashboard) + except (IndexError, KeyError, ValueError, TypeError): + pass + + return hashboards diff --git a/pyasic/miners/hns/_backends/__init__.py b/pyasic/miners/hns/_backends/__init__.py index 45ee0db4..5f268140 100644 --- a/pyasic/miners/hns/_backends/__init__.py +++ b/pyasic/miners/hns/_backends/__init__.py @@ -14,4 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ from .bfgminer import BFGMiner +from .cgminer import CGMiner from .goldshell import Goldshell +from .X3 import X3 diff --git a/pyasic/miners/hns/_backends/cgminer.py b/pyasic/miners/hns/_backends/cgminer.py new file mode 100644 index 00000000..81d76be8 --- /dev/null +++ b/pyasic/miners/hns/_backends/cgminer.py @@ -0,0 +1,304 @@ +# ------------------------------------------------------------------------------ +# 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 ipaddress +import logging +from collections import namedtuple +from typing import List, Optional, Tuple + +from pyasic.API.cgminer import CGMinerAPI +from pyasic.config import MinerConfig +from pyasic.data import Fan, HashBoard +from pyasic.data.error_codes import MinerErrorData +from pyasic.errors import APIError +from pyasic.miners.base import BaseMiner + + +class CGMiner(BaseMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip) + self.ip = ipaddress.ip_address(ip) + self.api = CGMinerAPI(ip, api_ver) + self.api_ver = api_ver + self.api_type = "CGMiner" + self.uname = "root" + self.pwd = "admin" + self.config = None + + async def resume_mining(self) -> bool: + return False + + async def stop_mining(self) -> bool: + return False + + async def get_config(self) -> MinerConfig: + api_pools = await self.api.pools() + + if api_pools: + self.config = MinerConfig().from_api(api_pools["POOLS"]) + return self.config + + async def fault_light_off(self) -> bool: + return False + + async def fault_light_on(self) -> bool: + return False + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + return None + + async def set_power_limit(self, wattage: int) -> bool: + return False + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## + + async def get_mac(self) -> Optional[str]: + return None + + async def get_model(self, api_devdetails: dict = None) -> Optional[str]: + if self.model: + logging.debug(f"Found model for {self.ip}: {self.model}") + return self.model + + if not api_devdetails: + try: + api_devdetails = await self.api.devdetails() + except APIError: + pass + + if api_devdetails: + try: + self.model = api_devdetails["DEVDETAILS"][0]["Model"].replace( + "Antminer ", "" + ) + logging.debug(f"Found model for {self.ip}: {self.model}") + return self.model + except (TypeError, IndexError, KeyError): + pass + + logging.warning(f"Failed to get model for miner: {self}") + return None + + async def get_version( + self, api_version: dict = None + ) -> Tuple[Optional[str], Optional[str]]: + miner_version = namedtuple("MinerVersion", "api_ver fw_ver") + return miner_version( + api_ver=await self.get_api_ver(api_version=api_version), + fw_ver=await self.get_fw_ver(api_version=api_version), + ) + + async def get_api_ver(self, api_version: dict = None) -> Optional[str]: + if self.api_ver: + return self.api_ver + + if not api_version: + try: + api_version = await self.api.version() + except APIError: + pass + + if api_version: + try: + self.api_ver = api_version["VERSION"][0]["API"] + except (KeyError, IndexError): + pass + + return self.api_ver + + async def get_fw_ver(self, api_version: dict = None) -> Optional[str]: + if self.fw_ver: + return self.fw_ver + + if not api_version: + try: + api_version = await self.api.version() + except APIError: + pass + + if api_version: + try: + self.fw_ver = api_version["VERSION"][0]["CGMiner"] + except (KeyError, IndexError): + pass + + return self.fw_ver + + async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + # get hr from API + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + return round( + float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 + ) + except (IndexError, KeyError, ValueError, TypeError): + pass + + async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + hashboards = [] + + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + if api_stats: + try: + board_offset = -1 + boards = api_stats["STATS"] + if len(boards) > 1: + for board_num in range(1, 16, 5): + for _b_num in range(5): + b = boards[1].get(f"chain_acn{board_num + _b_num}") + + if b and not b == 0 and board_offset == -1: + board_offset = board_num + if board_offset == -1: + board_offset = 1 + + for i in range(board_offset, board_offset + self.ideal_hashboards): + hashboard = HashBoard( + slot=i - board_offset, expected_chips=self.nominal_chips + ) + + chip_temp = boards[1].get(f"temp{i}") + if chip_temp: + hashboard.chip_temp = round(chip_temp) + + temp = boards[1].get(f"temp2_{i}") + if temp: + hashboard.temp = round(temp) + + hashrate = boards[1].get(f"chain_rate{i}") + if hashrate: + hashboard.hashrate = round(float(hashrate) / 1000, 2) + + chips = boards[1].get(f"chain_acn{i}") + if chips: + hashboard.chips = chips + hashboard.missing = False + if (not chips) or (not chips > 0): + hashboard.missing = True + hashboards.append(hashboard) + except (IndexError, KeyError, ValueError, TypeError): + pass + + return hashboards + + async def get_env_temp(self) -> Optional[float]: + return None + + async def get_wattage(self) -> Optional[int]: + return None + + async def get_wattage_limit(self) -> Optional[int]: + return None + + async def get_fans(self, api_stats: dict = None) -> List[Fan]: + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + fans_data = [Fan(), Fan(), Fan(), Fan()] + if api_stats: + try: + fan_offset = -1 + + for fan_num in range(1, 8, 4): + for _f_num in range(4): + f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}") + if f and not f == 0 and fan_offset == -1: + fan_offset = fan_num + if fan_offset == -1: + fan_offset = 1 + + for fan in range(self.fan_count): + fans_data[fan] = Fan( + api_stats["STATS"][1].get(f"fan{fan_offset+fan}") + ) + except (KeyError, IndexError): + pass + return fans_data + + async def get_fan_psu(self) -> Optional[int]: + return None + + async def get_pools(self, api_pools: dict = None) -> List[dict]: + groups = [] + + if not api_pools: + try: + api_pools = await self.api.pools() + except APIError: + pass + + if api_pools: + try: + pools = {} + for i, pool in enumerate(api_pools["POOLS"]): + pools[f"pool_{i + 1}_url"] = ( + pool["URL"] + .replace("stratum+tcp://", "") + .replace("stratum2+tcp://", "") + ) + pools[f"pool_{i + 1}_user"] = pool["User"] + pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" + + groups.append(pools) + except KeyError: + pass + return groups + + async def get_errors(self) -> List[MinerErrorData]: + return [] + + async def get_fault_light(self) -> bool: + return False + + async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]: + # X19 method, not sure compatibility + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + if api_stats: + try: + ideal_rate = api_stats["STATS"][1]["total_rateideal"] + try: + rate_unit = api_stats["STATS"][1]["rate_unit"] + except KeyError: + rate_unit = "GH" + if rate_unit == "GH": + return round(ideal_rate / 1000, 2) + if rate_unit == "MH": + return round(ideal_rate / 1000000, 2) + else: + return round(ideal_rate, 2) + except (KeyError, IndexError): + pass diff --git a/pyasic/miners/hns/_types/__init__.py b/pyasic/miners/hns/_types/__init__.py index d6d7eef1..3eb794c3 100644 --- a/pyasic/miners/hns/_types/__init__.py +++ b/pyasic/miners/hns/_types/__init__.py @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from .antminer import * from .goldshell import * diff --git a/pyasic/miners/hns/_types/antminer/X3/HS3.py b/pyasic/miners/hns/_types/antminer/X3/HS3.py new file mode 100644 index 00000000..3685d3a3 --- /dev/null +++ b/pyasic/miners/hns/_types/antminer/X3/HS3.py @@ -0,0 +1,27 @@ +# ------------------------------------------------------------------------------ +# 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 AntMiner + + +class HS3(AntMiner): # noqa - ignore ABC method implementation + def __init__(self, ip: str): + super().__init__() + self.ip = ip + self.model = "HS3" + self.nominal_chips = 72 + self.ideal_hashboards = 3 + self.fan_count = 2 diff --git a/pyasic/miners/hns/_types/antminer/X3/__init__.py b/pyasic/miners/hns/_types/antminer/X3/__init__.py new file mode 100644 index 00000000..5ca92b31 --- /dev/null +++ b/pyasic/miners/hns/_types/antminer/X3/__init__.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------ +# 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 .HS3 import HS3 diff --git a/pyasic/miners/hns/_types/antminer/__init__.py b/pyasic/miners/hns/_types/antminer/__init__.py new file mode 100644 index 00000000..647fb882 --- /dev/null +++ b/pyasic/miners/hns/_types/antminer/__init__.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------ +# 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 .X3 import * diff --git a/pyasic/miners/hns/antminer/__init__.py b/pyasic/miners/hns/antminer/__init__.py new file mode 100644 index 00000000..10eb58cb --- /dev/null +++ b/pyasic/miners/hns/antminer/__init__.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------ +# 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 .cgminer import * diff --git a/pyasic/miners/hns/antminer/cgminer/X3/HS3.py b/pyasic/miners/hns/antminer/cgminer/X3/HS3.py new file mode 100644 index 00000000..1ebc5595 --- /dev/null +++ b/pyasic/miners/hns/antminer/cgminer/X3/HS3.py @@ -0,0 +1,22 @@ +# ------------------------------------------------------------------------------ +# 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.hns._backends import X3 # noqa - Ignore access to _module +from pyasic.miners.hns._types import HS3 # noqa - Ignore access to _module + + +class CGMinerHS3(X3, HS3): + pass diff --git a/pyasic/miners/hns/antminer/cgminer/X3/__init__.py b/pyasic/miners/hns/antminer/cgminer/X3/__init__.py new file mode 100644 index 00000000..6f0bab0f --- /dev/null +++ b/pyasic/miners/hns/antminer/cgminer/X3/__init__.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------ +# 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 .HS3 import CGMinerHS3 diff --git a/pyasic/miners/hns/antminer/cgminer/__init__.py b/pyasic/miners/hns/antminer/cgminer/__init__.py new file mode 100644 index 00000000..647fb882 --- /dev/null +++ b/pyasic/miners/hns/antminer/cgminer/__init__.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------ +# 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 .X3 import * diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 8373e68d..7c0668d0 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -50,6 +50,10 @@ MINER_CLASSES = { "Default": CGMinerDR5, "CGMiner": CGMinerDR5, }, + "ANTMINER HS3": { + "Default": CGMinerHS3, + "CGMiner": CGMinerHS3, + }, "ANTMINER L7": { "Default": BMMinerL7, "BMMiner": BMMinerL7, diff --git a/pyasic/web/X3.py b/pyasic/web/X3.py new file mode 100644 index 00000000..13c4e457 --- /dev/null +++ b/pyasic/web/X3.py @@ -0,0 +1,72 @@ +# ------------------------------------------------------------------------------ +# 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.settings import PyasicSettings +from pyasic.web import BaseWebAPI + + +class X3WebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + super().__init__(ip) + self.pwd = PyasicSettings().global_x5_password + + async def send_command( + self, + command: Union[str, bytes], + ignore_errors: bool = False, + allow_warning: bool = True, + **parameters: Union[str, int, bool], + ) -> dict: + url = f"http://{self.ip}/cgi-bin/{command}.cgi" + auth = httpx.DigestAuth(self.username, self.pwd) + try: + async with httpx.AsyncClient() as client: + if parameters: + data = await client.post(url, data=parameters, auth=auth) + else: + data = await client.get(url, auth=auth) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + async def get_system_info(self) -> dict: + return await self.send_command("get_system_info") + + async def blink(self, blink: bool) -> dict: + if blink: + return await self.send_command("blink", action="startBlink") + return await self.send_command("blink", action="stopBlink") + + async def reboot(self) -> dict: + return await self.send_command("reboot") + + async def get_blink_status(self) -> dict: + return await self.send_command("blink", action="onPageLoaded") + + async def get_miner_conf(self) -> dict: + return await self.send_command("get_miner_conf") + + async def set_miner_conf(self, conf: dict) -> dict: + return await self.send_command("set_miner_conf", **conf)