Compare commits

...

24 Commits

Author SHA1 Message Date
Upstream Data
25d971b699 version: bump version number. 2023-03-01 21:46:03 -07:00
Upstream Data
cd16ef3a25 feature: add Z15 support. 2023-03-01 21:45:37 -07:00
Upstream Data
b70010272f feature: add L7 support. 2023-03-01 20:32:03 -07:00
Upstream Data
140a457445 Merge branch 'dev' 2023-03-01 19:56:27 -07:00
Upstream Data
f4775e6311 bug: better fix for the issue with whatsminer pools. 2023-03-01 19:37:51 -07:00
Upstream Data
a4ecda93a2 bug: Fix an issue with sending None to whatsminers. 2023-03-01 19:34:14 -07:00
Arceris
ba90f2f082 Add VH40 and VH20 chip counts to M50.py 2023-03-01 18:56:41 -07:00
UpstreamData
44ac958bbb version: bump version number. 2023-02-28 16:23:36 -07:00
UpstreamData
e9bcf2ec9f bug: fix an issue with missing a check for bosminer config when getting data. 2023-02-28 16:23:04 -07:00
UpstreamData
c73dfad01a format: move vnish over to the new web API style. 2023-02-28 11:32:42 -07:00
UpstreamData
d222912e30 format: update a bunch of formatting and remove a lot of useless imports. 2023-02-28 09:35:11 -07:00
UpstreamData
6f1c1e0290 format: swap X9 and Innosilicon over to the new web api format. 2023-02-27 16:07:34 -07:00
UpstreamData
ba0bb73aa3 format: swap X17 over to the new web api format. 2023-02-27 15:44:58 -07:00
UpstreamData
13fcf1d4aa format: swap X19 over to the new web api format. 2023-02-27 15:32:02 -07:00
UpstreamData
6be1e94216 version: bump version number. 2023-02-24 12:32:22 -07:00
UpstreamData
709b3efa81 bug: fix mis-identification of X19 on some new versions of stock. 2023-02-24 12:31:55 -07:00
UpstreamData
5ac5770331 version: bump version number. 2023-02-24 08:58:11 -07:00
UpstreamData
f131ebbdf5 bug: fix miner mode needing to be parsed as an int not a str. 2023-02-24 08:57:28 -07:00
UpstreamData
5441e50f73 version: bump version number. 2023-02-24 08:49:29 -07:00
UpstreamData
dc6a952de4 bug: fix backwards modes for X19. 2023-02-24 08:49:13 -07:00
UpstreamData
b781d215fb version: bump version number. 2023-02-24 08:44:20 -07:00
UpstreamData
086b31ba23 feature: add enum for miner mode in config. 2023-02-24 08:43:52 -07:00
UpstreamData
46b7352769 version: bump version number. 2023-02-23 15:39:37 -07:00
UpstreamData
e218c5039d bug: fix wrong LPM mode number for X19. 2023-02-23 15:39:08 -07:00
54 changed files with 1591 additions and 464 deletions

View File

@@ -92,7 +92,9 @@ class BaseMinerAPI:
async def send_privileged_command(self, *args, **kwargs) -> dict: async def send_privileged_command(self, *args, **kwargs) -> dict:
return await self.send_command(*args, **kwargs) return await self.send_command(*args, **kwargs)
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict: async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
) -> dict:
"""Creates and sends multiple commands as one command to the miner. """Creates and sends multiple commands as one command to the miner.
Parameters: Parameters:
@@ -107,7 +109,9 @@ class BaseMinerAPI:
# standard format doesn't work for X19 # standard format doesn't work for X19
command = "+".join(commands) command = "+".join(commands)
try: try:
data = await self.send_command(command, allow_warning=allow_warning) data = await self.send_command(
command, allow_warning=allow_warning, ignore_errors=ignore_errors
)
except APIError: except APIError:
return {command: [{}] for command in commands} return {command: [{}] for command in commands}
logging.debug(f"{self} - (Multicommand) - Received data") logging.debug(f"{self} - (Multicommand) - Received data")

View File

@@ -246,7 +246,7 @@ class BTMinerAPI(BaseMinerAPI):
logging.debug(f"{self} - (Send Privileged Command) - Sending") logging.debug(f"{self} - (Send Privileged Command) - Sending")
try: try:
data = await self._send_bytes(enc_command, timeout) data = await self._send_bytes(enc_command, timeout)
except (asyncio.CancelledError, asyncio.TimeoutError) as e: except (asyncio.CancelledError, asyncio.TimeoutError):
if ignore_errors: if ignore_errors:
return {} return {}
raise APIError("No data was returned from the API.") raise APIError("No data was returned from the API.")

View File

@@ -62,7 +62,7 @@ class CGMinerAPI(BaseMinerAPI):
for cmd in commands: for cmd in commands:
data[cmd] = [] data[cmd] = []
data[cmd].append(await self.send_command(cmd, allow_warning=True)) data[cmd].append(await self.send_command(cmd, allow_warning=True))
except APIError as e: except APIError:
pass pass
except Exception as e: except Exception as e:
logging.warning( logging.warning(

View File

@@ -14,18 +14,24 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import json
import logging import logging
import random import random
import string import string
import time import time
from dataclasses import asdict, dataclass, fields from dataclasses import asdict, dataclass, fields
from typing import Dict, List, Literal from enum import IntEnum
from typing import List, Literal
import toml import toml
import yaml import yaml
class X19PowerMode(IntEnum):
Normal = 0
Sleep = 1
LPM = 3
@dataclass @dataclass
class _Pool: class _Pool:
"""A dataclass for pool information. """A dataclass for pool information.
@@ -85,6 +91,19 @@ class _Pool:
pool = {"url": self.url, "user": username, "pass": self.password} pool = {"url": self.url, "user": username, "pass": self.password}
return pool return pool
def as_x15(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an X19 device.
Parameters:
user_suffix: The suffix to append to username.
"""
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "pass": self.password}
return pool
def as_inno(self, user_suffix: str = None) -> dict: def as_inno(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an Innosilicon device. """Convert the data in this class to a dict usable by an Innosilicon device.
@@ -182,6 +201,34 @@ class _PoolGroup:
pools.append(pool.as_x19(user_suffix=user_suffix)) pools.append(pool.as_x19(user_suffix=user_suffix))
return pools return pools
def as_x15(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a list usable by an X15 device.
Parameters:
user_suffix: The suffix to append to username.
"""
pools = {
"_ant_pool1url": "",
"_ant_pool1user": "",
"_ant_pool1pw": "",
"_ant_pool2url": "",
"_ant_pool2user": "",
"_ant_pool2pw": "",
"_ant_pool3url": "",
"_ant_pool3user": "",
"_ant_pool3pw": "",
}
for idx, pool in enumerate(self.pools[:3]):
pools[f"_ant_pool{idx+1}url"] = pool.as_x15(user_suffix=user_suffix)["url"]
pools[f"_ant_pool{idx+1}user"] = pool.as_x15(user_suffix=user_suffix)[
"user"
]
pools[f"_ant_pool{idx+1}pw"] = pool.as_x15(user_suffix=user_suffix)[
"pass"
]
return pools
def as_inno(self, user_suffix: str = None) -> dict: def as_inno(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a list usable by an Innosilicon device. """Convert the data in this class to a list usable by an Innosilicon device.
@@ -195,17 +242,23 @@ class _PoolGroup:
pools[f"{key}{idx+1}"] = pool_data[key] pools[f"{key}{idx+1}"] = pool_data[key]
return pools return pools
def as_wm(self, user_suffix: str = None) -> List[dict]: def as_wm(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a list usable by a Whatsminer device. """Convert the data in this class to a list usable by a Whatsminer device.
Parameters: Parameters:
user_suffix: The suffix to append to username. user_suffix: The suffix to append to username.
""" """
pools = [] pools = {}
for pool in self.pools[:3]: for i in range(1, 4):
pools.append(pool.as_wm(user_suffix=user_suffix)) if i <= len(self.pools):
while len(pools) < 3: pool_wm = self.pools[i - 1].as_wm(user_suffix)
pools.append({"url": None, "user": None, "pass": None}) pools[f"pool_{i}"] = pool_wm["url"]
pools[f"worker_{i}"] = pool_wm["user"]
pools[f"passwd_{i}"] = pool_wm["pass"]
else:
pools[f"pool_{i}"] = ""
pools[f"worker_{i}"] = ""
pools[f"passwd_{i}"] = ""
return pools return pools
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str = None) -> str:
@@ -267,6 +320,7 @@ class MinerConfig:
asicboost: bool = None asicboost: bool = None
miner_mode: IntEnum = X19PowerMode.Normal
autotuning_enabled: bool = True autotuning_enabled: bool = True
autotuning_mode: Literal["power", "hashrate"] = None autotuning_mode: Literal["power", "hashrate"] = None
autotuning_wattage: int = None autotuning_wattage: int = None
@@ -287,6 +341,8 @@ class MinerConfig:
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config") logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
data_dict = asdict(self) data_dict = asdict(self)
for key in asdict(self).keys(): for key in asdict(self).keys():
if isinstance(data_dict[key], IntEnum):
data_dict[key] = data_dict[key].value
if data_dict[key] is None: if data_dict[key] is None:
del data_dict[key] del data_dict[key]
return data_dict return data_dict
@@ -324,10 +380,7 @@ class MinerConfig:
self.fan_speed = int(data["bitmain-fan-pwm"]) self.fan_speed = int(data["bitmain-fan-pwm"])
elif key == "bitmain-work-mode": elif key == "bitmain-work-mode":
if data[key]: if data[key]:
if data[key] == 1: self.miner_mode = X19PowerMode(int(data[key]))
self.autotuning_wattage = 0
if data[key] == 2:
self.autotuning_wattage = 1200
elif key == "fan_control": elif key == "fan_control":
for _key in data[key].keys(): for _key in data[key].keys():
if _key == "min_fans": if _key == "min_fans":
@@ -427,7 +480,7 @@ class MinerConfig:
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config") logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader)) return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
def as_wm(self, user_suffix: str = None) -> Dict[str, int]: def as_wm(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a config usable by a Whatsminer device. """Convert the data in this class to a config usable by a Whatsminer device.
Parameters: Parameters:
@@ -448,7 +501,7 @@ class MinerConfig:
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config") logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
return self.pool_groups[0].as_inno(user_suffix=user_suffix) return self.pool_groups[0].as_inno(user_suffix=user_suffix)
def as_x19(self, user_suffix: str = None) -> str: def as_x19(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a config usable by an X19 device. """Convert the data in this class to a config usable by an X19 device.
Parameters: Parameters:
@@ -459,14 +512,8 @@ class MinerConfig:
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix), "pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
"bitmain-fan-ctrl": False, "bitmain-fan-ctrl": False,
"bitmain-fan-pwn": 100, "bitmain-fan-pwn": 100,
"miner-mode": 0, # Normal Mode "miner-mode": self.miner_mode.value,
} }
if self.autotuning_wattage:
if self.autotuning_wattage == 0:
cfg["miner-mode"] = 1 # Sleep Mode
if self.autotuning_wattage < 1800:
cfg["miner-mode"] = 2 # LPM?
if not self.temp_mode == "auto": if not self.temp_mode == "auto":
cfg["bitmain-fan-ctrl"] = True cfg["bitmain-fan-ctrl"] = True
@@ -474,7 +521,17 @@ class MinerConfig:
if self.fan_speed: if self.fan_speed:
cfg["bitmain-fan-ctrl"] = str(self.fan_speed) cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
return json.dumps(cfg) return cfg
def as_x15(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a config usable by an X15 device.
Parameters:
user_suffix: The suffix to append to username.
"""
cfg = self.pool_groups[0].as_x15(user_suffix=user_suffix)
return cfg
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str = None) -> str:
"""Convert the data in this class to a config usable by an Avalonminer device. """Convert the data in this class to a config usable by an Avalonminer device.

View File

@@ -429,7 +429,7 @@ class MinerData:
@property @property
def efficiency(self): # noqa - Skip PyCharm inspection def efficiency(self): # noqa - Skip PyCharm inspection
if self.hashrate == 0: if self.hashrate == 0 or self.wattage == -1:
return 0 return 0
return round(self.wattage / self.hashrate) return round(self.wattage / self.hashrate)

View File

@@ -15,17 +15,25 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio import asyncio
import logging
from typing import List, Union from typing import List, Union
# from pyasic.errors import PhaseBalancingError
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners import AnyMiner from pyasic.miners import AnyMiner
from pyasic.miners._backends import X19, BOSMiner, BTMiner from pyasic.miners._backends import ( # noqa - Ignore access to _module
from pyasic.miners._types import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus X19,
BOSMiner,
# from pprint import pprint as print BTMiner,
)
from pyasic.miners._types import ( # noqa - Ignore access to _module
S9,
S17,
T17,
S17e,
S17Plus,
S17Pro,
T17e,
T17Plus,
)
FAN_USAGE = 50 # 50 W per fan FAN_USAGE = 50 # 50 W per fan

View File

@@ -0,0 +1,176 @@
# ------------------------------------------------------------------------------
# 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 Union
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.web.X15 import X15WebAPI
from typing import List
from pyasic.data import Fan, HashBoard
from pyasic.errors import APIError
from pyasic.config import MinerConfig, X19PowerMode
class X15(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 = X15WebAPI(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_x15(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 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) -> bool:
if self.light:
return self.light
try:
data = await self.web.get_blink_status()
if data:
self.light = data["isBlinking"]
except KeyError:
pass
return self.light
async def get_hostname(self) -> Union[str, None]:
try:
data = await self.web.get_system_info()
if data:
return data["hostname"]
except KeyError:
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), 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

View File

@@ -15,47 +15,23 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio import asyncio
import json
from typing import List, Optional, Union from typing import List, Optional, Union
import httpx
from pyasic.API import APIError from pyasic.API import APIError
from pyasic.config import MinerConfig from pyasic.config import MinerConfig, X19PowerMode
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings from pyasic.web.X19 import X19WebAPI
class X19(BMMiner): class X19(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver) super().__init__(ip, api_ver=api_ver)
self.ip = ip self.ip = ip
self.uname = "root" self.web = X19WebAPI(ip)
self.pwd = PyasicSettings().global_x19_password
async def send_web_command(
self, command: str, params: dict = None
) -> Optional[dict]:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.uname, self.pwd)
try:
async with httpx.AsyncClient() as client:
if params:
data = await client.post(url, data=params, 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_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
data = await self.send_web_command("get_miner_conf") data = await self.web.get_miner_conf()
if data: if data:
self.config = MinerConfig().from_raw(data) self.config = MinerConfig().from_raw(data)
return self.config return self.config
@@ -63,9 +39,7 @@ class X19(BMMiner):
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config self.config = config
conf = config.as_x19(user_suffix=user_suffix) conf = config.as_x19(user_suffix=user_suffix)
await self.send_web_command( await self.web.set_miner_conf(conf)
"set_miner_conf", params=conf # noqa: ignore conf being a str
)
for i in range(7): for i in range(7):
data = await self.get_config() data = await self.get_config()
@@ -74,46 +48,40 @@ class X19(BMMiner):
await asyncio.sleep(1) await asyncio.sleep(1)
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
data = await self.send_web_command( data = await self.web.blink(blink=True)
"blink",
params=json.dumps({"blink": "true"}), # noqa - ignore params being a str
)
if data: if data:
if data.get("code") == "B000": if data.get("code") == "B000":
self.light = True self.light = True
return self.light return self.light
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
data = await self.send_web_command( data = await self.web.blink(blink=False)
"blink",
params=json.dumps({"blink": "false"}), # noqa - ignore params being a str
)
if data: if data:
if data.get("code") == "B100": if data.get("code") == "B100":
self.light = True self.light = True
return self.light return self.light
async def reboot(self) -> bool: async def reboot(self) -> bool:
data = await self.send_web_command("reboot") data = await self.web.reboot()
if data: if data:
return True return True
return False return False
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
cfg = await self.get_config() cfg = await self.get_config()
cfg.autotuning_wattage = 0 cfg.miner_mode = X19PowerMode.Sleep
await self.send_config(cfg) await self.send_config(cfg)
return True return True
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
cfg = await self.get_config() cfg = await self.get_config()
cfg.autotuning_wattage = 3600 cfg.miner_mode = X19PowerMode.Normal
await self.send_config(cfg) await self.send_config(cfg)
return True return True
async def get_hostname(self) -> Union[str, None]: async def get_hostname(self) -> Union[str, None]:
try: try:
data = await self.send_web_command("get_system_info") data = await self.web.get_system_info()
if data: if data:
return data["hostname"] return data["hostname"]
except KeyError: except KeyError:
@@ -121,14 +89,14 @@ class X19(BMMiner):
async def get_mac(self) -> Union[str, None]: async def get_mac(self) -> Union[str, None]:
try: try:
data = await self.send_web_command("get_system_info") data = await self.web.get_system_info()
if data: if data:
return data["macaddr"] return data["macaddr"]
except KeyError: except KeyError:
pass pass
try: try:
data = await self.send_web_command("get_network_info") data = await self.web.get_network_info()
if data: if data:
return data["macaddr"] return data["macaddr"]
except KeyError: except KeyError:
@@ -136,7 +104,7 @@ class X19(BMMiner):
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
errors = [] errors = []
data = await self.send_web_command("summary") data = await self.web.summary()
if data: if data:
try: try:
for item in data["SUMMARY"][0]["status"]: for item in data["SUMMARY"][0]["status"]:
@@ -153,7 +121,7 @@ class X19(BMMiner):
if self.light: if self.light:
return self.light return self.light
try: try:
data = await self.send_web_command("get_blink_status") data = await self.web.get_blink_status()
if data: if data:
self.light = data["blink"] self.light = data["blink"]
except KeyError: except KeyError:
@@ -193,42 +161,34 @@ class X19(BMMiner):
): ):
if not hostname: if not hostname:
hostname = await self.get_hostname() hostname = await self.get_hostname()
payload = { await self.web.set_network_conf(
"ipAddress": ip, ip=ip,
"ipDns": dns, dns=dns,
"ipGateway": gateway, gateway=gateway,
"ipHost": hostname, subnet_mask=subnet_mask,
"ipPro": 2, # static hostname=hostname,
"ipSub": subnet_mask, protocol=2,
} )
await self.send_web_command("set_network_conf", params=payload)
async def set_dhcp(self, hostname: str = None): async def set_dhcp(self, hostname: str = None):
if not hostname: if not hostname:
hostname = await self.get_hostname() hostname = await self.get_hostname()
payload = { await self.web.set_network_conf(
"ipAddress": "", ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
"ipDns": "", )
"ipGateway": "",
"ipHost": hostname,
"ipPro": 1, # DHCP
"ipSub": "",
}
await self.send_web_command("set_network_conf", params=payload)
async def set_hostname(self, hostname: str): async def set_hostname(self, hostname: str):
cfg = await self.send_web_command("get_network_info") cfg = await self.web.get_network_info()
dns = cfg["conf_dnsservers"] dns = cfg["conf_dnsservers"]
gateway = cfg["conf_gateway"] gateway = cfg["conf_gateway"]
ip = cfg["conf_ipaddress"] ip = cfg["conf_ipaddress"]
subnet_mask = cfg["conf_netmask"] subnet_mask = cfg["conf_netmask"]
protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2 protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2
payload = { await self.web.set_network_conf(
"ipAddress": ip, ip=ip,
"ipDns": dns, dns=dns,
"ipGateway": gateway, gateway=gateway,
"ipHost": hostname, subnet_mask=subnet_mask,
"ipPro": protocol, hostname=hostname,
"ipSub": subnet_mask, protocol=protocol,
} )
await self.send_web_command("set_network_conf", params=payload)

View File

@@ -0,0 +1,263 @@
# ------------------------------------------------------------------------------
# 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
from typing import List, Optional, Union
from pyasic.API import APIError
from pyasic.config import MinerConfig, X19PowerMode
from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.data import HashBoard
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.web.X7 import X7WebAPI
class X7(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.web = X7WebAPI(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 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:
# not actually GHS, MHS.
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000000), 5)
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, 5)
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_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 = "MH"
if rate_unit == "GH":
return round(ideal_rate, 2)
if rate_unit == "MH":
return round(ideal_rate / 1000000, 5)
else:
return round(ideal_rate, 2)
except (KeyError, IndexError):
pass
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
conf = config.as_x19(user_suffix=user_suffix)
await self.web.set_miner_conf(conf)
for i in range(7):
data = await self.get_config()
if data.as_x19() == conf:
break
await asyncio.sleep(1)
async def fault_light_on(self) -> bool:
data = await self.web.blink(blink=True)
if data:
if data.get("code") == "B000":
self.light = True
return self.light
async def fault_light_off(self) -> bool:
data = await self.web.blink(blink=False)
if data:
if data.get("code") == "B100":
self.light = True
return self.light
async def reboot(self) -> bool:
data = await self.web.reboot()
if data:
return True
return False
async def stop_mining(self) -> bool:
cfg = await self.get_config()
cfg.miner_mode = X19PowerMode.Sleep
await self.send_config(cfg)
return True
async def resume_mining(self) -> bool:
cfg = await self.get_config()
cfg.miner_mode = X19PowerMode.Normal
await self.send_config(cfg)
return True
async def get_hostname(self) -> Union[str, None]:
try:
data = await self.web.get_system_info()
if data:
return data["hostname"]
except KeyError:
pass
async def get_mac(self) -> Union[str, None]:
try:
data = await self.web.get_system_info()
if data:
return data["macaddr"]
except KeyError:
pass
try:
data = await self.web.get_network_info()
if data:
return data["macaddr"]
except KeyError:
pass
async def get_errors(self) -> List[MinerErrorData]:
errors = []
data = await self.web.summary()
if data:
try:
for item in data["SUMMARY"][0]["status"]:
try:
if not item["status"] == "s":
errors.append(X19Error(item["msg"]))
except KeyError:
continue
except (KeyError, IndexError):
pass
return errors
async def get_fault_light(self) -> bool:
if self.light:
return self.light
try:
data = await self.web.get_blink_status()
if data:
self.light = data["blink"]
except KeyError:
pass
return self.light
async def set_static_ip(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str = "255.255.255.0",
hostname: str = None,
):
if not hostname:
hostname = await self.get_hostname()
await self.web.set_network_conf(
ip=ip,
dns=dns,
gateway=gateway,
subnet_mask=subnet_mask,
hostname=hostname,
protocol=2,
)
async def set_dhcp(self, hostname: str = None):
if not hostname:
hostname = await self.get_hostname()
await self.web.set_network_conf(
ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
)
async def set_hostname(self, hostname: str):
cfg = await self.web.get_network_info()
dns = cfg["conf_dnsservers"]
gateway = cfg["conf_gateway"]
ip = cfg["conf_ipaddress"]
subnet_mask = cfg["conf_netmask"]
protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2
await self.web.set_network_conf(
ip=ip,
dns=dns,
gateway=gateway,
subnet_mask=subnet_mask,
hostname=hostname,
protocol=protocol,
)

View File

@@ -22,3 +22,5 @@ from .cgminer_avalon import CGMinerAvalon
from .hiveon import Hiveon from .hiveon import Hiveon
from .vnish import VNish from .vnish import VNish
from .X19 import X19 from .X19 import X19
from .X7 import X7
from .X15 import X15

View File

@@ -17,17 +17,16 @@
import ipaddress import ipaddress
import logging import logging
from collections import namedtuple from collections import namedtuple
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple
import asyncssh import asyncssh
from pyasic.API.bmminer import BMMinerAPI from pyasic.API.bmminer import BMMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
class BMMiner(BaseMiner): class BMMiner(BaseMiner):

View File

@@ -26,11 +26,10 @@ import toml
from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
@@ -690,14 +689,15 @@ class BOSMiner(BaseMiner):
# need to use get_config, as this will never read perfectly as there are some bad edge cases # need to use get_config, as this will never read perfectly as there are some bad edge cases
groups = [] groups = []
cfg = await self.get_config() cfg = await self.get_config()
for group in cfg.pool_groups: if cfg:
pools = {"quota": group.quota} for group in cfg.pool_groups:
for _i, _pool in enumerate(group.pools): pools = {"quota": group.quota}
pools[f"pool_{_i + 1}_url"] = _pool.url.replace( for _i, _pool in enumerate(group.pools):
"stratum+tcp://", "" pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
).replace("stratum2+tcp://", "") "stratum+tcp://", ""
pools[f"pool_{_i + 1}_user"] = _pool.username ).replace("stratum2+tcp://", "")
groups.append(pools) pools[f"pool_{_i + 1}_user"] = _pool.username
groups.append(pools)
return groups return groups
else: else:
groups[0][f"pool_{i + 1}_url"] = ( groups[0][f"pool_{i + 1}_url"] = (
@@ -839,7 +839,6 @@ class BOSMiner(BaseMiner):
api_devs = await self.api.devs() api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
nom_hr = 0
if api_devs: if api_devs:
try: try:

View File

@@ -16,8 +16,7 @@
import ipaddress import ipaddress
import logging import logging
from collections import namedtuple from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Union
import asyncssh import asyncssh
@@ -180,5 +179,5 @@ class BOSMinerOld(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]: async def get_nominal_hashrate(self) -> Optional[float]:
return None return None
async def get_data(self, allow_warning: bool = False) -> MinerData: async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))

View File

@@ -18,15 +18,14 @@ import ipaddress
import logging import logging
import warnings import warnings
from collections import namedtuple from collections import namedtuple
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple
from pyasic.API.btminer import BTMinerAPI from pyasic.API.btminer import BTMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
@@ -122,17 +121,7 @@ class BTMiner(BaseMiner):
pools_conf = conf["pools"] pools_conf = conf["pools"]
try: try:
await self.api.update_pools( await self.api.update_pools(**pools_conf)
pools_conf[0]["url"],
pools_conf[0]["user"],
pools_conf[0]["pass"],
pools_conf[1]["url"],
pools_conf[1]["user"],
pools_conf[1]["pass"],
pools_conf[2]["url"],
pools_conf[2]["user"],
pools_conf[2]["pass"],
)
except APIError: except APIError:
pass pass
try: try:
@@ -546,8 +535,6 @@ class BTMiner(BaseMiner):
pass pass
async def get_fault_light(self, api_get_miner_info: dict = None) -> bool: async def get_fault_light(self, api_get_miner_info: dict = None) -> bool:
data = None
if not api_get_miner_info: if not api_get_miner_info:
try: try:
api_get_miner_info = await self.api.get_miner_info() api_get_miner_info = await self.api.get_miner_info()

View File

@@ -17,17 +17,16 @@
import ipaddress import ipaddress
import logging import logging
from collections import namedtuple from collections import namedtuple
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple
import asyncssh import asyncssh
from pyasic.API.cgminer import CGMinerAPI from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
class CGMiner(BaseMiner): class CGMiner(BaseMiner):
@@ -129,12 +128,8 @@ class CGMiner(BaseMiner):
else: else:
return True return True
async def get_config(self, api_pools: dict = None) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data api_pools = await self.api.pools()
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools: if api_pools:
self.config = MinerConfig().from_api(api_pools["POOLS"]) self.config = MinerConfig().from_api(api_pools["POOLS"])

View File

@@ -14,20 +14,15 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import ipaddress
import logging import logging
import re import re
from collections import namedtuple from typing import List, Optional
from typing import List, Optional, Tuple, Union
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner from pyasic.miners._backends import CGMiner
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings
class CGMinerAvalon(CGMiner): class CGMinerAvalon(CGMiner):
@@ -79,7 +74,7 @@ class CGMinerAvalon(CGMiner):
logging.debug(f"{self}: Sending config.") # noqa - This doesnt work... logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
conf = config.as_avalon(user_suffix=user_suffix) conf = config.as_avalon(user_suffix=user_suffix)
try: try:
data = await self.api.ascset( data = await self.api.ascset( # noqa
0, "setpool", f"root,root,{conf}" 0, "setpool", f"root,root,{conf}"
) # this should work but doesn't ) # this should work but doesn't
except APIError: except APIError:

View File

@@ -14,92 +14,19 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import json
import logging import logging
import warnings from typing import Optional
from typing import Optional, Union
import httpx
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners._backends.bmminer import BMMiner from pyasic.miners._backends.bmminer import BMMiner
from pyasic.settings import PyasicSettings from pyasic.web.vnish import VNishWebAPI
class VNish(BMMiner): class VNish(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.api_type = "VNish" self.api_type = "VNish"
self.uname = "root" self.web = VNishWebAPI(ip)
self.pwd = PyasicSettings().global_vnish_password
self.jwt = None
async def auth(self):
async with httpx.AsyncClient() as client:
try:
auth = await client.post(
f"http://{self.ip}/api/v1/unlock",
json={"pw": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate web token with miner: {self}")
else:
if not auth.status_code == 200:
warnings.warn(
f"Could not authenticate web token with miner: {self}"
)
return None
json_auth = auth.json()
self.jwt = json_auth["token"]
return self.jwt
async def send_web_command(
self, command: str, data: Union[dict, None] = None, method: str = "GET"
):
if not self.jwt:
await self.auth()
if not data:
data = {}
async with httpx.AsyncClient() as client:
for i in range(PyasicSettings().miner_get_data_retries):
try:
auth = self.jwt
if command.startswith("system"):
auth = "Bearer " + self.jwt
if method == "GET":
response = await client.get(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
)
elif method == "POST":
if data:
response = await client.post(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
json=data,
)
else:
response = await client.post(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
)
else:
raise APIError("Bad method type.")
if not response.status_code == 200:
# refresh the token, retry
await self.auth()
continue
json_data = response.json()
if json_data:
return json_data
return True
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
async def get_model(self, api_stats: dict = None) -> Optional[str]: async def get_model(self, api_stats: dict = None) -> Optional[str]:
# check if model is cached # check if model is cached
@@ -122,16 +49,26 @@ class VNish(BMMiner):
pass pass
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
data = await self.send_web_command("mining/restart", method="POST") data = await self.web.restart_vnish()
return data if data:
try:
return data["success"]
except KeyError:
pass
return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
data = await self.send_web_command("system/reboot", method="POST") data = await self.web.reboot()
return data if data:
try:
return data["success"]
except KeyError:
pass
return False
async def get_mac(self, web_summary: dict = None) -> str: async def get_mac(self, web_summary: dict = None) -> str:
if not web_summary: if not web_summary:
web_info = await self.send_web_command("info") web_info = await self.web.info()
if web_info: if web_info:
try: try:
@@ -149,7 +86,7 @@ class VNish(BMMiner):
async def get_hostname(self, web_summary: dict = None) -> str: async def get_hostname(self, web_summary: dict = None) -> str:
if not web_summary: if not web_summary:
web_info = await self.send_web_command("info") web_info = await self.web.info()
if web_info: if web_info:
try: try:
@@ -167,7 +104,7 @@ class VNish(BMMiner):
async def get_wattage(self, web_summary: dict = None) -> Optional[int]: async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if not web_summary:
web_summary = await self.send_web_command("summary") web_summary = await self.web.summary()
if web_summary: if web_summary:
try: try:
@@ -196,7 +133,7 @@ class VNish(BMMiner):
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
if not web_settings: if not web_settings:
web_settings = await self.send_web_command("summary") web_settings = await self.web.summary()
if web_settings: if web_settings:
try: try:
@@ -207,7 +144,7 @@ class VNish(BMMiner):
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]: async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if not web_summary: if not web_summary:
web_summary = await self.send_web_command("summary") web_summary = await self.web.summary()
if web_summary: if web_summary:
try: try:

View File

@@ -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._types.makes import AntMiner
class Z15(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "Z15"
self.nominal_chips = 3
self.fan_count = 2

View File

@@ -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 .Z15 import *

View File

@@ -0,0 +1,25 @@
# ------------------------------------------------------------------------------
# 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._types.makes import AntMiner
class L7(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "L7"
self.nominal_chips = 120
self.fan_count = 4

View File

@@ -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 .L7 import L7

View File

@@ -13,7 +13,8 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X7 import *
from .X9 import * from .X9 import *
from .X15 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *

View File

@@ -48,10 +48,7 @@ class M50VH20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "M50 VH20" self.model = "M50 VH20"
self.nominal_chips = 0 self.nominal_chips = 111
warnings.warn(
"Unknown chip count for miner type M50 VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.fan_count = 2 self.fan_count = 2
@@ -72,10 +69,7 @@ class M50VH40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "M50 VH40" self.model = "M50 VH40"
self.nominal_chips = 0 self.nominal_chips = 84
warnings.warn(
"Unknown chip count for miner type M50 VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.fan_count = 2 self.fan_count = 2

View File

@@ -14,45 +14,21 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import json from typing import Union
from typing import Optional, Union
import httpx
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings from pyasic.web.X17 import X17WebAPI
class BMMinerX17(BMMiner): class BMMinerX17(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver) super().__init__(ip, api_ver=api_ver)
self.ip = ip self.ip = ip
self.uname = "root" self.web = X17WebAPI(ip)
self.pwd = PyasicSettings().global_x17_password
async def send_web_command(
self, command: str, params: dict = None
) -> Optional[dict]:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.uname, self.pwd)
try:
async with httpx.AsyncClient() as client:
if params:
data = await client.post(url, data=params, 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_mac(self) -> Union[str, None]: async def get_mac(self) -> Union[str, None]:
try: try:
data = await self.send_web_command("get_system_info") data = await self.web.get_system_info()
if data: if data:
return data["macaddr"] return data["macaddr"]
except KeyError: except KeyError:
@@ -60,11 +36,9 @@ class BMMinerX17(BMMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
# this should time out, after it does do a check # this should time out, after it does do a check
await self.send_web_command("blink", params={"action": "startBlink"}) await self.web.blink(blink=True)
try: try:
data = await self.send_web_command( data = await self.web.get_blink_status()
"blink", params={"action": "onPageLoaded"}
)
if data: if data:
if data["isBlinking"]: if data["isBlinking"]:
self.light = True self.light = True
@@ -73,11 +47,9 @@ class BMMinerX17(BMMiner):
return self.light return self.light
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
await self.send_web_command("blink", params={"action": "stopBlink"}) await self.web.blink(blink=False)
try: try:
data = await self.send_web_command( data = await self.web.get_blink_status()
"blink", params={"action": "onPageLoaded"}
)
if data: if data:
if not data["isBlinking"]: if not data["isBlinking"]:
self.light = False self.light = False
@@ -86,7 +58,7 @@ class BMMinerX17(BMMiner):
return self.light return self.light
async def reboot(self) -> bool: async def reboot(self) -> bool:
data = await self.send_web_command("reboot") data = await self.web.reboot()
if data: if data:
return True return True
return False return False
@@ -95,9 +67,7 @@ class BMMinerX17(BMMiner):
if self.light: if self.light:
return self.light return self.light
try: try:
data = await self.send_web_command( data = await self.web.get_blink_status()
"blink", params={"action": "onPageLoaded"}
)
if data: if data:
self.light = data["isBlinking"] self.light = data["isBlinking"]
except KeyError: except KeyError:
@@ -106,7 +76,7 @@ class BMMinerX17(BMMiner):
async def get_hostname(self) -> Union[str, None]: async def get_hostname(self) -> Union[str, None]:
try: try:
data = await self.send_web_command("get_system_info") data = await self.web.get_system_info()
if data: if data:
return data["hostname"] return data["hostname"]
except KeyError: except KeyError:

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19 # noqa - Ignore access to _module from pyasic.miners._types import S19 # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19Pro # noqa - Ignore access to _module from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19XP # noqa - Ignore access to _module from pyasic.miners._types import S19XP # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19a # noqa - Ignore access to _module from pyasic.miners._types import S19a # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19aPro # noqa - Ignore access to _module from pyasic.miners._types import S19aPro # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19j # noqa - Ignore access to _module from pyasic.miners._types import S19j # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import S19jPro # noqa - Ignore access to _module from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners._backends import X19 from pyasic.miners._backends import X19 # noqa - Ignore access to _module
from pyasic.miners._types import T19 # noqa - Ignore access to _module from pyasic.miners._types import T19 # noqa - Ignore access to _module
# noqa - Ignore access to _module # noqa - Ignore access to _module

View File

@@ -0,0 +1,24 @@
# ------------------------------------------------------------------------------
# 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 X7 # noqa - Ignore access to _module
from pyasic.miners._types import L7 # noqa - Ignore access to _module
# noqa - Ignore access to _module
class BMMinerL7(X7, L7):
pass

View File

@@ -0,0 +1,17 @@
# ------------------------------------------------------------------------------
# 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 .L7 import BMMinerL7

View File

@@ -14,53 +14,29 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import json from typing import Union
from typing import Optional, Union
import httpx
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.miners._types import S9 # noqa - Ignore access to _module from pyasic.miners._types import S9 # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings from pyasic.web.S9 import S9WebAPI
class BMMinerS9(BMMiner, S9): class BMMinerS9(BMMiner, S9):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver) super().__init__(ip, api_ver=api_ver)
self.ip = ip self.ip = ip
self.uname = "root" self.web = S9WebAPI(ip)
self.pwd = PyasicSettings().global_x19_password
async def send_web_command(
self, command: str, params: dict = None
) -> Optional[dict]:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.uname, self.pwd)
try:
async with httpx.AsyncClient() as client:
if params:
data = await client.post(url, data=params, 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_mac(self) -> Union[str, None]: async def get_mac(self) -> Union[str, None]:
try: try:
data = await self.send_web_command("get_system_info") data = await self.web.get_system_info()
if data: if data:
return data["macaddr"] return data["macaddr"]
except KeyError: except KeyError:
pass pass
try: try:
data = await self.send_web_command("get_network_info") data = await self.web.get_network_info()
if data: if data:
return data["macaddr"] return data["macaddr"]
except KeyError: except KeyError:

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X7 import *
from .X9 import * from .X9 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *

View File

@@ -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._backends import X15 # noqa - Ignore access to _module
from pyasic.miners._types import Z15 # noqa - Ignore access to _module
class CGMinerZ15(X15, Z15):
pass

View File

@@ -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 .Z15 import CGMinerZ15

View File

@@ -15,5 +15,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X9 import * from .X9 import *
from .X15 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *

View File

@@ -18,11 +18,10 @@ from typing import List, Optional
import asyncssh import asyncssh
from pyasic.data import HashBoard, MinerData from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module
from pyasic.miners._types import T9 # noqa - Ignore access to _module from pyasic.miners._types import T9 # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class HiveonT9(Hiveon, T9): class HiveonT9(Hiveon, T9):

View File

@@ -31,9 +31,10 @@ from pyasic.errors import APIError
class BaseMiner(ABC): class BaseMiner(ABC):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
self.ip = None self.ip = None
self.uname = "root"
self.pwd = "admin"
self.api = None self.api = None
self.web = None
self.uname = None
self.pwd = None
self.api_type = None self.api_type = None
self.api_ver = None self.api_ver = None
self.fw_ver = None self.fw_ver = None
@@ -396,8 +397,13 @@ class BaseMiner(ABC):
web_data = {} web_data = {}
for command in web_params: for command in web_params:
data = await self.send_web_command(command) # noqa: web only anyway try:
web_data[command] = data cmd_func = getattr(self.web, command)
data = await cmd_func() # noqa: web only anyway
except (LookupError, APIError):
pass
else:
web_data[command] = data
for data_name in data_to_get: for data_name in data_to_get:
function = getattr(self, "get_" + data_name) function = getattr(self, "get_" + data_name)
sig = inspect.signature(function) sig = inspect.signature(function)

View File

@@ -13,13 +13,8 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import json
import logging import logging
import warnings from typing import List, Optional
from collections import namedtuple
from typing import List, Optional, Tuple, Union
import httpx
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
@@ -27,65 +22,14 @@ from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings from pyasic.web.Inno import InnosiliconWebAPI
class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus): class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver) super().__init__(ip, api_ver=api_ver)
self.ip = ip self.ip = ip
self.uname = "admin" self.web = InnosiliconWebAPI(ip)
self.pwd = PyasicSettings().global_innosilicon_password
self.jwt = None
async def auth(self):
async with httpx.AsyncClient() as client:
try:
auth = await client.post(
f"http://{self.ip}/api/auth",
data={"username": self.uname, "password": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate web token with miner: {self}")
else:
json_auth = auth.json()
self.jwt = json_auth.get("jwt")
return self.jwt
async def send_web_command(self, command: str, data: Union[dict, None] = None):
if not self.jwt:
await self.auth()
if not data:
data = {}
async with httpx.AsyncClient() as client:
for i in range(PyasicSettings().miner_get_data_retries):
try:
response = await client.post(
f"http://{self.ip}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt},
timeout=5,
data=data,
)
json_data = response.json()
if (
not json_data.get("success")
and "token" in json_data
and json_data.get("token") == "expired"
):
# refresh the token, retry
await self.auth()
continue
if not json_data.get("success"):
if json_data.get("msg"):
raise APIError(json_data["msg"])
elif json_data.get("message"):
raise APIError(json_data["message"])
raise APIError("Innosilicon web api command failed.")
return json_data
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
return False return False
@@ -108,7 +52,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
async def reboot(self) -> bool: async def reboot(self) -> bool:
try: try:
data = await self.send_web_command("reboot") data = await self.web.reboot()
except APIError: except APIError:
pass pass
else: else:
@@ -116,7 +60,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
async def restart_cgminer(self) -> bool: async def restart_cgminer(self) -> bool:
try: try:
data = await self.send_web_command("restartCgMiner") data = await self.web.restart_cgminer()
except APIError: except APIError:
pass pass
else: else:
@@ -127,29 +71,24 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config self.config = config
await self.send_web_command( await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
"updatePools", data=config.as_inno(user_suffix=user_suffix)
)
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac( async def get_mac(
self, self, web_get_all: dict = None, web_overview: dict = None
web_getAll: dict = None, # noqa
web_overview: dict = None, # noqa: named this way for automatic functionality
) -> Optional[str]: ) -> Optional[str]:
web_all_data = web_getAll.get("all") if not web_get_all and not web_overview:
if not web_all_data and not web_overview:
try: try:
web_overview = await self.send_web_command("overview") web_overview = await self.web.overview()
except APIError: except APIError:
pass pass
if web_all_data: if web_get_all:
try: try:
mac = web_all_data["mac"] mac = web_get_all["mac"]
return mac.upper() return mac.upper()
except KeyError: except KeyError:
pass pass
@@ -168,7 +107,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if not web_type: if not web_type:
try: try:
web_type = await self.send_web_command("type") web_type = await self.web.type()
except APIError: except APIError:
pass pass
@@ -180,21 +119,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
async def get_hashrate( async def get_hashrate(
self, self, api_summary: dict = None, web_get_all: dict = None
api_summary: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> Optional[float]: ) -> Optional[float]:
web_all_data = web_getAll.get("all") if not api_summary and not web_get_all:
if not api_summary and not web_all_data:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if web_all_data: if web_get_all:
try: try:
return round( return round(
float(web_all_data["total_hash"]["Hash Rate H"] / 1000000000000), 2 float(web_get_all["total_hash"]["Hash Rate H"] / 1000000000000), 2
) )
except KeyError: except KeyError:
pass pass
@@ -206,11 +142,8 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
async def get_hashboards( async def get_hashboards(
self, self, api_stats: dict = None, web_get_all: dict = None
api_stats: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[HashBoard]: ) -> List[HashBoard]:
web_all_data = web_getAll.get("all")
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.nominal_chips) HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards) for i in range(self.ideal_hashboards)
@@ -222,13 +155,13 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
except APIError: except APIError:
pass pass
if not web_all_data: if not web_get_all:
try: try:
web_all_data = await self.send_web_command("getAll") web_get_all = await self.web.get_all()
except APIError: except APIError:
pass pass
else: else:
web_all_data = web_all_data["all"] web_get_all = web_get_all["all"]
if api_stats: if api_stats:
if api_stats.get("STATS"): if api_stats.get("STATS"):
@@ -242,9 +175,9 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
hashboards[idx].chips = chips hashboards[idx].chips = chips
hashboards[idx].missing = False hashboards[idx].missing = False
if web_all_data: if web_get_all:
if web_all_data.get("chain"): if web_get_all.get("chain"):
for board in web_all_data["chain"]: for board in web_get_all["chain"]:
idx = board.get("ASC") idx = board.get("ASC")
if idx is not None: if idx is not None:
temp = board.get("Temp min") temp = board.get("Temp min")
@@ -264,22 +197,19 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
return hashboards return hashboards
async def get_wattage( async def get_wattage(
self, self, web_get_all: dict = None, api_stats: dict = None
web_getAll: dict = None,
api_stats: dict = None, # noqa: named this way for automatic functionality
) -> Optional[int]: ) -> Optional[int]:
web_all_data = web_getAll.get("all") if not web_get_all:
if not web_all_data:
try: try:
web_all_data = await self.send_web_command("getAll") web_get_all = await self.web.get_all()
except APIError: except APIError:
pass pass
else: else:
web_all_data = web_all_data["all"] web_get_all = web_get_all["all"]
if web_all_data: if web_get_all:
try: try:
return web_all_data["power"] return web_get_all["power"]
except KeyError: except KeyError:
pass pass
@@ -300,23 +230,19 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
wattage = int(wattage) wattage = int(wattage)
return wattage return wattage
async def get_fans( async def get_fans(self, web_get_all: dict = None) -> List[Fan]:
self, if not web_get_all:
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[Fan]:
web_all_data = web_getAll.get("all")
if not web_all_data:
try: try:
web_all_data = await self.send_web_command("getAll") web_get_all = await self.web.get_all()
except APIError: except APIError:
pass pass
else: else:
web_all_data = web_all_data["all"] web_get_all = web_get_all["all"]
fan_data = [Fan(), Fan(), Fan(), Fan()] fan_data = [Fan(), Fan(), Fan(), Fan()]
if web_all_data: if web_get_all:
try: try:
spd = web_all_data["fansSpeed"] spd = web_get_all["fansSpeed"]
except KeyError: except KeyError:
pass pass
else: else:
@@ -353,21 +279,20 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
return groups return groups
async def get_errors( async def get_errors(
self, web_getErrorDetail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality
web_error_details = web_getErrorDetail
errors = [] errors = []
if not web_error_details: if not web_get_error_detail:
try: try:
web_error_details = await self.send_web_command("getErrorDetail") web_get_error_detail = await self.web.get_error_detail()
except APIError: except APIError:
pass pass
if web_error_details: if web_get_error_detail:
try: try:
# only 1 error? # only 1 error?
# TODO: check if this should be a loop, can't remember. # TODO: check if this should be a loop, can't remember.
err = web_error_details["code"] err = web_get_error_detail["code"]
except KeyError: except KeyError:
pass pass
else: else:
@@ -376,19 +301,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
errors.append(InnosiliconError(error_code=err)) errors.append(InnosiliconError(error_code=err))
return errors return errors
async def get_wattage_limit(self, web_getAll: dict = None) -> Optional[int]: async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
web_all_data = web_getAll.get("all") if not web_get_all:
if not web_all_data:
try: try:
web_all_data = await self.send_web_command("getAll") web_get_all = await self.web.get_all()
except APIError: except APIError:
pass pass
else: else:
web_all_data = web_all_data["all"] web_get_all = web_get_all["all"]
if web_all_data: if web_get_all:
try: try:
level = web_all_data["running_mode"]["level"] level = web_get_all["running_mode"]["level"]
except KeyError: except KeyError:
pass pass
else: else:

View File

@@ -42,6 +42,10 @@ from pyasic.misc import Singleton
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
MINER_CLASSES = { MINER_CLASSES = {
"ANTMINER L7": {
"Default": BMMinerL7,
"BMMiner": BMMinerL7,
},
"ANTMINER S9": { "ANTMINER S9": {
"Default": BOSMinerS9, "Default": BOSMinerS9,
"BOSMiner": BOSMinerOld, "BOSMiner": BOSMinerOld,
@@ -59,6 +63,10 @@ MINER_CLASSES = {
"Hiveon": HiveonT9, "Hiveon": HiveonT9,
"CGMiner": CGMinerT9, "CGMiner": CGMinerT9,
}, },
"ANTMINER Z15": {
"Default": CGMinerZ15,
"CGMiner": CGMinerZ15,
},
"ANTMINER S17": { "ANTMINER S17": {
"Default": BMMinerS17, "Default": BMMinerS17,
"BOSMiner+": BOSMinerS17, "BOSMiner+": BOSMinerS17,
@@ -781,6 +789,10 @@ class MinerFactory(metaclass=Singleton):
if version and not model: if version and not model:
try: try:
model = version["VERSION"][0]["Type"].upper() model = version["VERSION"][0]["Type"].upper()
if "ANTMINER BHB" in model:
# def antminer, get from web
sysinfo = await self.__get_system_info_from_web(str(ip))
model = sysinfo["minertype"].upper()
except KeyError: except KeyError:
pass pass

View File

@@ -27,7 +27,8 @@ class _MinerListener:
def connection_made(self, transport): def connection_made(self, transport):
self.transport = transport self.transport = transport
def datagram_received(self, data, _addr): @staticmethod
def datagram_received(data, _addr):
m = data.decode() m = data.decode()
if "," in m: if "," in m:
ip, mac = m.split(",") ip, mac = m.split(",")

View File

@@ -14,7 +14,6 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from collections import namedtuple
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from pyasic.API.unknown import UnknownAPI from pyasic.API.unknown import UnknownAPI
@@ -26,7 +25,9 @@ from pyasic.miners.base import BaseMiner
class UnknownMiner(BaseMiner): class UnknownMiner(BaseMiner):
def __init__(self, ip: str, *args, **kwargs) -> None: def __init__(
self, ip: str, *args, **kwargs
) -> None: # noqa - ignore *args and **kwargs for signature consistency
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.api = UnknownAPI(ip) self.api = UnknownAPI(ip)

106
pyasic/web/Inno.py Normal file
View File

@@ -0,0 +1,106 @@
# ------------------------------------------------------------------------------
# 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
import warnings
from typing import Union
import httpx
from pyasic.errors import APIError
from pyasic.settings import PyasicSettings
from pyasic.web import BaseWebAPI
class InnosiliconWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.username = "admin"
self.pwd = PyasicSettings().global_innosilicon_password
self.jwt = None
async def auth(self):
async with httpx.AsyncClient() as client:
try:
auth = await client.post(
f"http://{self.ip}/api/auth",
data={"username": self.username, "password": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate web token with miner: {self}")
else:
json_auth = auth.json()
self.jwt = json_auth.get("jwt")
return self.jwt
async def send_command(
self,
command: Union[str, bytes],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
if not self.jwt:
await self.auth()
async with httpx.AsyncClient() as client:
for i in range(PyasicSettings().miner_get_data_retries):
try:
response = await client.post(
f"http://{self.ip}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt},
timeout=5,
data=parameters,
)
json_data = response.json()
if (
not json_data.get("success")
and "token" in json_data
and json_data.get("token") == "expired"
):
# refresh the token, retry
await self.auth()
continue
if not json_data.get("success"):
if json_data.get("msg"):
raise APIError(json_data["msg"])
elif json_data.get("message"):
raise APIError(json_data["message"])
raise APIError("Innosilicon web api command failed.")
return json_data
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
async def reboot(self) -> dict:
return await self.send_command("reboot")
async def restart_cgminer(self) -> dict:
return await self.send_command("restartCgMiner")
async def update_pools(self, conf: dict) -> dict:
return await self.send_command("updatePools", **conf)
async def overview(self) -> dict:
return await self.send_command("overview")
async def type(self) -> dict:
return await self.send_command("type")
async def get_all(self):
return await self.send_command("getAll")
async def get_error_detail(self):
return await self.send_command("getErrorDetail")

58
pyasic/web/S9.py Normal file
View File

@@ -0,0 +1,58 @@
# ------------------------------------------------------------------------------
# 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 S9WebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.pwd = PyasicSettings().global_x17_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 get_network_info(self) -> dict:
return await self.send_command("get_network_info")

72
pyasic/web/X15.py Normal file
View File

@@ -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 X15WebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.pwd = PyasicSettings().global_x17_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)

66
pyasic/web/X17.py Normal file
View File

@@ -0,0 +1,66 @@
# ------------------------------------------------------------------------------
# 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 X17WebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.pwd = PyasicSettings().global_x17_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")

99
pyasic/web/X19.py Normal file
View File

@@ -0,0 +1,99 @@
# ------------------------------------------------------------------------------
# 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 X19WebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.pwd = PyasicSettings().global_x19_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=json.dumps(parameters), auth=auth # noqa
)
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_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)
async def blink(self, blink: bool) -> dict:
if blink:
return await self.send_command("blink", blink="true")
return await self.send_command("blink", blink="false")
async def reboot(self) -> dict:
return await self.send_command("reboot")
async def get_system_info(self) -> dict:
return await self.send_command("get_system_info")
async def get_network_info(self) -> dict:
return await self.send_command("get_network_info")
async def summary(self) -> dict:
return await self.send_command("summary")
async def get_blink_status(self) -> dict:
return await self.send_command("get_blink_status")
async def set_network_conf(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str,
hostname: str,
protocol: int,
) -> dict:
return await self.send_command(
"set_network_conf",
ipAddress=ip,
ipDns=dns,
ipGateway=gateway,
ipHost=hostname,
ipPro=protocol,
ipSub=subnet_mask,
)

99
pyasic/web/X7.py Normal file
View File

@@ -0,0 +1,99 @@
# ------------------------------------------------------------------------------
# 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 X7WebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.pwd = PyasicSettings().global_x19_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=json.dumps(parameters), auth=auth # noqa
)
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_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)
async def blink(self, blink: bool) -> dict:
if blink:
return await self.send_command("blink", blink="true")
return await self.send_command("blink", blink="false")
async def reboot(self) -> dict:
return await self.send_command("reboot")
async def get_system_info(self) -> dict:
return await self.send_command("get_system_info")
async def get_network_info(self) -> dict:
return await self.send_command("get_network_info")
async def summary(self) -> dict:
return await self.send_command("summary")
async def get_blink_status(self) -> dict:
return await self.send_command("get_blink_status")
async def set_network_conf(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str,
hostname: str,
protocol: int,
) -> dict:
return await self.send_command(
"set_network_conf",
ipAddress=ip,
ipDns=dns,
ipGateway=gateway,
ipHost=hostname,
ipPro=protocol,
ipSub=subnet_mask,
)

87
pyasic/web/__init__.py Normal file
View File

@@ -0,0 +1,87 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import ipaddress
import warnings
from abc import ABC, abstractmethod
from typing import Union
from pyasic.errors import APIWarning
class BaseWebAPI(ABC):
def __init__(self, ip: str) -> None:
# ip address of the miner
self.ip = ipaddress.ip_address(ip)
self.username = "root"
self.pwd = "root"
def __new__(cls, *args, **kwargs):
if cls is BaseWebAPI:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
return object.__new__(cls)
def __repr__(self):
return f"{self.__class__.__name__}: {str(self.ip)}"
@abstractmethod
async def send_command(
self,
command: Union[str, bytes],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
pass
def _check_commands(self, *commands):
allowed_commands = self.get_commands()
return_commands = []
for command in [*commands]:
if command in allowed_commands:
return_commands.append(command)
else:
warnings.warn(
f"""Removing incorrect command: {command}
If you are sure you want to use this command please use WebAPI.send_command("{command}", ignore_errors=True) instead.""",
APIWarning,
)
return return_commands
@property
def commands(self) -> list:
return self.get_commands()
def get_commands(self) -> list:
"""Get a list of command accessible to a specific type of web API on the miner.
Returns:
A list of all web commands that the miner supports.
"""
return [
func
for func in
# each function in self
dir(self)
if not func == "commands"
if callable(getattr(self, func)) and
# no __ or _ methods
not func.startswith("__") and not func.startswith("_") and
# remove all functions that are in this base class
func
not in [
func for func in dir(BaseWebAPI) if callable(getattr(BaseWebAPI, func))
]
]

112
pyasic/web/vnish.py Normal file
View File

@@ -0,0 +1,112 @@
# ------------------------------------------------------------------------------
# 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
import warnings
from typing import Union
import httpx
from pyasic.settings import PyasicSettings
from pyasic.web import BaseWebAPI
class VNishWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.username = "admin"
self.pwd = PyasicSettings().global_vnish_password
self.token = None
async def auth(self):
async with httpx.AsyncClient() as client:
try:
auth = await client.post(
f"http://{self.ip}/api/v1/unlock",
json={"pw": self.pwd},
)
except httpx.HTTPError:
warnings.warn(f"Could not authenticate web token with miner: {self}")
else:
if not auth.status_code == 200:
warnings.warn(
f"Could not authenticate web token with miner: {self}"
)
return None
json_auth = auth.json()
self.token = json_auth["token"]
return self.token
async def send_command(
self,
command: Union[str, bytes],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
if not self.token:
await self.auth()
async with httpx.AsyncClient() as client:
for i in range(PyasicSettings().miner_get_data_retries):
try:
auth = self.token
if command.startswith("system"):
auth = "Bearer " + self.token
if parameters.get("post"):
parameters.pop("post")
response = await client.post(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
json=parameters,
)
elif not parameters == {}:
response = await client.post(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
json=parameters,
)
else:
response = await client.get(
f"http://{self.ip}/api/v1/{command}",
headers={"Authorization": auth},
timeout=5,
)
if not response.status_code == 200:
# refresh the token, retry
await self.auth()
continue
json_data = response.json()
if json_data:
return json_data
return {"success": True}
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
async def restart_vnish(self) -> dict:
return await self.send_command("mining/restart", post=True)
async def reboot(self) -> dict:
return await self.send_command("system/reboot", post=True)
async def info(self):
return await self.send_command("info")
async def summary(self):
return await self.send_command("summary")

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.30.5" version = "0.31.0"
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"