Update get_data to us get_some_data sub functions. (#27)
This commit is contained in:
@@ -67,6 +67,7 @@ details {
|
|||||||
<summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
|
<summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X/#m30svg10">VG10</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
|
<li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
|
||||||
@@ -76,9 +77,11 @@ details {
|
|||||||
<summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
<summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
|
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X/#m30svh30">VH30</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
|
||||||
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X/#m30svh60">VH60</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30SVG10
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M30SVG20
|
## M30SVG20
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
|
||||||
@@ -57,6 +65,14 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VH30
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M30S+VE40
|
## M30S+VE40
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
|
||||||
@@ -81,6 +97,14 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VH60
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH60
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M30S++
|
## M30S++
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus
|
||||||
|
|||||||
@@ -56,7 +56,12 @@ class BaseMinerAPI:
|
|||||||
Returns:
|
Returns:
|
||||||
The return data from the API command parsed from JSON into a dict.
|
The return data from the API command parsed from JSON into a dict.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"{self} - (Send Privileged Command) - {command} " + f'with args {parameters}' if parameters else '')
|
logging.debug(
|
||||||
|
f"{self} - (Send Privileged Command) - {command} "
|
||||||
|
+ f"with args {parameters}"
|
||||||
|
if parameters
|
||||||
|
else ""
|
||||||
|
)
|
||||||
# create the command
|
# create the command
|
||||||
cmd = {"command": command}
|
cmd = {"command": command}
|
||||||
if parameters:
|
if parameters:
|
||||||
@@ -81,12 +86,10 @@ class BaseMinerAPI:
|
|||||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
||||||
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, 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.
|
||||||
|
|
||||||
@@ -102,7 +105,7 @@ class BaseMinerAPI:
|
|||||||
try:
|
try:
|
||||||
data = await self.send_command(command, allow_warning=allow_warning)
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
except APIError:
|
except APIError:
|
||||||
return {}
|
return {command: [{}] for command in commands}
|
||||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -156,7 +159,9 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# handle OSError 121
|
# handle OSError 121
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if getattr(e, "winerror") == "121":
|
if getattr(e, "winerror") == "121":
|
||||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired.")
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||||
|
)
|
||||||
return b"{}"
|
return b"{}"
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
@@ -236,7 +241,9 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||||
str_data = str_data.replace("[,{", "[{")
|
str_data = str_data.replace("[,{", "[{")
|
||||||
# fix an error with Avalonminers returning inf and nan
|
# fix an error with Avalonminers returning inf and nan
|
||||||
|
str_data = str_data.replace("info", "1nfo")
|
||||||
str_data = str_data.replace("inf", "0")
|
str_data = str_data.replace("inf", "0")
|
||||||
|
str_data = str_data.replace("1nfo", "info")
|
||||||
str_data = str_data.replace("nan", "0")
|
str_data = str_data.replace("nan", "0")
|
||||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||||
if str_data.startswith(","):
|
if str_data.startswith(","):
|
||||||
|
|||||||
@@ -34,13 +34,11 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028) -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||||
super().__init__(ip, port=port)
|
super().__init__(ip, port=port)
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
async def multicommand(
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
self, *commands: str, allow_warning: bool = True
|
|
||||||
) -> dict:
|
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
commands = self._check_commands(*commands)
|
commands = self._check_commands(*commands)
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
@@ -50,21 +48,27 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
data = await self.send_command(command, allow_warning=allow_warning)
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
except APIError:
|
except APIError:
|
||||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||||
data = await self._x19_multicommand(*command.split("+"))
|
data = await self._x19_multicommand(
|
||||||
|
*command.split("+"), allow_warning=allow_warning
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands):
|
async def _x19_multicommand(self, *commands, allow_warning: bool = True):
|
||||||
data = None
|
data = None
|
||||||
try:
|
try:
|
||||||
data = {}
|
data = {}
|
||||||
# send all commands individually
|
# send all commands individually
|
||||||
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=allow_warning)
|
||||||
|
)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
raise APIError(e)
|
raise APIError(e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}")
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -33,11 +33,10 @@ class BOSMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028) -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||||
super().__init__(ip, port=port)
|
super().__init__(ip, port=port)
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
|
||||||
async def asccount(self) -> dict:
|
async def asccount(self) -> dict:
|
||||||
"""Get data on the number of ASC devices and their info.
|
"""Get data on the number of ASC devices and their info.
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
@@ -181,7 +183,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ip: str,
|
ip: str,
|
||||||
api_ver: str = "1.0.0",
|
api_ver: str = "0.0.0",
|
||||||
port: int = 4028,
|
port: int = 4028,
|
||||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||||
):
|
):
|
||||||
@@ -190,23 +192,66 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self.current_token = None
|
self.current_token = None
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
|
"""
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# commands starting with "get_" aren't supported, but we can fake that
|
||||||
|
get_commands_data = {}
|
||||||
|
for command in list(commands):
|
||||||
|
if command.startswith("get_"):
|
||||||
|
commands.remove(command)
|
||||||
|
# send seperately and append later
|
||||||
|
try:
|
||||||
|
get_commands_data[command] = [
|
||||||
|
await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
]
|
||||||
|
except APIError:
|
||||||
|
get_commands_data[command] = [{}]
|
||||||
|
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
main_data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError:
|
||||||
|
main_data = {command: [{}] for command in commands}
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
|
|
||||||
|
data = dict(**main_data, **get_commands_data)
|
||||||
|
return data
|
||||||
|
|
||||||
async def send_privileged_command(
|
async def send_privileged_command(
|
||||||
self, command: Union[str, bytes], ignore_errors: bool = False, timeout: int = 10, **kwargs
|
self,
|
||||||
|
command: Union[str, bytes],
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
timeout: int = 10,
|
||||||
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
logging.debug(f"{self} - (Send Privileged Command) - {command} " + f'with args {kwargs}' if len(kwargs) > 0 else '')
|
logging.debug(
|
||||||
|
f"{self} - (Send Privileged Command) - {command} " + f"with args {kwargs}"
|
||||||
|
if len(kwargs) > 0
|
||||||
|
else ""
|
||||||
|
)
|
||||||
command = {"cmd": command, **kwargs}
|
command = {"cmd": command, **kwargs}
|
||||||
|
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
|
print(token_data)
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
|
|
||||||
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) as e:
|
||||||
if command['cmd'] in ['reboot', 'restart']:
|
if command["cmd"] in ["reboot", "restart"]:
|
||||||
logging.info(f"{self} - (reboot/restart) - Whatsminers currently break this. "
|
logging.info(
|
||||||
f"Ignoring exception. Command probably worked.")
|
f"{self} - (reboot/restart) - Whatsminers currently break this. "
|
||||||
|
f"Ignoring exception. Command probably worked."
|
||||||
|
)
|
||||||
# FAKING IT HERE
|
# FAKING IT HERE
|
||||||
data = b'{"STATUS": "S", "When": 1670966423, "Code": 131, "Msg": "API command OK", "Description": "Reboot"}'
|
data = b'{"STATUS": "S", "When": 1670966423, "Code": 131, "Msg": "API command OK", "Description": "Reboot"}'
|
||||||
else:
|
else:
|
||||||
@@ -240,6 +285,12 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
logging.debug(f"{self} - (Get Token) - Getting token")
|
logging.debug(f"{self} - (Get Token) - Getting token")
|
||||||
|
if self.current_token:
|
||||||
|
if self.current_token[
|
||||||
|
"timestamp"
|
||||||
|
] > datetime.datetime.now() - datetime.timedelta(minutes=30):
|
||||||
|
return self.current_token
|
||||||
|
|
||||||
# get the token
|
# get the token
|
||||||
data = await self.send_command("get_token")
|
data = await self.send_command("get_token")
|
||||||
|
|
||||||
@@ -261,8 +312,11 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self.current_token = {
|
self.current_token = {
|
||||||
"host_sign": host_sign,
|
"host_sign": host_sign,
|
||||||
"host_passwd_md5": host_passwd_md5,
|
"host_passwd_md5": host_passwd_md5,
|
||||||
|
"timestamp": datetime.datetime.now(),
|
||||||
}
|
}
|
||||||
logging.debug(f"{self} - (Get Token) - Gathered token data: {self.current_token}")
|
logging.debug(
|
||||||
|
f"{self} - (Get Token) - Gathered token data: {self.current_token}"
|
||||||
|
)
|
||||||
return self.current_token
|
return self.current_token
|
||||||
|
|
||||||
#### PRIVILEGED COMMANDS ####
|
#### PRIVILEGED COMMANDS ####
|
||||||
@@ -383,8 +437,8 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self,
|
self,
|
||||||
auto: bool = True,
|
auto: bool = True,
|
||||||
color: str = "red",
|
color: str = "red",
|
||||||
period: int = 400,
|
period: int = 60,
|
||||||
duration: int = 200,
|
duration: int = 20,
|
||||||
start: int = 0,
|
start: int = 0,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Set the LED on the miner using the API.
|
"""Set the LED on the miner using the API.
|
||||||
@@ -443,7 +497,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
d = await asyncio.wait_for(self.send_privileged_command("reboot"), timeout=timeout)
|
d = await asyncio.wait_for(
|
||||||
|
self.send_privileged_command("reboot"), timeout=timeout
|
||||||
|
)
|
||||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
@@ -691,7 +747,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"0."
|
f"0."
|
||||||
)
|
)
|
||||||
|
|
||||||
return await self.send_privileged_command("set_temp_offset", temp_offset=temp_offset)
|
return await self.send_privileged_command(
|
||||||
|
"set_temp_offset", temp_offset=temp_offset
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def adjust_power_limit(self, power_limit: int):
|
async def adjust_power_limit(self, power_limit: int):
|
||||||
@@ -712,8 +770,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return await self.send_privileged_command("adjust_power_limit", power_limit=power_limit)
|
return await self.send_privileged_command(
|
||||||
|
"adjust_power_limit", power_limit=power_limit
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def adjust_upfreq_speed(self, upfreq_speed: int):
|
async def adjust_upfreq_speed(self, upfreq_speed: int):
|
||||||
@@ -740,7 +799,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"range. Please set a number between 0 (Normal) and "
|
f"range. Please set a number between 0 (Normal) and "
|
||||||
f"9 (Fastest)."
|
f"9 (Fastest)."
|
||||||
)
|
)
|
||||||
return await self.send_privileged_command("adjust_upfreq_speed", upfreq_speed=upfreq_speed)
|
return await self.send_privileged_command(
|
||||||
|
"adjust_upfreq_speed", upfreq_speed=upfreq_speed
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def set_poweroff_cool(self, poweroff_cool: bool):
|
async def set_poweroff_cool(self, poweroff_cool: bool):
|
||||||
@@ -760,7 +821,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await self.send_privileged_command("set_poweroff_cool", poweroff_cool=int(poweroff_cool))
|
return await self.send_privileged_command(
|
||||||
|
"set_poweroff_cool", poweroff_cool=int(poweroff_cool)
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def set_fan_zero_speed(self, fan_zero_speed: bool):
|
async def set_fan_zero_speed(self, fan_zero_speed: bool):
|
||||||
@@ -780,8 +843,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return await self.send_privileged_command("set_fan_zero_speed", fan_zero_speed=int(fan_zero_speed))
|
return await self.send_privileged_command(
|
||||||
|
"set_fan_zero_speed", fan_zero_speed=int(fan_zero_speed)
|
||||||
|
)
|
||||||
|
|
||||||
#### END privileged COMMANDS ####
|
#### END privileged COMMANDS ####
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,11 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0", port: int = 4028):
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
async def multicommand(
|
|
||||||
self, *commands: str, allow_warning: bool = True
|
|
||||||
) -> dict:
|
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
commands = self._check_commands(*commands)
|
commands = self._check_commands(*commands)
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
@@ -66,7 +63,9 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
except APIError as e:
|
except APIError as e:
|
||||||
raise APIError(e)
|
raise APIError(e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}")
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ class UnknownAPI(BaseMinerAPI):
|
|||||||
with as many APIs as possible.
|
with as many APIs as possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip, api_ver: str = "1.0.0", port: int = 4028):
|
def __init__(self, ip, api_ver: str = "0.0.0", port: int = 4028):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
|
||||||
async def asccount(self) -> dict:
|
async def asccount(self) -> dict:
|
||||||
return await self.send_command("asccount")
|
return await self.send_command("asccount")
|
||||||
|
|
||||||
|
|||||||
@@ -420,7 +420,10 @@ class MinerConfig:
|
|||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
||||||
return {"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix), "wattage": self.autotuning_wattage}
|
return {
|
||||||
|
"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix),
|
||||||
|
"wattage": self.autotuning_wattage,
|
||||||
|
}
|
||||||
|
|
||||||
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 config usable by an Innosilicon device.
|
"""Convert the data in this class to a config usable by an Innosilicon device.
|
||||||
|
|||||||
@@ -129,12 +129,10 @@ class MinerData:
|
|||||||
fault_light: Union[bool, None] = None
|
fault_light: Union[bool, None] = None
|
||||||
efficiency: int = field(init=False)
|
efficiency: int = field(init=False)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fields(cls):
|
def fields(cls):
|
||||||
return [f.name for f in fields(cls)]
|
return [f.name for f in fields(cls)]
|
||||||
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,5 @@ class BraiinsOSError:
|
|||||||
def fields(cls):
|
def fields(cls):
|
||||||
return fields(cls)
|
return fields(cls)
|
||||||
|
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class InnosiliconError:
|
|||||||
def fields(cls):
|
def fields(cls):
|
||||||
return fields(cls)
|
return fields(cls)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
if self.error_code in ERROR_CODES:
|
if self.error_code in ERROR_CODES:
|
||||||
|
|||||||
@@ -33,15 +33,44 @@ class WhatsminerError:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
|
if len(str(self.error_code)) > 3 and str(self.error_code).startswith("55"):
|
||||||
|
# 55 error code base has chip numbers, so the format is
|
||||||
|
# 55 -> board num len 1 -> chip num len 3
|
||||||
|
err_type = 55
|
||||||
|
err_subtype = int(str(self.error_code)[2:3])
|
||||||
|
err_value = int(str(self.error_code)[3:])
|
||||||
|
else:
|
||||||
err_type = int(str(self.error_code)[:-2])
|
err_type = int(str(self.error_code)[:-2])
|
||||||
err_subtype = int(str(self.error_code)[-2:-1])
|
err_subtype = int(str(self.error_code)[-2:-1])
|
||||||
err_value = int(str(self.error_code)[-1:])
|
err_value = int(str(self.error_code)[-1:])
|
||||||
try:
|
try:
|
||||||
select_err_subtype = ERROR_CODES[err_type][err_subtype]
|
select_err_type = ERROR_CODES[err_type]
|
||||||
|
if err_subtype in select_err_type:
|
||||||
|
select_err_subtype = select_err_type[err_subtype]
|
||||||
if err_value in select_err_subtype:
|
if err_value in select_err_subtype:
|
||||||
return select_err_subtype[err_value]
|
return select_err_subtype[err_value]
|
||||||
elif "n" in select_err_subtype:
|
elif "n" in select_err_subtype:
|
||||||
return select_err_subtype["n"].replace("{n}", str(err_value)) # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
return select_err_subtype[
|
||||||
|
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
].replace(
|
||||||
|
"{n}", str(err_value)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
|
elif "n" in select_err_type:
|
||||||
|
select_err_subtype = select_err_type[
|
||||||
|
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
]
|
||||||
|
if err_value in select_err_subtype:
|
||||||
|
return select_err_subtype[err_value]
|
||||||
|
elif "c" in select_err_subtype:
|
||||||
|
return (
|
||||||
|
select_err_subtype["c"]
|
||||||
|
.replace( # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
"{n}", str(err_subtype)
|
||||||
|
)
|
||||||
|
.replace("{c}", str(err_value))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return "Unknown error type."
|
return "Unknown error type."
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -196,10 +225,12 @@ ERROR_CODES = {
|
|||||||
},
|
},
|
||||||
50: { # water velocity error
|
50: { # water velocity error
|
||||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
||||||
|
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
||||||
},
|
},
|
||||||
51: { # frequency error
|
51: { # frequency error
|
||||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||||
},
|
},
|
||||||
|
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
||||||
84: {
|
84: {
|
||||||
1: {0: "Software version error."},
|
1: {0: "Software version error."},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,12 +14,16 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from typing import List, Union, Tuple, Optional
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
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 HashBoard, MinerData
|
from pyasic.data import HashBoard, MinerData
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ from pyasic.settings import PyasicSettings
|
|||||||
class BMMiner(BaseMiner):
|
class BMMiner(BaseMiner):
|
||||||
"""Base handler for BMMiner based miners."""
|
"""Base handler for BMMiner based miners."""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api = BMMinerAPI(ip, api_ver)
|
self.api = BMMinerAPI(ip, api_ver)
|
||||||
@@ -36,74 +40,16 @@ class BMMiner(BaseMiner):
|
|||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
# check if model is cached
|
|
||||||
if self.model:
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
|
|
||||||
# get devdetails data
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
|
|
||||||
# if we get data back, parse it for model
|
|
||||||
if version_data:
|
|
||||||
# handle Antminer BMMiner as a base
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
|
|
||||||
# if we don't get devdetails, log a failed attempt
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> str:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or "?"
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
# open an ssh connection
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
# if we get the connection, check hostname
|
|
||||||
if conn is not None:
|
|
||||||
# get output of the hostname file
|
|
||||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
|
||||||
host = data.stdout.strip()
|
|
||||||
|
|
||||||
# return hostname data
|
|
||||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
else:
|
|
||||||
# return ? if we fail to get hostname with no ssh connection
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
except Exception:
|
|
||||||
# return ? if we fail to get hostname with an exception
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
cmd: The command to run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = await self._get_ssh_connection()
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
# open an ssh connection
|
# open an ssh connection
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with conn:
|
||||||
# 3 retries
|
# 3 retries
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
@@ -122,31 +68,17 @@ class BMMiner(BaseMiner):
|
|||||||
# return the result, either command output or None
|
# return the result, either command output or None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_config(self) -> Union[list, None]:
|
async def get_config(self) -> MinerConfig:
|
||||||
"""Get the pool configuration of the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Pool config data or None.
|
|
||||||
"""
|
|
||||||
# get pool data
|
# get pool data
|
||||||
|
try:
|
||||||
pools = await self.api.pools()
|
pools = await self.api.pools()
|
||||||
pool_data = []
|
except APIError:
|
||||||
|
return self.config
|
||||||
|
|
||||||
# ensure we got pool data
|
self.config = MinerConfig().from_api(pools["POOLS"])
|
||||||
if not pools:
|
return self.config
|
||||||
return
|
|
||||||
|
|
||||||
# parse all the pools
|
|
||||||
for pool in pools["POOLS"]:
|
|
||||||
pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"})
|
|
||||||
return pool_data
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
"""Reboot the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result of rebooting the miner.
|
|
||||||
"""
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
logging.debug(f"{self}: Sending reboot command.")
|
||||||
_ret = await self.send_ssh_command("reboot")
|
_ret = await self.send_ssh_command("reboot")
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
logging.debug(f"{self}: Reboot command completed.")
|
||||||
@@ -157,41 +89,12 @@ class BMMiner(BaseMiner):
|
|||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_version(self) -> dict:
|
|
||||||
"""Get miner firmware version.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner api & firmware version or None.
|
|
||||||
"""
|
|
||||||
# check if version is cached
|
|
||||||
if self.fw_ver and self.api_ver:
|
|
||||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
|
||||||
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
|
|
||||||
# Now get the API version
|
|
||||||
version = await self.api.version()
|
|
||||||
self.api_ver = version['VERSION'][0]['API']
|
|
||||||
self.fw_ver = version['VERSION'][0]['CompileTime']
|
|
||||||
self.api.api_ver = self.api_ver
|
|
||||||
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
|
|
||||||
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
return "00:00:00:00:00:00"
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -201,74 +104,99 @@ class BMMiner(BaseMiner):
|
|||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
"""Get data from the miner.
|
return False
|
||||||
|
|
||||||
Returns:
|
##################################################
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
"""
|
##################################################
|
||||||
data = MinerData(
|
|
||||||
ip=str(self.ip),
|
async def get_mac(self) -> str:
|
||||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
return "00:00:00:00:00:00"
|
||||||
ideal_hashboards=self.ideal_hashboards,
|
|
||||||
|
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||||
|
if self.model:
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
|
||||||
|
if not api_devdetails:
|
||||||
|
try:
|
||||||
|
api_devdetails = await self.api.devdetails()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_devdetails:
|
||||||
|
try:
|
||||||
|
self.model = api_devdetails["DEVDETAILS"][0]["Model"].replace(
|
||||||
|
"Antminer ", ""
|
||||||
)
|
)
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
except (TypeError, IndexError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.warning(f"Failed to get model for miner: {self}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_version(
|
||||||
|
self, api_version: dict = None
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
|
||||||
|
# Check to see if the version info is already cached
|
||||||
|
if self.api_ver and self.fw_ver:
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
if not api_version:
|
||||||
|
try:
|
||||||
|
api_version = await self.api.version()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_version:
|
||||||
|
try:
|
||||||
|
self.api_ver = api_version["VERSION"][0]["API"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
async def get_hostname(self) -> Optional[str]:
|
||||||
|
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||||
|
if hn:
|
||||||
|
self.hostname = hn
|
||||||
|
return self.hostname
|
||||||
|
|
||||||
|
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||||
|
# get hr from API
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||||
|
hashboards = []
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
fan_offset = -1
|
boards = api_stats["STATS"]
|
||||||
|
|
||||||
model = await self.get_model()
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
errors = await self.get_errors()
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
if self.make:
|
|
||||||
data.make = self.make
|
|
||||||
|
|
||||||
await self.get_version()
|
|
||||||
data.api_ver = self.api_ver
|
|
||||||
data.fw_ver = self.fw_ver
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
for error in errors:
|
|
||||||
data.errors.append(error)
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary", "pools", "stats", allow_warning=allow_warning
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
|
||||||
pools = miner_data.get("pools")[0]
|
|
||||||
stats = miner_data.get("stats")[0]
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary.get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("GHS av")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000, 2)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
boards = stats.get("STATS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 1:
|
if len(boards) > 1:
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
@@ -279,7 +207,6 @@ class BMMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
env_temp_list = []
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||||
@@ -303,79 +230,192 @@ class BMMiner(BaseMiner):
|
|||||||
hashboard.missing = False
|
hashboard.missing = False
|
||||||
if (not chips) or (not chips > 0):
|
if (not chips) or (not chips > 0):
|
||||||
hashboard.missing = True
|
hashboard.missing = True
|
||||||
data.hashboards.append(hashboard)
|
hashboards.append(hashboard)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
if f"temp_pcb{i}" in boards[1].keys():
|
return hashboards
|
||||||
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
|
|
||||||
if not env_temp == 0:
|
async def get_env_temp(self) -> Optional[float]:
|
||||||
env_temp_list.append(int(env_temp))
|
return None
|
||||||
if not env_temp_list == []:
|
|
||||||
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
|
async def get_wattage(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage_limit(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_fans(
|
||||||
|
self, api_stats: dict = None
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
|
||||||
|
Tuple[Optional[int]],
|
||||||
|
]:
|
||||||
|
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
|
||||||
|
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
|
||||||
|
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
|
||||||
|
|
||||||
|
psu_fans = psu_fan_speeds(None)
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans_data = [None, None, None, None]
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
|
fan_offset = -1
|
||||||
|
|
||||||
if stats:
|
|
||||||
temp = stats.get("STATS")
|
|
||||||
if temp:
|
|
||||||
if len(temp) > 1:
|
|
||||||
for fan_num in range(1, 8, 4):
|
for fan_num in range(1, 8, 4):
|
||||||
for _f_num in range(4):
|
for _f_num in range(4):
|
||||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||||
if f and not f == 0 and fan_offset == -1:
|
if f and not f == 0 and fan_offset == -1:
|
||||||
fan_offset = fan_num
|
fan_offset = fan_num
|
||||||
if fan_offset == -1:
|
if fan_offset == -1:
|
||||||
fan_offset = 1
|
fan_offset = 1
|
||||||
|
|
||||||
for fan in range(self.fan_count):
|
for fan in range(self.fan_count):
|
||||||
setattr(
|
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
|
||||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
except (KeyError, IndexError):
|
||||||
)
|
pass
|
||||||
|
fans = fan_speeds(*fans_data)
|
||||||
|
|
||||||
|
return miner_fan_speeds(fans, psu_fans)
|
||||||
|
|
||||||
|
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not api_pools:
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_pools:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(api_pools["POOLS"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["URL"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_fault_light(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _get_data(self, allow_warning: bool) -> dict:
|
||||||
|
miner_data = None
|
||||||
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"pools",
|
||||||
|
"version",
|
||||||
|
"devdetails",
|
||||||
|
"stats",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"version",
|
||||||
|
"pools",
|
||||||
|
"stats",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if miner_data:
|
||||||
|
break
|
||||||
|
if miner_data:
|
||||||
|
summary = miner_data.get("summary")
|
||||||
|
if summary:
|
||||||
|
summary = summary[0]
|
||||||
|
pools = miner_data.get("pools")
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pools = pools[0]
|
||||||
pool_2 = None
|
version = miner_data.get("version")
|
||||||
pool_1_user = None
|
if version:
|
||||||
pool_2_user = None
|
version = version[0]
|
||||||
pool_1_quota = 1
|
devdetails = miner_data.get("devdetails")
|
||||||
pool_2_quota = 1
|
if devdetails:
|
||||||
quota = 0
|
devdetails = devdetails[0]
|
||||||
for pool in pools.get("POOLS"):
|
stats = miner_data.get("stats")
|
||||||
if not pool_1_user:
|
if stats:
|
||||||
pool_1_user = pool.get("User")
|
stats = stats[0]
|
||||||
pool_1 = pool["URL"]
|
else:
|
||||||
pool_1_quota = pool["Quota"]
|
summary, pools, devdetails, version, stats = (None for _ in range(5))
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
data = { # noqa - Ignore dictionary could be re-written
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
# ip - Done at start
|
||||||
"stratum2+tcp://", ""
|
# datetime - Done auto
|
||||||
)
|
"mac": await self.get_mac(),
|
||||||
data.pool_1_url = pool_1
|
"model": await self.get_model(api_devdetails=devdetails),
|
||||||
|
# make - Done at start
|
||||||
|
"api_ver": None, # - Done at end
|
||||||
|
"fw_ver": None, # Done at end.
|
||||||
|
"hostname": await self.get_hostname(),
|
||||||
|
"hashrate": await self.get_hashrate(api_summary=summary),
|
||||||
|
"hashboards": await self.get_hashboards(api_stats=stats),
|
||||||
|
# ideal_hashboards - Done at start
|
||||||
|
"env_temp": await self.get_env_temp(),
|
||||||
|
"wattage": await self.get_wattage(),
|
||||||
|
"wattage_limit": await self.get_wattage_limit(),
|
||||||
|
"fan_1": None, # - Done at end
|
||||||
|
"fan_2": None, # - Done at end
|
||||||
|
"fan_3": None, # - Done at end
|
||||||
|
"fan_4": None, # - Done at end
|
||||||
|
"fan_psu": None, # - Done at end
|
||||||
|
# ideal_chips - Done at start
|
||||||
|
"pool_split": None, # - Done at end
|
||||||
|
"pool_1_url": None, # - Done at end
|
||||||
|
"pool_1_user": None, # - Done at end
|
||||||
|
"pool_2_url": None, # - Done at end
|
||||||
|
"pool_2_user": None, # - Done at end
|
||||||
|
"errors": await self.get_errors(),
|
||||||
|
"fault_light": await self.get_fault_light(),
|
||||||
|
}
|
||||||
|
|
||||||
if pool_1_user:
|
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
|
||||||
data.pool_1_user = pool_1_user
|
fan_data = await self.get_fans()
|
||||||
|
if fan_data:
|
||||||
|
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
|
||||||
|
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
|
||||||
|
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
|
||||||
|
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
|
||||||
|
|
||||||
if pool_2:
|
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
pools_data = await self.get_pools(api_pools=pools)
|
||||||
data.pool_2_user = pool_2_user
|
if pools_data:
|
||||||
|
data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||||
if quota:
|
data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||||
data.pool_split = str(quota)
|
if len(pools_data) > 1:
|
||||||
|
data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||||
|
data[
|
||||||
|
"pool_split"
|
||||||
|
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data["pool_2_url"] = pools_data[0]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[0]["pool_2_user"]
|
||||||
|
data["quota"] = "0"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,17 +14,22 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from collections import namedtuple
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
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 MinerData
|
from pyasic.data import MinerData, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerOld(BaseMiner):
|
class BOSMinerOld(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api = BOSMinerAPI(ip, api_ver)
|
self.api = BOSMinerAPI(ip, api_ver)
|
||||||
@@ -32,25 +37,24 @@ class BOSMinerOld(BaseMiner):
|
|||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
:return: Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = await self._get_ssh_connection()
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
# open an ssh connection
|
# open an ssh connection
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with conn:
|
||||||
# 3 retries
|
# 3 retries
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
# run the command and get the result
|
# run the command and get the result
|
||||||
result = await conn.run(cmd)
|
result = await conn.run(cmd)
|
||||||
if result.stdout:
|
|
||||||
result = result.stdout
|
result = result.stdout
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if e == "SSH connection closed":
|
|
||||||
return "Update completed."
|
|
||||||
# if the command fails, log it
|
# if the command fails, log it
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
logging.warning(f"{self} command {cmd} error: {e}")
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ class BOSMinerOld(BaseMiner):
|
|||||||
return
|
return
|
||||||
continue
|
continue
|
||||||
# return the result, either command output or None
|
# return the result, either command output or None
|
||||||
return str(result)
|
return result
|
||||||
|
|
||||||
async def update_to_plus(self):
|
async def update_to_plus(self):
|
||||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
||||||
@@ -77,18 +81,6 @@ class BOSMinerOld(BaseMiner):
|
|||||||
async def get_config(self) -> None:
|
async def get_config(self) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def get_hostname(self) -> str:
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
return "00:00:00:00:00:00"
|
|
||||||
|
|
||||||
async def get_model(self) -> str:
|
|
||||||
return "S9"
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -104,8 +96,89 @@ class BOSMinerOld(BaseMiner):
|
|||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_data(self, **kwargs) -> MinerData:
|
|
||||||
return MinerData(ip=str(self.ip))
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
async def get_mac(self) -> Optional[str]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_model(self) -> str:
|
||||||
|
return "S9"
|
||||||
|
|
||||||
|
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
async def get_hostname(self) -> Optional[str]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_hashrate(self) -> Optional[float]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_hashboards(self) -> List[HashBoard]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_env_temp(self) -> Optional[float]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage_limit(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_fans(
|
||||||
|
self,
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
|
||||||
|
Tuple[Optional[int]],
|
||||||
|
]:
|
||||||
|
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
|
||||||
|
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
|
||||||
|
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
|
||||||
|
|
||||||
|
fans = fan_speeds(None, None, None, None)
|
||||||
|
psu_fans = psu_fan_speeds(None)
|
||||||
|
|
||||||
|
return miner_fan_speeds(fans, psu_fans)
|
||||||
|
|
||||||
|
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not api_pools:
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_pools:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(api_pools["POOLS"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["URL"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_fault_light(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _get_data(self, allow_warning: bool) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
||||||
|
return MinerData(ip=str(self.ip))
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from typing import List, Union, Tuple, Optional
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -26,78 +27,13 @@ from pyasic.settings import PyasicSettings
|
|||||||
|
|
||||||
|
|
||||||
class BTMiner(BaseMiner):
|
class BTMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api = BTMinerAPI(ip, api_ver)
|
self.api = BTMinerAPI(ip, api_ver)
|
||||||
self.api_type = "BTMiner"
|
self.api_type = "BTMiner"
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
if self.model:
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
if version_data:
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].split("V")[0]
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or None.
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
host_data = await self.api.get_miner_info()
|
|
||||||
if host_data:
|
|
||||||
host = host_data["Msg"]["hostname"]
|
|
||||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get hostname for miner: {self}")
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
"""Get the mac address of the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The mac address of the miner as a string.
|
|
||||||
"""
|
|
||||||
mac = ""
|
|
||||||
data = await self.api.summary()
|
|
||||||
if data:
|
|
||||||
if data.get("SUMMARY"):
|
|
||||||
if len(data["SUMMARY"]) > 0:
|
|
||||||
_mac = data["SUMMARY"][0].get("MAC")
|
|
||||||
if _mac:
|
|
||||||
mac = _mac
|
|
||||||
if mac == "":
|
|
||||||
try:
|
|
||||||
data = await self.api.get_miner_info()
|
|
||||||
if data:
|
|
||||||
if "Msg" in data.keys():
|
|
||||||
if "mac" in data["Msg"].keys():
|
|
||||||
mac = data["Msg"]["mac"]
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return str(mac).upper()
|
|
||||||
|
|
||||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
async def _reset_api_pwd_to_admin(self, pwd: str):
|
||||||
try:
|
try:
|
||||||
data = await self.api.update_pwd(pwd, "admin")
|
data = await self.api.update_pwd(pwd, "admin")
|
||||||
@@ -109,23 +45,6 @@ class BTMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
data = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = await self.api.get_miner_info()
|
|
||||||
except APIError:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
if data:
|
|
||||||
if "Msg" in data.keys():
|
|
||||||
if "ledstat" in data["Msg"].keys():
|
|
||||||
if not data["Msg"]["ledstat"] == "auto":
|
|
||||||
self.light = True
|
|
||||||
if data["Msg"]["ledstat"] == "auto":
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.set_led(auto=True)
|
data = await self.api.set_led(auto=True)
|
||||||
@@ -138,14 +57,12 @@ class BTMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def fault_light_on(self, flash: list = []) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
if flash == []:
|
|
||||||
# If no flash pattern is provided, use a red-green semi-slow alternating flash
|
|
||||||
flash = [{"color": "green", "start":0, "period":400, "duration":200},
|
|
||||||
{"color": "red", "start":200, "period":400, "duration":200}]
|
|
||||||
try:
|
try:
|
||||||
for x in flash:
|
data = await self.api.set_led(auto=False)
|
||||||
data = await self.api.set_led(auto=False, **x)
|
await self.api.set_led(
|
||||||
|
auto=False, color="green", start=0, period=1, duration=0
|
||||||
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data:
|
if data:
|
||||||
@@ -155,44 +72,21 @@ class BTMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
data = []
|
|
||||||
try:
|
|
||||||
err_data = await self.api.get_error_code()
|
|
||||||
if err_data:
|
|
||||||
if err_data.get("Msg"):
|
|
||||||
if err_data["Msg"].get("error_code"):
|
|
||||||
for err in err_data["Msg"]["error_code"]:
|
|
||||||
if isinstance(err, dict):
|
|
||||||
for code in err:
|
|
||||||
data.append(WhatsminerError(error_code=int(code)))
|
|
||||||
else:
|
|
||||||
data.append(WhatsminerError(error_code=int(err)))
|
|
||||||
except APIError:
|
|
||||||
summary_data = await self.api.summary()
|
|
||||||
if summary_data.get("SUMMARY"):
|
|
||||||
summary_data = summary_data["SUMMARY"]
|
|
||||||
if summary_data[0].get("Error Code Count"):
|
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
|
||||||
if not summary_data[0][f"Error Code {i}"] == "":
|
|
||||||
data.append(
|
|
||||||
WhatsminerError(
|
|
||||||
error_code=summary_data[0][f"Error Code {i}"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
|
try:
|
||||||
data = await self.api.reboot()
|
data = await self.api.reboot()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
if data["Msg"] == "API command OK":
|
if data["Msg"] == "API command OK":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
|
try:
|
||||||
data = await self.api.restart()
|
data = await self.api.restart()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
if data["Msg"] == "API command OK":
|
if data["Msg"] == "API command OK":
|
||||||
return True
|
return True
|
||||||
@@ -222,6 +116,7 @@ class BTMiner(BaseMiner):
|
|||||||
conf = config.as_wm(user_suffix=user_suffix)
|
conf = config.as_wm(user_suffix=user_suffix)
|
||||||
pools_conf = conf["pools"]
|
pools_conf = conf["pools"]
|
||||||
|
|
||||||
|
try:
|
||||||
await self.api.update_pools(
|
await self.api.update_pools(
|
||||||
pools_conf[0]["url"],
|
pools_conf[0]["url"],
|
||||||
pools_conf[0]["user"],
|
pools_conf[0]["user"],
|
||||||
@@ -233,6 +128,8 @@ class BTMiner(BaseMiner):
|
|||||||
pools_conf[2]["user"],
|
pools_conf[2]["user"],
|
||||||
pools_conf[2]["pass"],
|
pools_conf[2]["pass"],
|
||||||
)
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
await self.api.adjust_power_limit(conf["wattage"])
|
await self.api.adjust_power_limit(conf["wattage"])
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -261,231 +158,6 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
async def get_data(self, allow_warning: bool = True) -> MinerData:
|
|
||||||
"""Get data from the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
|
||||||
"""
|
|
||||||
data = MinerData(
|
|
||||||
ip=str(self.ip),
|
|
||||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
|
||||||
ideal_hashboards=self.ideal_hashboards,
|
|
||||||
)
|
|
||||||
|
|
||||||
mac = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
model = await self.get_model()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get model: {self}")
|
|
||||||
model = None
|
|
||||||
data.model = "Whatsminer"
|
|
||||||
|
|
||||||
try:
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get hostname: {self}")
|
|
||||||
hostname = None
|
|
||||||
data.hostname = "Whatsminer"
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if self.make:
|
|
||||||
data.make = self.make
|
|
||||||
|
|
||||||
await self.get_version()
|
|
||||||
data.api_ver = self.api_ver
|
|
||||||
data.fw_ver = self.fw_ver
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
err_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
try:
|
|
||||||
miner_data = await self.api.multicommand("summary", "devs", "pools", allow_warning=allow_warning)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
err_data = await self.api.get_error_code()
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
if not (miner_data or err_data):
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data["summary"][0] if miner_data.get("summary") else None
|
|
||||||
devs = miner_data["devs"][0] if miner_data.get("devs") else None
|
|
||||||
pools = miner_data["pools"][0] if miner_data.get("pools") else None
|
|
||||||
try:
|
|
||||||
psu_data = await self.api.get_psu()
|
|
||||||
except APIError:
|
|
||||||
psu_data = None
|
|
||||||
if not err_data:
|
|
||||||
try:
|
|
||||||
err_data = await self.api.get_error_code()
|
|
||||||
except APIError:
|
|
||||||
err_data = None
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
summary_data = summary.get("SUMMARY")
|
|
||||||
if summary_data:
|
|
||||||
if len(summary_data) > 0:
|
|
||||||
wattage_limit = None
|
|
||||||
if summary_data[0].get("MAC"):
|
|
||||||
mac = summary_data[0]["MAC"]
|
|
||||||
|
|
||||||
if summary_data[0].get("Env Temp"):
|
|
||||||
data.env_temp = summary_data[0]["Env Temp"]
|
|
||||||
|
|
||||||
if summary_data[0].get("Power Limit"):
|
|
||||||
wattage_limit = summary_data[0]["Power Limit"]
|
|
||||||
|
|
||||||
if summary_data[0].get("Power Fanspeed"):
|
|
||||||
data.fan_psu = summary_data[0]["Power Fanspeed"]
|
|
||||||
|
|
||||||
if self.fan_count > 0:
|
|
||||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
|
||||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
|
||||||
|
|
||||||
hr = summary_data[0].get("MHS 1m")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000000, 2)
|
|
||||||
|
|
||||||
wattage = summary_data[0].get("Power")
|
|
||||||
if wattage:
|
|
||||||
data.wattage = round(wattage)
|
|
||||||
|
|
||||||
if not wattage_limit:
|
|
||||||
wattage_limit = round(wattage)
|
|
||||||
|
|
||||||
data.wattage_limit = wattage_limit
|
|
||||||
|
|
||||||
if summary_data[0].get("Error Code Count"):
|
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
|
||||||
if not summary_data[0][f"Error Code {i}"] == "":
|
|
||||||
data.errors.append(
|
|
||||||
WhatsminerError(
|
|
||||||
error_code=summary_data[0][
|
|
||||||
f"Error Code {i}"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if psu_data:
|
|
||||||
psu = psu_data.get("Msg")
|
|
||||||
if psu:
|
|
||||||
if psu.get("fan_speed"):
|
|
||||||
data.fan_psu = psu["fan_speed"]
|
|
||||||
|
|
||||||
if err_data:
|
|
||||||
if err_data.get("Msg"):
|
|
||||||
if err_data["Msg"].get("error_code"):
|
|
||||||
for err in err_data["Msg"]["error_code"]:
|
|
||||||
if isinstance(err, dict):
|
|
||||||
for code in err:
|
|
||||||
data.errors.append(
|
|
||||||
WhatsminerError(error_code=int(code))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data.errors.append(WhatsminerError(error_code=int(err)))
|
|
||||||
|
|
||||||
if devs:
|
|
||||||
dev_data = devs.get("DEVS")
|
|
||||||
if dev_data:
|
|
||||||
for board in dev_data:
|
|
||||||
temp_board = HashBoard(
|
|
||||||
slot=board["ASC"],
|
|
||||||
chip_temp=round(board["Chip Temp Avg"]),
|
|
||||||
temp=round(board["Temperature"]),
|
|
||||||
hashrate=round(board["MHS 1m"] / 1000000, 2),
|
|
||||||
chips=board["Effective Chips"],
|
|
||||||
missing=False if board["Effective Chips"] > 0 else True,
|
|
||||||
expected_chips=self.nominal_chips,
|
|
||||||
)
|
|
||||||
data.hashboards.append(temp_board)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools.get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
pool_1_quota = pool["Quota"]
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
if not mac:
|
|
||||||
try:
|
|
||||||
mac = await self.get_mac()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get mac: {self}")
|
|
||||||
mac = None
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def get_version(self) -> Union[dict, bool]:
|
|
||||||
"""Get miner firmware version.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner api & firmware version or None.
|
|
||||||
"""
|
|
||||||
# Check to see if the version info is already cached
|
|
||||||
if self.api_ver and self.fw_ver:
|
|
||||||
return {"api_ver": self.api_ver, "fw_ver": self.fw_ver}
|
|
||||||
data = await self.api.get_version()
|
|
||||||
if "Code" in data.keys():
|
|
||||||
if data["Code"] == 131:
|
|
||||||
self.api_ver = data["Msg"]["api_ver"].replace("whatsminer v", "")
|
|
||||||
self.fw_ver = data["Msg"]["fw_ver"]
|
|
||||||
self.api.api_ver = self.api_ver
|
|
||||||
return {"api_ver": self.api_ver, "fw_ver": self.fw_ver}
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
await self.api.adjust_power_limit(wattage)
|
await self.api.adjust_power_limit(wattage)
|
||||||
@@ -494,3 +166,434 @@ class BTMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
async def get_mac(
|
||||||
|
self, api_summary: dict = None, api_miner_info: dict = None
|
||||||
|
) -> Optional[str]:
|
||||||
|
if not api_miner_info:
|
||||||
|
try:
|
||||||
|
api_miner_info = await self.api.get_miner_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_miner_info:
|
||||||
|
try:
|
||||||
|
mac = api_miner_info["Msg"]["mac"]
|
||||||
|
return str(mac).upper()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
mac = api_summary["SUMMARY"][0]["MAC"]
|
||||||
|
return str(mac).upper()
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||||
|
if self.model:
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
|
||||||
|
if not api_devdetails:
|
||||||
|
try:
|
||||||
|
api_devdetails = await self.api.devdetails()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_devdetails:
|
||||||
|
try:
|
||||||
|
self.model = api_devdetails["DEVDETAILS"][0]["Model"].split("V")[0]
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
except (TypeError, IndexError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.warning(f"Failed to get model for miner: {self}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_version(
|
||||||
|
self, api_version: dict = None
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
# check if version is cached
|
||||||
|
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
|
||||||
|
# Check to see if the version info is already cached
|
||||||
|
if self.api_ver and self.fw_ver:
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
if not api_version:
|
||||||
|
try:
|
||||||
|
api_version = await self.api.get_version()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_version:
|
||||||
|
if "Code" in api_version.keys():
|
||||||
|
if api_version["Code"] == 131:
|
||||||
|
self.api_ver = api_version["Msg"]["api_ver"].replace(
|
||||||
|
"whatsminer v", ""
|
||||||
|
)
|
||||||
|
self.fw_ver = api_version["Msg"]["fw_ver"]
|
||||||
|
self.api.api_ver = self.api_ver
|
||||||
|
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
async def get_hostname(self, api_miner_info: dict = None) -> Optional[str]:
|
||||||
|
if self.hostname:
|
||||||
|
return self.hostname
|
||||||
|
|
||||||
|
if not api_miner_info:
|
||||||
|
try:
|
||||||
|
api_miner_info = await self.api.get_miner_info()
|
||||||
|
except APIError:
|
||||||
|
return None # only one way to get this
|
||||||
|
|
||||||
|
if api_miner_info:
|
||||||
|
try:
|
||||||
|
self.hostname = api_miner_info["Msg"]["hostname"]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.hostname
|
||||||
|
|
||||||
|
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||||
|
# get hr from API
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
|
||||||
|
|
||||||
|
hashboards = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
||||||
|
for i in range(self.ideal_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not api_devs:
|
||||||
|
try:
|
||||||
|
api_devs = await self.api.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_devs:
|
||||||
|
try:
|
||||||
|
for board in api_devs["DEVS"]:
|
||||||
|
if len(hashboards) < board["ASC"] + 1:
|
||||||
|
hashboards.append(
|
||||||
|
HashBoard(
|
||||||
|
slot=board["ASC"], expected_chips=self.nominal_chips
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.ideal_hashboards += 1
|
||||||
|
hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"])
|
||||||
|
hashboards[board["ASC"]].temp = round(board["Temperature"])
|
||||||
|
hashboards[board["ASC"]].hashrate = round(
|
||||||
|
float(board["MHS 1m"] / 1000000), 2
|
||||||
|
)
|
||||||
|
hashboards[board["ASC"]].chips = board["Effective Chips"]
|
||||||
|
hashboards[board["ASC"]].missing = False
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
async def get_env_temp(self, api_summary: dict = None) -> Optional[float]:
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return api_summary["SUMMARY"][0]["Env Temp"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_wattage(self, api_summary: dict = None) -> Optional[int]:
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return api_summary["SUMMARY"][0]["Power"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return api_summary["SUMMARY"][0]["Power Limit"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_fans(
|
||||||
|
self, api_summary: dict = None, api_psu: dict = None
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
|
||||||
|
Tuple[Optional[int]],
|
||||||
|
]:
|
||||||
|
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
|
||||||
|
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
|
||||||
|
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
|
||||||
|
|
||||||
|
fans = fan_speeds(None, None, None, None)
|
||||||
|
psu_fans = psu_fan_speeds(None)
|
||||||
|
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
if self.fan_count > 0:
|
||||||
|
fans = fan_speeds(
|
||||||
|
api_summary["SUMMARY"][0]["Fan Speed In"],
|
||||||
|
api_summary["SUMMARY"][0]["Fan Speed Out"],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
psu_fans = psu_fan_speeds(
|
||||||
|
int(api_summary["SUMMARY"][0]["Power Fanspeed"])
|
||||||
|
)
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not psu_fans[0]:
|
||||||
|
if not api_psu:
|
||||||
|
try:
|
||||||
|
api_psu = await self.api.get_psu()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_psu:
|
||||||
|
try:
|
||||||
|
psu_fans = psu_fan_speeds(int(api_psu["Msg"]["fan_speed"]))
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return miner_fan_speeds(fans, psu_fans)
|
||||||
|
|
||||||
|
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not api_pools:
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_pools:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(api_pools["POOLS"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["URL"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_errors(
|
||||||
|
self, api_summary: dict = None, api_error_codes: dict = None
|
||||||
|
) -> List[MinerErrorData]:
|
||||||
|
errors = []
|
||||||
|
if not api_summary and not api_error_codes:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
|
||||||
|
errors.append(
|
||||||
|
WhatsminerError(
|
||||||
|
error_code=api_summary["SUMMARY"][0][f"Error Code {i}"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not api_error_codes:
|
||||||
|
try:
|
||||||
|
api_error_codes = await self.api.get_error_code()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_error_codes:
|
||||||
|
for err in api_error_codes["Msg"]["error_code"]:
|
||||||
|
if isinstance(err, dict):
|
||||||
|
for code in err:
|
||||||
|
errors.append(WhatsminerError(error_code=int(code)))
|
||||||
|
else:
|
||||||
|
errors.append(WhatsminerError(error_code=int(err)))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
async def get_fault_light(self, api_miner_info: dict = None) -> bool:
|
||||||
|
data = None
|
||||||
|
|
||||||
|
if not api_miner_info:
|
||||||
|
try:
|
||||||
|
api_miner_info = await self.api.get_miner_info()
|
||||||
|
except APIError:
|
||||||
|
if not self.light:
|
||||||
|
self.light = False
|
||||||
|
|
||||||
|
if api_miner_info:
|
||||||
|
try:
|
||||||
|
self.light = api_miner_info["Msg"]["ledstat"] == "auto"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.light if self.light else False
|
||||||
|
|
||||||
|
async def _get_data(self, allow_warning: bool) -> dict:
|
||||||
|
miner_data = None
|
||||||
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"get_version",
|
||||||
|
"pools",
|
||||||
|
"devdetails",
|
||||||
|
"devs",
|
||||||
|
"get_psu",
|
||||||
|
"get_miner_info",
|
||||||
|
"get_error_code",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if miner_data:
|
||||||
|
break
|
||||||
|
if miner_data:
|
||||||
|
summary = miner_data.get("summary")
|
||||||
|
if summary:
|
||||||
|
summary = summary[0]
|
||||||
|
version = miner_data.get("get_version")
|
||||||
|
if version:
|
||||||
|
version = version[0]
|
||||||
|
pools = miner_data.get("pools")
|
||||||
|
if pools:
|
||||||
|
pools = pools[0]
|
||||||
|
devdetails = miner_data.get("devdetails")
|
||||||
|
if devdetails:
|
||||||
|
devdetails = devdetails[0]
|
||||||
|
devs = miner_data.get("devs")
|
||||||
|
if devs:
|
||||||
|
devs = devs[0]
|
||||||
|
psu = miner_data.get("get_psu")
|
||||||
|
if psu:
|
||||||
|
psu = psu[0]
|
||||||
|
miner_info = miner_data.get("get_miner_info")
|
||||||
|
if miner_info:
|
||||||
|
miner_info = miner_info[0]
|
||||||
|
error_codes = miner_data.get("get_error_codes")
|
||||||
|
if error_codes:
|
||||||
|
error_codes = error_codes[0]
|
||||||
|
else:
|
||||||
|
summary, version, pools, devdetails, devs, psu, miner_info, error_codes = (
|
||||||
|
None for _ in range(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = { # noqa - Ignore dictionary could be re-written
|
||||||
|
# ip - Done at start
|
||||||
|
# datetime - Done auto
|
||||||
|
"mac": await self.get_mac(api_summary=summary, api_miner_info=miner_info),
|
||||||
|
"model": await self.get_model(api_devdetails=devdetails),
|
||||||
|
# make - Done at start
|
||||||
|
"api_ver": None, # - Done at end
|
||||||
|
"fw_ver": None, # - Done at end
|
||||||
|
"hostname": await self.get_hostname(api_miner_info=miner_info),
|
||||||
|
"hashrate": await self.get_hashrate(api_summary=summary),
|
||||||
|
"hashboards": await self.get_hashboards(api_devs=devs),
|
||||||
|
# ideal_hashboards - Done at start
|
||||||
|
"env_temp": await self.get_env_temp(api_summary=summary),
|
||||||
|
"wattage": await self.get_wattage(api_summary=summary),
|
||||||
|
"wattage_limit": await self.get_wattage_limit(api_summary=summary),
|
||||||
|
"fan_1": None, # - Done at end
|
||||||
|
"fan_2": None, # - Done at end
|
||||||
|
"fan_3": None, # - Done at end
|
||||||
|
"fan_4": None, # - Done at end
|
||||||
|
"fan_psu": None, # - Done at end
|
||||||
|
# ideal_chips - Done at start
|
||||||
|
"pool_split": None, # - Done at end
|
||||||
|
"pool_1_url": None, # - Done at end
|
||||||
|
"pool_1_user": None, # - Done at end
|
||||||
|
"pool_2_url": None, # - Done at end
|
||||||
|
"pool_2_user": None, # - Done at end
|
||||||
|
"errors": await self.get_errors(
|
||||||
|
api_summary=summary, api_error_codes=error_codes
|
||||||
|
),
|
||||||
|
"fault_light": await self.get_fault_light(api_miner_info=miner_info),
|
||||||
|
}
|
||||||
|
|
||||||
|
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
|
||||||
|
fan_data = await self.get_fans()
|
||||||
|
|
||||||
|
if fan_data:
|
||||||
|
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
|
||||||
|
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
|
||||||
|
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
|
||||||
|
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
|
||||||
|
|
||||||
|
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
|
||||||
|
|
||||||
|
pools_data = await self.get_pools(api_pools=pools)
|
||||||
|
|
||||||
|
if pools_data:
|
||||||
|
data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||||
|
data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||||
|
if len(pools_data) > 1:
|
||||||
|
data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||||
|
data[
|
||||||
|
"pool_split"
|
||||||
|
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data["pool_2_url"] = pools_data[0]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[0]["pool_2_user"]
|
||||||
|
data["quota"] = "0"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from typing import List, Union, Tuple, Optional
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import asyncssh
|
||||||
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
from pyasic.API.cgminer import CGMinerAPI
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -26,7 +29,7 @@ from pyasic.settings import PyasicSettings
|
|||||||
|
|
||||||
|
|
||||||
class CGMiner(BaseMiner):
|
class CGMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api = CGMinerAPI(ip, api_ver)
|
self.api = CGMinerAPI(ip, api_ver)
|
||||||
@@ -36,63 +39,32 @@ class CGMiner(BaseMiner):
|
|||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
if self.model:
|
|
||||||
return self.model
|
|
||||||
try:
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
except APIError:
|
|
||||||
return None
|
|
||||||
if version_data:
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
|
||||||
return self.model
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or "?"
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
if conn is not None:
|
|
||||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
|
||||||
host = data.stdout.strip()
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
cmd: The command to run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
result = None
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
|
try:
|
||||||
|
conn = await self._get_ssh_connection()
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# open an ssh connection
|
||||||
|
async with conn:
|
||||||
|
# 3 retries
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
|
# run the command and get the result
|
||||||
result = await conn.run(cmd)
|
result = await conn.run(cmd)
|
||||||
result = result.stdout
|
result = result.stdout
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{cmd} error: {e}")
|
# if the command fails, log it
|
||||||
|
logging.warning(f"{self} command {cmd} error: {e}")
|
||||||
|
|
||||||
|
# on the 3rd retry, return None
|
||||||
if i == 3:
|
if i == 3:
|
||||||
return
|
return
|
||||||
continue
|
continue
|
||||||
|
# return the result, either command output or None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
@@ -103,7 +75,11 @@ class CGMiner(BaseMiner):
|
|||||||
"""Restart cgminer hashing process."""
|
"""Restart cgminer hashing process."""
|
||||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
||||||
commands = ";".join(commands)
|
commands = ";".join(commands)
|
||||||
|
try:
|
||||||
_ret = await self.send_ssh_command(commands)
|
_ret = await self.send_ssh_command(commands)
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
if isinstance(_ret, str):
|
if isinstance(_ret, str):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -111,13 +87,18 @@ class CGMiner(BaseMiner):
|
|||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
"""Reboots power to the physical miner."""
|
"""Reboots power to the physical miner."""
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
logging.debug(f"{self}: Sending reboot command.")
|
||||||
|
try:
|
||||||
_ret = await self.send_ssh_command("reboot")
|
_ret = await self.send_ssh_command("reboot")
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
logging.debug(f"{self}: Reboot command completed.")
|
||||||
if isinstance(_ret, str):
|
if isinstance(_ret, str):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
|
try:
|
||||||
commands = [
|
commands = [
|
||||||
"mkdir -p /etc/tmp/",
|
"mkdir -p /etc/tmp/",
|
||||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
||||||
@@ -126,9 +107,13 @@ class CGMiner(BaseMiner):
|
|||||||
]
|
]
|
||||||
commands = ";".join(commands)
|
commands = ";".join(commands)
|
||||||
await self.send_ssh_command(commands)
|
await self.send_ssh_command(commands)
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
|
try:
|
||||||
commands = [
|
commands = [
|
||||||
"mkdir -p /etc/tmp/",
|
"mkdir -p /etc/tmp/",
|
||||||
'echo "" > /etc/tmp/root',
|
'echo "" > /etc/tmp/root',
|
||||||
@@ -137,114 +122,130 @@ class CGMiner(BaseMiner):
|
|||||||
]
|
]
|
||||||
commands = ";".join(commands)
|
commands = ";".join(commands)
|
||||||
await self.send_ssh_command(commands)
|
await self.send_ssh_command(commands)
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_config(self) -> str:
|
async def get_config(self, api_pools: dict = None) -> MinerConfig:
|
||||||
"""Gets the config for the miner and sets it as `self.config`.
|
# get pool data
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
Returns:
|
if api_pools:
|
||||||
The config from `self.config`.
|
self.config = MinerConfig().from_api(api_pools["POOLS"])
|
||||||
"""
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
command = "cat /etc/config/cgminer"
|
|
||||||
result = await conn.run(command, check=True)
|
|
||||||
self.config = result.stdout
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
return "00:00:00:00:00:00"
|
return False
|
||||||
|
|
||||||
async def get_version(self) -> dict:
|
##################################################
|
||||||
"""Get miner firmware version.
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
|
##################################################
|
||||||
|
|
||||||
Returns:
|
async def get_mac(self) -> Optional[str]:
|
||||||
Miner api & firmware version or None.
|
return None
|
||||||
"""
|
|
||||||
# check if version is cached
|
|
||||||
if self.fw_ver and self.api_ver:
|
|
||||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
|
||||||
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
|
|
||||||
# Now get the API version
|
|
||||||
version = await self.api.version()
|
|
||||||
self.api_ver = version['VERSION'][0]['API']
|
|
||||||
self.fw_ver = version['VERSION'][0]['CGMiner']
|
|
||||||
self.api.api_ver = self.api_ver
|
|
||||||
return {'api_ver': self.api_ver,'fw_ver': self.fw_ver}
|
|
||||||
|
|
||||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||||
"""Get data from the miner.
|
if self.model:
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
|
||||||
Returns:
|
if not api_devdetails:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
try:
|
||||||
"""
|
api_devdetails = await self.api.devdetails()
|
||||||
data = MinerData(
|
except APIError:
|
||||||
ip=str(self.ip),
|
pass
|
||||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
|
||||||
ideal_hashboards=self.ideal_hashboards,
|
if api_devdetails:
|
||||||
|
try:
|
||||||
|
self.model = api_devdetails["DEVDETAILS"][0]["Model"].replace(
|
||||||
|
"Antminer ", ""
|
||||||
)
|
)
|
||||||
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
|
return self.model
|
||||||
|
except (TypeError, IndexError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.warning(f"Failed to get model for miner: {self}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_version(
|
||||||
|
self, api_version: dict = None
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
|
||||||
|
# Check to see if the version info is already cached
|
||||||
|
if self.api_ver and self.fw_ver:
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
if not api_version:
|
||||||
|
try:
|
||||||
|
api_version = await self.api.version()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_version:
|
||||||
|
try:
|
||||||
|
self.api_ver = api_version["VERSION"][0]["API"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return miner_version(self.api_ver, self.fw_ver)
|
||||||
|
|
||||||
|
async def get_hostname(self) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||||
|
except (asyncssh.Error, OSError):
|
||||||
|
return None
|
||||||
|
if hn:
|
||||||
|
self.hostname = hn
|
||||||
|
return self.hostname
|
||||||
|
|
||||||
|
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||||
|
# get hr from API
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return round(
|
||||||
|
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||||
|
)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||||
|
hashboards = []
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
fan_offset = -1
|
boards = api_stats["STATS"]
|
||||||
|
if len(boards) > 1:
|
||||||
model = await self.get_model()
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary", "pools", "stats", allow_warning=allow_warning
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
|
||||||
pools = miner_data.get("pools")[0]
|
|
||||||
stats = miner_data.get("stats")[0]
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary.get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("GHS av")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000, 2)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
boards = stats.get("STATS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||||
@@ -254,7 +255,6 @@ class CGMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
env_temp_list = []
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||||
@@ -278,81 +278,193 @@ class CGMiner(BaseMiner):
|
|||||||
hashboard.missing = False
|
hashboard.missing = False
|
||||||
if (not chips) or (not chips > 0):
|
if (not chips) or (not chips > 0):
|
||||||
hashboard.missing = True
|
hashboard.missing = True
|
||||||
data.hashboards.append(hashboard)
|
hashboards.append(hashboard)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
if f"temp_pcb{i}" in boards[1].keys():
|
return hashboards
|
||||||
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
|
|
||||||
if not env_temp == 0:
|
async def get_env_temp(self) -> Optional[float]:
|
||||||
env_temp_list.append(int(env_temp))
|
return None
|
||||||
if not env_temp_list == []:
|
|
||||||
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
|
async def get_wattage(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage_limit(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_fans(
|
||||||
|
self, api_stats: dict = None
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
|
||||||
|
Tuple[Optional[int]],
|
||||||
|
]:
|
||||||
|
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
|
||||||
|
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
|
||||||
|
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
|
||||||
|
|
||||||
|
psu_fans = psu_fan_speeds(None)
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans_data = [None, None, None, None]
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
|
fan_offset = -1
|
||||||
|
|
||||||
if stats:
|
|
||||||
temp = stats.get("STATS")
|
|
||||||
if temp:
|
|
||||||
if len(temp) > 1:
|
|
||||||
for fan_num in range(1, 8, 4):
|
for fan_num in range(1, 8, 4):
|
||||||
for _f_num in range(4):
|
for _f_num in range(4):
|
||||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||||
if f and not f == 0 and fan_offset == -1:
|
if f and not f == 0 and fan_offset == -1:
|
||||||
fan_offset = fan_num
|
fan_offset = fan_num
|
||||||
if fan_offset == -1:
|
if fan_offset == -1:
|
||||||
fan_offset = 1
|
fan_offset = 1
|
||||||
|
|
||||||
for fan in range(self.fan_count):
|
for fan in range(self.fan_count):
|
||||||
setattr(
|
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
|
||||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
except (KeyError, IndexError):
|
||||||
)
|
pass
|
||||||
|
fans = fan_speeds(*fans_data)
|
||||||
|
|
||||||
|
return miner_fan_speeds(fans, psu_fans)
|
||||||
|
|
||||||
|
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not api_pools:
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_pools:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(api_pools["POOLS"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["URL"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_fault_light(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _get_data(self, allow_warning: bool) -> dict:
|
||||||
|
miner_data = None
|
||||||
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"pools",
|
||||||
|
"devdetails",
|
||||||
|
"stats",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"pools",
|
||||||
|
"version",
|
||||||
|
"stats",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if miner_data:
|
||||||
|
break
|
||||||
|
if miner_data:
|
||||||
|
summary = miner_data.get("summary")
|
||||||
|
if summary:
|
||||||
|
summary = summary[0]
|
||||||
|
pools = miner_data.get("pools")
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pools = pools[0]
|
||||||
pool_2 = None
|
version = miner_data.get("version")
|
||||||
pool_1_user = None
|
if version:
|
||||||
pool_2_user = None
|
version = version[0]
|
||||||
pool_1_quota = 1
|
devdetails = miner_data.get("devdetails")
|
||||||
pool_2_quota = 1
|
if devdetails:
|
||||||
quota = 0
|
devdetails = devdetails[0]
|
||||||
for pool in pools.get("POOLS"):
|
stats = miner_data.get("stats")
|
||||||
if not pool_1_user:
|
if stats:
|
||||||
pool_1_user = pool.get("User")
|
stats = stats[0]
|
||||||
pool_1 = pool["URL"]
|
else:
|
||||||
if pool.get("Quota"):
|
summary, pools, devdetails, version, stats = (None for _ in range(5))
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
if pool.get("Quota"):
|
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
if pool.get("Quota"):
|
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
data = { # noqa - Ignore dictionary could be re-written
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
# ip - Done at start
|
||||||
"stratum2+tcp://", ""
|
# datetime - Done auto
|
||||||
)
|
"mac": await self.get_mac(),
|
||||||
data.pool_1_url = pool_1
|
"model": await self.get_model(api_devdetails=devdetails),
|
||||||
|
# make - Done at start
|
||||||
|
"api_ver": None, # - Done at end
|
||||||
|
"fw_ver": None, # - Done at end
|
||||||
|
"hostname": await self.get_hostname(),
|
||||||
|
"hashrate": await self.get_hashrate(api_summary=summary),
|
||||||
|
"hashboards": await self.get_hashboards(api_stats=stats),
|
||||||
|
# ideal_hashboards - Done at start
|
||||||
|
"env_temp": await self.get_env_temp(),
|
||||||
|
"wattage": await self.get_wattage(),
|
||||||
|
"wattage_limit": await self.get_wattage_limit(),
|
||||||
|
"fan_1": None, # - Done at end
|
||||||
|
"fan_2": None, # - Done at end
|
||||||
|
"fan_3": None, # - Done at end
|
||||||
|
"fan_4": None, # - Done at end
|
||||||
|
"fan_psu": None, # - Done at end
|
||||||
|
# ideal_chips - Done at start
|
||||||
|
"pool_split": None, # - Done at end
|
||||||
|
"pool_1_url": None, # - Done at end
|
||||||
|
"pool_1_user": None, # - Done at end
|
||||||
|
"pool_2_url": None, # - Done at end
|
||||||
|
"pool_2_user": None, # - Done at end
|
||||||
|
"errors": await self.get_errors(),
|
||||||
|
"fault_light": await self.get_fault_light(),
|
||||||
|
}
|
||||||
|
|
||||||
if pool_1_user:
|
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
|
||||||
data.pool_1_user = pool_1_user
|
fan_data = await self.get_fans()
|
||||||
|
|
||||||
if pool_2:
|
if fan_data:
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
|
||||||
"stratum2+tcp://", ""
|
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
|
||||||
)
|
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
|
||||||
data.pool_2_url = pool_2
|
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
|
||||||
|
|
||||||
if pool_2_user:
|
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
pools_data = await self.get_pools(api_pools=pools)
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
if pools_data:
|
||||||
|
data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||||
|
data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||||
|
if len(pools_data) > 1:
|
||||||
|
data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||||
|
data[
|
||||||
|
"pool_split"
|
||||||
|
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data["pool_2_url"] = pools_data[0]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[0]["pool_2_user"]
|
||||||
|
data["quota"] = "0"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Union
|
from typing import List, Union, Tuple, Optional
|
||||||
|
from collections import namedtuple
|
||||||
|
import re
|
||||||
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
from pyasic.API.cgminer import CGMinerAPI
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -27,33 +29,39 @@ from pyasic.miners._backends import CGMiner
|
|||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon(CGMiner):
|
class CGMinerAvalon(CGMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.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.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if self.light:
|
|
||||||
return self.light
|
|
||||||
data = await self.api.ascset(0, "led", "1-255")
|
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
|
try:
|
||||||
data = await self.api.ascset(0, "led", "1-1")
|
data = await self.api.ascset(0, "led", "1-1")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
|
try:
|
||||||
data = await self.api.ascset(0, "led", "1-0")
|
data = await self.api.ascset(0, "led", "1-0")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
if (await self.api.restart())["STATUS"] == "RESTART":
|
try:
|
||||||
|
data = await self.api.restart()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if data["STATUS"] == "RESTART":
|
||||||
return True
|
return True
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
@@ -64,182 +72,16 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
"""Configures miner with yaml config."""
|
"""Configures miner with yaml config."""
|
||||||
raise NotImplementedError
|
return None
|
||||||
logging.debug(f"{self}: Sending config.")
|
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:
|
||||||
data = await self.api.ascset(
|
data = await self.api.ascset(
|
||||||
0, "setpool", f"root,root,{conf}"
|
0, "setpool", f"root,root,{conf}"
|
||||||
) # this should work but doesn't
|
) # this should work but doesn't
|
||||||
return data
|
except APIError:
|
||||||
|
pass
|
||||||
async def get_mac(self) -> str:
|
# return data
|
||||||
mac = None
|
|
||||||
version = await self.api.version()
|
|
||||||
if version:
|
|
||||||
if "VERSION" in version.keys():
|
|
||||||
if "MAC" in version["VERSION"][0].keys():
|
|
||||||
base_mac = version["VERSION"][0]["MAC"].upper()
|
|
||||||
# parse the MAC into a recognizable form
|
|
||||||
mac = ":".join(
|
|
||||||
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
|
||||||
)
|
|
||||||
return mac
|
|
||||||
|
|
||||||
async def get_data(self, allow_warning: bool = True):
|
|
||||||
data = MinerData(
|
|
||||||
ip=str(self.ip),
|
|
||||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
|
||||||
ideal_hashboards=self.ideal_hashboards,
|
|
||||||
hashboards=[
|
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
|
||||||
for i in range(self.ideal_hashboards)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
model = await self.get_model()
|
|
||||||
mac = None
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if self.make:
|
|
||||||
data.make = self.make
|
|
||||||
|
|
||||||
await self.get_version()
|
|
||||||
data.api_ver = self.api_ver
|
|
||||||
data.fw_ver = self.fw_ver
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"version", "summary", "pools", "stats", allow_warning=allow_warning
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
if not miner_data:
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
|
|
||||||
if hostname and not hostname == "?":
|
|
||||||
data.hostname = hostname
|
|
||||||
elif mac:
|
|
||||||
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")
|
|
||||||
version = miner_data.get("version")
|
|
||||||
pools = miner_data.get("pools")
|
|
||||||
stats = miner_data.get("stats")
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary[0].get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("MHS 1m")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000000, 2)
|
|
||||||
|
|
||||||
if version:
|
|
||||||
if "VERSION" in version[0].keys():
|
|
||||||
if "MAC" in version[0]["VERSION"][0].keys():
|
|
||||||
base_mac = version[0]["VERSION"][0]["MAC"].upper()
|
|
||||||
# parse the MAC into a recognizable form
|
|
||||||
mac = ":".join(
|
|
||||||
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
|
||||||
)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
stats_data = stats[0].get("STATS")
|
|
||||||
if stats_data:
|
|
||||||
for key in stats_data[0].keys():
|
|
||||||
if key.startswith("MM ID"):
|
|
||||||
raw_data = self.parse_stats(stats_data[0][key])
|
|
||||||
for fan in range(self.fan_count):
|
|
||||||
if f"Fan{fan+1}" in raw_data:
|
|
||||||
setattr(
|
|
||||||
data,
|
|
||||||
f"fan_{fan+1}",
|
|
||||||
int(raw_data[f"Fan{fan+1}"]),
|
|
||||||
)
|
|
||||||
for board in range(self.ideal_hashboards):
|
|
||||||
chip_temp = raw_data.get("MTmax")
|
|
||||||
if chip_temp:
|
|
||||||
data.hashboards[board].chip_temp = chip_temp[board]
|
|
||||||
|
|
||||||
temp = raw_data.get("MTavg")
|
|
||||||
if temp:
|
|
||||||
data.hashboards[board].temp = temp[board]
|
|
||||||
|
|
||||||
chips = raw_data.get(f"PVT_T{board}")
|
|
||||||
if chips:
|
|
||||||
data.hashboards[board].chips = len(
|
|
||||||
[item for item in chips if not item == "0"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools[0].get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
pool_1_quota = pool["Quota"]
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
else:
|
|
||||||
mac = await self.get_mac()
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
if hostname and not hostname == "?":
|
|
||||||
data.hostname = hostname
|
|
||||||
elif mac:
|
|
||||||
data.hostname = f"Avalon{mac.replace(':', '')[-6:]}"
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_stats(stats):
|
def parse_stats(stats):
|
||||||
@@ -274,3 +116,263 @@ class CGMinerAvalon(CGMiner):
|
|||||||
stats_items.append(raw_data)
|
stats_items.append(raw_data)
|
||||||
|
|
||||||
return stats_dict
|
return stats_dict
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
async def get_mac(self, api_version: dict = None) -> Optional[str]:
|
||||||
|
if not api_version:
|
||||||
|
try:
|
||||||
|
api_version = await self.api.version()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_version:
|
||||||
|
try:
|
||||||
|
base_mac = api_version["VERSION"][0]["MAC"]
|
||||||
|
base_mac = base_mac.upper()
|
||||||
|
mac = ":".join(
|
||||||
|
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
||||||
|
)
|
||||||
|
return mac
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
||||||
|
if not mac:
|
||||||
|
mac = await self.get_mac()
|
||||||
|
|
||||||
|
if mac:
|
||||||
|
return f"Avalon{mac.replace(':', '')[-6:]}"
|
||||||
|
|
||||||
|
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||||
|
if not api_summary:
|
||||||
|
try:
|
||||||
|
api_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_summary:
|
||||||
|
try:
|
||||||
|
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||||
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||||
|
hashboards = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
||||||
|
for i in range(self.ideal_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
|
stats_data = api_stats[0].get("STATS")
|
||||||
|
if stats_data:
|
||||||
|
for key in stats_data[0].keys():
|
||||||
|
if key.startswith("MM ID"):
|
||||||
|
raw_data = self.parse_stats(stats_data[0][key])
|
||||||
|
for board in range(self.ideal_hashboards):
|
||||||
|
chip_temp = raw_data.get("MTmax")
|
||||||
|
if chip_temp:
|
||||||
|
hashboards[board].chip_temp = chip_temp[board]
|
||||||
|
|
||||||
|
temp = raw_data.get("MTavg")
|
||||||
|
if temp:
|
||||||
|
hashboards[board].temp = temp[board]
|
||||||
|
|
||||||
|
chips = raw_data.get(f"PVT_T{board}")
|
||||||
|
if chips:
|
||||||
|
hashboards[board].chips = len(
|
||||||
|
[item for item in chips if not item == "0"]
|
||||||
|
)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
async def get_env_temp(self) -> Optional[float]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_wattage_limit(self) -> Optional[int]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_fans(
|
||||||
|
self, api_stats: dict = None
|
||||||
|
) -> Tuple[
|
||||||
|
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
|
||||||
|
Tuple[Optional[int]],
|
||||||
|
]:
|
||||||
|
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
|
||||||
|
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
|
||||||
|
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
|
||||||
|
|
||||||
|
psu_fans = psu_fan_speeds(None)
|
||||||
|
|
||||||
|
if not api_stats:
|
||||||
|
try:
|
||||||
|
api_stats = await self.api.stats()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans_data = [None, None, None, None]
|
||||||
|
if api_stats:
|
||||||
|
try:
|
||||||
|
stats_data = api_stats[0].get("STATS")
|
||||||
|
if stats_data:
|
||||||
|
for key in stats_data[0].keys():
|
||||||
|
if key.startswith("MM ID"):
|
||||||
|
raw_data = self.parse_stats(stats_data[0][key])
|
||||||
|
for fan in range(self.fan_count):
|
||||||
|
fans_data[fan] = int(raw_data[f"Fan{fan + 1}"])
|
||||||
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
fans = fan_speeds(*fans_data)
|
||||||
|
|
||||||
|
return miner_fan_speeds(fans, psu_fans)
|
||||||
|
|
||||||
|
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not api_pools:
|
||||||
|
try:
|
||||||
|
api_pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if api_pools:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(api_pools["POOLS"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["URL"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_fault_light(self) -> bool:
|
||||||
|
if self.light:
|
||||||
|
return self.light
|
||||||
|
try:
|
||||||
|
data = await self.api.ascset(0, "led", "1-255")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _get_data(self, allow_warning: bool) -> dict:
|
||||||
|
miner_data = None
|
||||||
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
|
try:
|
||||||
|
miner_data = await self.api.multicommand(
|
||||||
|
"summary",
|
||||||
|
"pools",
|
||||||
|
"version",
|
||||||
|
"devdetails",
|
||||||
|
"stats",
|
||||||
|
allow_warning=allow_warning,
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if miner_data:
|
||||||
|
break
|
||||||
|
if miner_data:
|
||||||
|
summary = miner_data.get("summary")
|
||||||
|
if summary:
|
||||||
|
summary = summary[0]
|
||||||
|
pools = miner_data.get("pools")
|
||||||
|
if pools:
|
||||||
|
pools = pools[0]
|
||||||
|
version = miner_data.get("version")
|
||||||
|
if version:
|
||||||
|
version = version[0]
|
||||||
|
devdetails = miner_data.get("devdetails")
|
||||||
|
if devdetails:
|
||||||
|
devdetails = devdetails[0]
|
||||||
|
stats = miner_data.get("stats")
|
||||||
|
if stats:
|
||||||
|
stats = stats[0]
|
||||||
|
else:
|
||||||
|
summary, pools, devdetails, version, stats = (None for _ in range(5))
|
||||||
|
|
||||||
|
data = { # noqa - Ignore dictionary could be re-written
|
||||||
|
# ip - Done at start
|
||||||
|
# datetime - Done auto
|
||||||
|
"mac": await self.get_mac(),
|
||||||
|
"model": await self.get_model(api_devdetails=devdetails),
|
||||||
|
# make - Done at start
|
||||||
|
"api_ver": None, # - Done at end
|
||||||
|
"fw_ver": None, # - Done at end
|
||||||
|
"hostname": await self.get_hostname(),
|
||||||
|
"hashrate": await self.get_hashrate(api_summary=summary),
|
||||||
|
"hashboards": await self.get_hashboards(api_stats=stats),
|
||||||
|
# ideal_hashboards - Done at start
|
||||||
|
"env_temp": await self.get_env_temp(),
|
||||||
|
"wattage": await self.get_wattage(),
|
||||||
|
"wattage_limit": await self.get_wattage_limit(),
|
||||||
|
"fan_1": None, # - Done at end
|
||||||
|
"fan_2": None, # - Done at end
|
||||||
|
"fan_3": None, # - Done at end
|
||||||
|
"fan_4": None, # - Done at end
|
||||||
|
"fan_psu": None, # - Done at end
|
||||||
|
# ideal_chips - Done at start
|
||||||
|
"pool_split": None, # - Done at end
|
||||||
|
"pool_1_url": None, # - Done at end
|
||||||
|
"pool_1_user": None, # - Done at end
|
||||||
|
"pool_2_url": None, # - Done at end
|
||||||
|
"pool_2_user": None, # - Done at end
|
||||||
|
"errors": await self.get_errors(),
|
||||||
|
"fault_light": await self.get_fault_light(),
|
||||||
|
}
|
||||||
|
|
||||||
|
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
|
||||||
|
fan_data = await self.get_fans()
|
||||||
|
|
||||||
|
if fan_data:
|
||||||
|
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
|
||||||
|
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
|
||||||
|
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
|
||||||
|
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
|
||||||
|
|
||||||
|
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
|
||||||
|
|
||||||
|
pools_data = await self.get_pools(api_pools=pools)
|
||||||
|
|
||||||
|
if pools_data:
|
||||||
|
data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||||
|
data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||||
|
if len(pools_data) > 1:
|
||||||
|
data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||||
|
data[
|
||||||
|
"pool_split"
|
||||||
|
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data["pool_2_url"] = pools_data[0]["pool_2_url"]
|
||||||
|
data["pool_2_user"] = pools_data[0]["pool_2_user"]
|
||||||
|
data["quota"] = "0"
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -18,49 +18,9 @@ from pyasic.miners._backends import BMMiner
|
|||||||
|
|
||||||
|
|
||||||
class Hiveon(BMMiner):
|
class Hiveon(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.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.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api_type = "Hiveon"
|
self.api_type = "Hiveon"
|
||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
|
|
||||||
async def get_board_info(self) -> dict:
|
|
||||||
"""Gets data on each board and chain in the miner."""
|
|
||||||
board_stats = await self.api.stats()
|
|
||||||
stats = board_stats["STATS"][1]
|
|
||||||
boards = {}
|
|
||||||
board_chains = {0: [2, 9, 10], 1: [3, 11, 12], 2: [4, 13, 14]}
|
|
||||||
for idx, board in enumerate(board_chains):
|
|
||||||
boards[board] = []
|
|
||||||
for chain in board_chains[board]:
|
|
||||||
count = stats[f"chain_acn{chain}"]
|
|
||||||
chips = stats[f"chain_acs{chain}"].replace(" ", "")
|
|
||||||
if not count == 18 or "x" in chips:
|
|
||||||
nominal = False
|
|
||||||
else:
|
|
||||||
nominal = True
|
|
||||||
boards[board].append(
|
|
||||||
{
|
|
||||||
"chain": chain,
|
|
||||||
"chip_count": count,
|
|
||||||
"chip_status": chips,
|
|
||||||
"nominal": nominal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return boards
|
|
||||||
|
|
||||||
async def get_bad_boards(self) -> dict:
|
|
||||||
"""Checks for and provides list of non working boards."""
|
|
||||||
boards = await self.get_board_info()
|
|
||||||
bad_boards = {}
|
|
||||||
for board in boards.keys():
|
|
||||||
for chain in boards[board]:
|
|
||||||
if not chain["chip_count"] == 18 or "x" in chain["chip_status"]:
|
|
||||||
if board not in bad_boards.keys():
|
|
||||||
bad_boards[board] = []
|
|
||||||
bad_boards[board].append(chain)
|
|
||||||
return bad_boards
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S17(AntMiner): # noqa - ignore ABC method implementation
|
class S17(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
|
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
|
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S17e(AntMiner): # noqa - ignore ABC method implementation
|
class S17e(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class T17(AntMiner): # noqa - ignore ABC method implementation
|
class T17(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
|
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class T17e(AntMiner): # noqa - ignore ABC method implementation
|
class T17e(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19(AntMiner): # noqa - ignore ABC method implementation
|
class S19(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19XP(AntMiner): # noqa - ignore ABC method implementation
|
class S19XP(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19a(AntMiner): # noqa - ignore ABC method implementation
|
class S19a(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
|
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19j(AntMiner): # noqa - ignore ABC method implementation
|
class S19j(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
|
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S9(AntMiner): # noqa - ignore ABC method implementation
|
class S9(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class S9i(AntMiner): # noqa - ignore ABC method implementation
|
class S9i(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AntMiner
|
from pyasic.miners._types.makes import AntMiner
|
||||||
|
|
||||||
|
|
||||||
class T9(AntMiner): # noqa - ignore ABC method implementation
|
class T9(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import AvalonMiner
|
from pyasic.miners._types.makes import AvalonMiner
|
||||||
|
|
||||||
|
|
||||||
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
|
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import InnosiliconMiner
|
from pyasic.miners._types.makes import InnosiliconMiner
|
||||||
|
|
||||||
|
|
||||||
class InnosiliconT3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation
|
class InnosiliconT3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,21 +14,25 @@
|
|||||||
|
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
|
|
||||||
|
|
||||||
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation
|
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.make = "WhatsMiner"
|
self.make = "WhatsMiner"
|
||||||
|
|
||||||
|
|
||||||
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation
|
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.make = "AntMiner"
|
self.make = "AntMiner"
|
||||||
|
|
||||||
|
|
||||||
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation
|
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.make = "AvalonMiner"
|
self.make = "AvalonMiner"
|
||||||
|
|
||||||
|
|
||||||
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation
|
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M20(WhatsMiner): # noqa - ignore ABC method implementation
|
class M20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M20S(WhatsMiner): # noqa - ignore ABC method implementation
|
class M20S(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M20SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M20SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M21(WhatsMiner): # noqa - ignore ABC method implementation
|
class M21(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M21S(WhatsMiner): # noqa - ignore ABC method implementation
|
class M21S(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M21SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M21SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M30S(WhatsMiner): # noqa - ignore ABC method implementation
|
class M30S(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -57,3 +58,12 @@ class M30SVE10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
self.model = "M30S VE10"
|
self.model = "M30S VE10"
|
||||||
self.nominal_chips = 105
|
self.nominal_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M30SVG10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M30S VG10"
|
||||||
|
self.nominal_chips = 66
|
||||||
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M30SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M30SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -31,6 +32,7 @@ class M30SPlusVG60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
self.nominal_chips = 86
|
self.nominal_chips = 86
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
class M30SPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
class M30SPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -56,3 +58,19 @@ class M30SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
self.model = "M30S+ VF20"
|
self.model = "M30S+ VF20"
|
||||||
self.nominal_chips = 111
|
self.nominal_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
class M30SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M30S+ VH30"
|
||||||
|
self.nominal_chips = 70
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
class M30SPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M30S+ VH60"
|
||||||
|
self.nominal_chips = 66
|
||||||
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M30SPlusPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M30SPlusPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M31S(WhatsMiner): # noqa - ignore ABC method implementation
|
class M31S(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -31,6 +32,7 @@ class M31SV10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
self.nominal_chips = 105
|
self.nominal_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
class M31SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
class M31SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -39,6 +41,7 @@ class M31SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
self.nominal_chips = 111
|
self.nominal_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
class M31SV60(WhatsMiner): # noqa - ignore ABC method implementation
|
class M31SV60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M31SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M31SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M32(WhatsMiner): # noqa - ignore ABC method implementation
|
class M32(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M32S(WhatsMiner): # noqa - ignore ABC method implementation
|
class M32S(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._types.makes import WhatsMiner
|
from pyasic.miners._types.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
class M34SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
class M34SPlus(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str):
|
def __init__(self, ip: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -12,8 +12,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 .M30S import M30S, M30SV50, M30SVE10, M30SVE20, M30SVG20
|
from .M30S import M30S, M30SV50, M30SVE10, M30SVE20, M30SVG20, M30SVG10
|
||||||
from .M30S_Plus import M30SPlus, M30SPlusVE40, M30SPlusVF20, M30SPlusVG60, M30SPlusVG40
|
from .M30S_Plus import M30SPlus, M30SPlusVE40, M30SPlusVF20, M30SPlusVG60, M30SPlusVG40, M30SPlusVH30, M30SPlusVH60
|
||||||
from .M30S_Plus_Plus import (
|
from .M30S_Plus_Plus import (
|
||||||
M30SPlusPlus,
|
M30SPlusPlus,
|
||||||
M30SPlusPlusVG30,
|
M30SPlusPlusVG30,
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS17(BMMinerX17, S17):
|
class BMMinerS17(BMMinerX17, S17):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS17Plus(BMMinerX17, S17Plus):
|
class BMMinerS17Plus(BMMinerX17, S17Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS17Pro(BMMinerX17, S17Pro):
|
class BMMinerS17Pro(BMMinerX17, S17Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS17e(BMMinerX17, S17e):
|
class BMMinerS17e(BMMinerX17, S17e):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerT17(BMMinerX17, T17):
|
class BMMinerT17(BMMinerX17, T17):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerT17Plus(BMMinerX17, T17Plus):
|
class BMMinerT17Plus(BMMinerX17, T17Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X17 import BMMinerX17
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerT17e(BMMinerX17, T17e):
|
class BMMinerT17e(BMMinerX17, T17e):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -12,7 +12,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 typing import Union
|
from typing import Union, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
@@ -21,90 +21,90 @@ from pyasic.settings import PyasicSettings
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerX17(BMMiner):
|
class BMMinerX17(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.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.uname = "root"
|
||||||
self.pwd = PyasicSettings().global_x17_password
|
self.pwd = PyasicSettings().global_x17_password
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
async def send_web_command(
|
||||||
hostname = None
|
self, command: str, params: dict = None
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
) -> Optional[dict]:
|
||||||
|
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
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)
|
data = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
try:
|
||||||
if len(data.keys()) > 0:
|
return data.json()
|
||||||
if "hostname" in data.keys():
|
except json.decoder.JSONDecodeError:
|
||||||
hostname = data["hostname"]
|
pass
|
||||||
return hostname
|
|
||||||
|
|
||||||
async def get_mac(self) -> Union[str, None]:
|
async def get_mac(self) -> Union[str, None]:
|
||||||
mac = None
|
try:
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
data = await self.send_web_command("get_system_info")
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
if data:
|
||||||
async with httpx.AsyncClient() as client:
|
return data["macaddr"]
|
||||||
data = await client.get(url, auth=auth)
|
except KeyError:
|
||||||
if data.status_code == 200:
|
pass
|
||||||
data = data.json()
|
|
||||||
if len(data.keys()) > 0:
|
|
||||||
if "macaddr" in data.keys():
|
|
||||||
mac = data["macaddr"]
|
|
||||||
return mac
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
# this should time out, after it does do a check
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
await self.send_web_command("blink", params={"action": "startBlink"})
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
try:
|
try:
|
||||||
await client.post(url, data={"action": "startBlink"}, auth=auth)
|
data = await self.send_web_command(
|
||||||
except httpx.ReadTimeout:
|
"blink", params={"action": "onPageLoaded"}
|
||||||
# Expected behaviour
|
)
|
||||||
pass
|
if data:
|
||||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if data["isBlinking"]:
|
if data["isBlinking"]:
|
||||||
self.light = True
|
self.light = True
|
||||||
return True
|
except KeyError:
|
||||||
return False
|
pass
|
||||||
|
return self.light
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
await self.send_web_command("blink", params={"action": "stopBlink"})
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
data = await self.send_web_command(
|
||||||
await client.post(url, data={"action": "stopBlink"}, auth=auth)
|
"blink", params={"action": "onPageLoaded"}
|
||||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
)
|
||||||
if data.status_code == 200:
|
if data:
|
||||||
data = data.json()
|
|
||||||
if not data["isBlinking"]:
|
if not data["isBlinking"]:
|
||||||
self.light = False
|
self.light = False
|
||||||
return True
|
except KeyError:
|
||||||
return False
|
pass
|
||||||
|
|
||||||
async def check_light(self) -> Union[bool, None]:
|
|
||||||
if self.light:
|
|
||||||
return self.light
|
return self.light
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if data["isBlinking"]:
|
|
||||||
self.light = True
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.light = False
|
|
||||||
return False
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
data = await self.send_web_command("reboot")
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
if data:
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def get_fault_light(self) -> bool:
|
||||||
|
if self.light:
|
||||||
|
return self.light
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command(
|
||||||
|
"blink", params={"action": "onPageLoaded"}
|
||||||
|
)
|
||||||
|
if data:
|
||||||
|
self.light = data["isBlinking"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return self.light
|
||||||
|
|
||||||
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command("get_system_info")
|
||||||
|
if data:
|
||||||
|
return data["hostname"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19(BMMinerX19, S19):
|
class BMMinerS19(BMMinerX19, S19):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19Pro(BMMinerX19, S19Pro):
|
class BMMinerS19Pro(BMMinerX19, S19Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19XP(BMMinerX19, S19XP):
|
class BMMinerS19XP(BMMinerX19, S19XP):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19a(BMMinerX19, S19a):
|
class BMMinerS19a(BMMinerX19, S19a):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19aPro(BMMinerX19, S19aPro):
|
class BMMinerS19aPro(BMMinerX19, S19aPro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19j(BMMinerX19, S19j):
|
class BMMinerS19j(BMMinerX19, S19j):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS19jPro(BMMinerX19, S19jPro):
|
class BMMinerS19jPro(BMMinerX19, S19jPro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ from .X19 import BMMinerX19
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerT19(BMMinerX19, T19):
|
class BMMinerT19(BMMinerX19, T19):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import List, Union
|
from typing import List, Union, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
@@ -25,131 +25,76 @@ from pyasic.settings import PyasicSettings
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerX19(BMMiner):
|
class BMMinerX19(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.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.uname = "root"
|
||||||
self.pwd = PyasicSettings().global_x19_password
|
self.pwd = PyasicSettings().global_x19_password
|
||||||
|
|
||||||
async def check_light(self) -> Union[bool, None]:
|
async def send_web_command(
|
||||||
if self.light:
|
self, command: str, params: dict = None
|
||||||
return self.light
|
) -> Optional[dict]:
|
||||||
url = f"http://{self.ip}/cgi-bin/get_blink_status.cgi"
|
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
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)
|
data = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
if data.status_code == 200:
|
if data.status_code == 200:
|
||||||
data = data.json()
|
try:
|
||||||
light = data["blink"]
|
return data.json()
|
||||||
self.light = light
|
except json.decoder.JSONDecodeError:
|
||||||
return light
|
pass
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
url = f"http://{self.ip}/cgi-bin/get_miner_conf.cgi"
|
data = await self.send_web_command("get_miner_conf")
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
if data:
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
self.config = MinerConfig().from_raw(data)
|
self.config = MinerConfig().from_raw(data)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
url = f"http://{self.ip}/cgi-bin/set_miner_conf.cgi"
|
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
|
||||||
conf = config.as_x19(user_suffix=user_suffix)
|
conf = config.as_x19(user_suffix=user_suffix)
|
||||||
|
await self.send_web_command(
|
||||||
|
"set_miner_conf", params=conf # noqa: ignore conf being a str
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
await client.post(url, data=conf, auth=auth) # noqa - ignore conf being a str
|
|
||||||
except httpx.ReadTimeout:
|
|
||||||
pass
|
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
data = await self.get_config()
|
data = await self.get_config()
|
||||||
if data.as_x19() == conf:
|
if data.as_x19() == conf:
|
||||||
break
|
break
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
|
||||||
hostname = None
|
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if len(data.keys()) > 0:
|
|
||||||
if "hostname" in data.keys():
|
|
||||||
hostname = data["hostname"]
|
|
||||||
return hostname
|
|
||||||
|
|
||||||
async def get_mac(self) -> Union[str, None]:
|
|
||||||
mac = None
|
|
||||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if len(data.keys()) > 0:
|
|
||||||
if "macaddr" in data.keys():
|
|
||||||
mac = data["macaddr"]
|
|
||||||
return mac
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
data = await self.send_web_command(
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
"blink",
|
||||||
data = json.dumps({"blink": "true"})
|
params=json.dumps({"blink": "true"}), # noqa - ignore params being a str
|
||||||
async with httpx.AsyncClient() as client:
|
)
|
||||||
data = await client.post(url, data=data, auth=auth) # noqa - ignore conf being a str
|
if data:
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if data.get("code") == "B000":
|
if data.get("code") == "B000":
|
||||||
self.light = True
|
self.light = True
|
||||||
return True
|
return self.light
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
data = await self.send_web_command(
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
"blink",
|
||||||
data = json.dumps({"blink": "false"})
|
params=json.dumps({"blink": "false"}), # noqa - ignore params being a str
|
||||||
async with httpx.AsyncClient() as client:
|
)
|
||||||
data = await client.post(url, data=data, auth=auth) # noqa - ignore conf being a str
|
if data:
|
||||||
if data.status_code == 200:
|
|
||||||
data = data.json()
|
|
||||||
if data.get("code") == "B100":
|
if data.get("code") == "B100":
|
||||||
self.light = False
|
self.light = True
|
||||||
return True
|
return self.light
|
||||||
return False
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
data = await self.send_web_command("reboot")
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
if data:
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data.status_code == 200:
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
errors = []
|
|
||||||
url = f"http://{self.ip}/cgi-bin/summary.cgi"
|
|
||||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
data = await client.get(url, auth=auth)
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
data = data.json()
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
return []
|
|
||||||
if "SUMMARY" in data.keys():
|
|
||||||
if "status" in data["SUMMARY"][0].keys():
|
|
||||||
for item in data["SUMMARY"][0]["status"]:
|
|
||||||
if not item["status"] == "s":
|
|
||||||
errors.append(X19Error(item["msg"]))
|
|
||||||
return errors
|
|
||||||
|
|
||||||
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.autotuning_wattage = 0
|
||||||
@@ -161,3 +106,45 @@ class BMMinerX19(BMMiner):
|
|||||||
cfg.autotuning_wattage = 1
|
cfg.autotuning_wattage = 1
|
||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command("get_system_info")
|
||||||
|
if data:
|
||||||
|
return data["hostname"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_mac(self) -> Union[str, None]:
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command("get_system_info")
|
||||||
|
if data:
|
||||||
|
return data["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
|
errors = []
|
||||||
|
data = await self.send_web_command("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.send_web_command("get_blink_status")
|
||||||
|
if data:
|
||||||
|
self.light = data["blink"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return self.light
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS9(BMMiner, S9):
|
class BMMinerS9(BMMiner, S9):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S9i # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerS9i(BMMiner, S9i):
|
class BMMinerS9i(BMMiner, S9i):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import T9 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BMMinerT9(BMMiner, T9):
|
class BMMinerT9(BMMiner, T9):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS17(BOSMiner, S17):
|
class BOSMinerS17(BOSMiner, S17):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17e # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS17e(BOSMiner, S17e):
|
class BOSMinerS17e(BOSMiner, S17e):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import T17 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerT17(BOSMiner, T17):
|
class BOSMinerT17(BOSMiner, T17):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import T17Plus # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import T17e # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerT17e(BOSMiner, T17e):
|
class BOSMinerT17e(BOSMiner, T17e):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S19 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS19(BOSMiner, S19):
|
class BOSMinerS19(BOSMiner, S19):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S19j # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS19j(BOSMiner, S19j):
|
class BOSMinerS19j(BOSMiner, S19j):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import T19 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerT19(BOSMiner, T19):
|
class BOSMinerT19(BOSMiner, T19):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class BOSMinerS9(BOSMiner, S9):
|
class BOSMinerS9(BOSMiner, S9):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17 # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class CGMinerS17(CGMiner, S17):
|
class CGMinerS17(CGMiner, S17):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class CGMinerS17Plus(CGMiner, S17Plus):
|
class CGMinerS17Plus(CGMiner, S17Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
@@ -17,6 +17,4 @@ from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
|
|||||||
|
|
||||||
|
|
||||||
class CGMinerS17Pro(CGMiner, S17Pro):
|
class CGMinerS17Pro(CGMiner, S17Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
|
pass
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
self.ip = ip
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user