Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
228daecbbf | ||
|
|
81aa569d07 | ||
|
|
aba8d7b8b9 | ||
|
|
449403bc57 | ||
|
|
997a1bbe31 | ||
|
|
2c7e0e3efe | ||
|
|
6051a46654 | ||
|
|
c7847d2789 | ||
|
|
215af72a6b | ||
|
|
b33586f8eb | ||
|
|
8db81d9b3c | ||
|
|
cf3b2fedf4 | ||
|
|
81db6d8e28 | ||
|
|
d47e59d057 | ||
|
|
8388d2f4ac | ||
|
|
f5ec513ce0 | ||
|
|
8975842f0b | ||
|
|
c6ca1df112 | ||
|
|
92f58a9682 | ||
|
|
5dc19dbd78 | ||
|
|
b0fc11bcc1 | ||
|
|
23ebe460a4 | ||
|
|
92db8c8161 | ||
|
|
d9b522734a | ||
|
|
e02457f0b2 | ||
|
|
104b4c18c2 | ||
|
|
1970f8c924 | ||
|
|
e431a06ac5 | ||
|
|
6da9b58088 | ||
|
|
3108443041 | ||
|
|
37ebb9e6c3 | ||
|
|
6c2e0f59b1 | ||
|
|
6eb9cdce90 | ||
|
|
e96d9447d1 | ||
|
|
d3cca11322 | ||
|
|
86155db455 | ||
|
|
5b078b4b27 | ||
|
|
9672dd6873 |
@@ -230,7 +230,7 @@ These functions are
|
||||
|
||||
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs.
|
||||
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>M3X Series:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
@@ -109,6 +111,12 @@ details {
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m32s">M32S</a></summary>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m34s">M34S+</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m34sve10">VE10</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
|
||||
@@ -193,3 +193,19 @@
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M34S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M34S+VE10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlusVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -72,20 +72,21 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
)
|
||||
return return_commands
|
||||
|
||||
async def multicommand(self, *commands: str) -> dict:
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command)
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
return {}
|
||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||
@@ -129,15 +130,15 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
command: Union[str, bytes],
|
||||
parameters: Union[str, int, bool] = None,
|
||||
ignore_errors: bool = False,
|
||||
x19_command: bool = False,
|
||||
allow_warning: bool = True,
|
||||
) -> dict:
|
||||
"""Send an API command to the miner and return the result.
|
||||
|
||||
Parameters:
|
||||
command: The command to sent to the miner.
|
||||
parameters: Any additional parameters to be sent with the command.
|
||||
ignore_errors: Whether or not to raise APIError when the command returns an error.
|
||||
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
|
||||
ignore_errors: Whether to raise APIError when the command returns an error.
|
||||
allow_warning: Whether to warn if the command fails.
|
||||
|
||||
Returns:
|
||||
The return data from the API command parsed from JSON into a dict.
|
||||
@@ -157,7 +158,7 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
# validate the command succeeded
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if not x19_command:
|
||||
if allow_warning:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
|
||||
@@ -39,16 +39,16 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_x19_error: bool = False
|
||||
self, *commands: str, allow_warning: bool = True
|
||||
) -> dict:
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
@@ -62,7 +62,7 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, x19_command=True))
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError as e:
|
||||
raise APIError(e)
|
||||
except Exception as e:
|
||||
|
||||
@@ -767,7 +767,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
General miner info.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_miner_info")
|
||||
return await self.send_command("get_miner_info", allow_warning=False)
|
||||
|
||||
async def get_error_code(self) -> dict:
|
||||
"""Get a list of error codes from the miner.
|
||||
@@ -781,4 +781,4 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
A list of error codes on the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_error_code")
|
||||
return await self.send_command("get_error_code", allow_warning=False)
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI, APIError
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
@@ -36,6 +38,37 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
def __init__(self, ip: str, port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, allow_warning: bool = True
|
||||
) -> dict:
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands):
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError as e:
|
||||
raise APIError(e)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
<details>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
from typing import Literal, List
|
||||
import random
|
||||
import string
|
||||
@@ -37,6 +37,10 @@ class _Pool:
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||
|
||||
@@ -136,6 +140,10 @@ class _PoolGroup:
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
@@ -263,6 +271,10 @@ class MinerConfig:
|
||||
dps_shutdown_enabled: bool = None
|
||||
dps_shutdown_duration: float = None
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Convert the data in this class to a dict."""
|
||||
data_dict = asdict(self)
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Union, List
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from dataclasses import dataclass, field, asdict, fields
|
||||
from datetime import datetime, timezone
|
||||
from functools import reduce
|
||||
import time
|
||||
import json
|
||||
import copy
|
||||
@@ -22,6 +23,17 @@ import copy
|
||||
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError
|
||||
|
||||
|
||||
@dataclass
|
||||
class HashBoard:
|
||||
slot: int = 0
|
||||
hashrate: float = 0.0
|
||||
temp: int = -1
|
||||
chip_temp: int = -1
|
||||
chips: int = 0
|
||||
expected_chips: int = 0
|
||||
missing: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerData:
|
||||
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
|
||||
@@ -73,17 +85,19 @@ class MinerData:
|
||||
model: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
left_board_hashrate: float = 0.0
|
||||
center_board_hashrate: float = 0.0
|
||||
right_board_hashrate: float = 0.0
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
ideal_hashboards: int = 1
|
||||
left_board_hashrate: float = field(init=False)
|
||||
center_board_hashrate: float = field(init=False)
|
||||
right_board_hashrate: float = field(init=False)
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = -1.0
|
||||
left_board_temp: int = -1
|
||||
left_board_chip_temp: int = -1
|
||||
center_board_temp: int = -1
|
||||
center_board_chip_temp: int = -1
|
||||
right_board_temp: int = -1
|
||||
right_board_chip_temp: int = -1
|
||||
left_board_temp: int = field(init=False)
|
||||
left_board_chip_temp: int = field(init=False)
|
||||
center_board_temp: int = field(init=False)
|
||||
center_board_chip_temp: int = field(init=False)
|
||||
right_board_temp: int = field(init=False)
|
||||
right_board_chip_temp: int = field(init=False)
|
||||
wattage: int = -1
|
||||
wattage_limit: int = -1
|
||||
fan_1: int = -1
|
||||
@@ -91,9 +105,9 @@ class MinerData:
|
||||
fan_3: int = -1
|
||||
fan_4: int = -1
|
||||
fan_psu: int = -1
|
||||
left_chips: int = 0
|
||||
center_chips: int = 0
|
||||
right_chips: int = 0
|
||||
left_chips: int = field(init=False)
|
||||
center_chips: int = field(init=False)
|
||||
right_chips: int = field(init=False)
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
@@ -109,6 +123,11 @@ class MinerData:
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return [f.name for f in fields(cls)]
|
||||
|
||||
|
||||
def __post_init__(self):
|
||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||
|
||||
@@ -163,12 +182,148 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
return self.right_chips + self.center_chips + self.left_chips
|
||||
return sum([hb.chips for hb in self.hashboards])
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].chips
|
||||
return 0
|
||||
|
||||
@left_chips.setter
|
||||
def left_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chips
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].chips
|
||||
return 0
|
||||
|
||||
@center_chips.setter
|
||||
def center_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].chips
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chips
|
||||
return 0
|
||||
|
||||
@right_chips.setter
|
||||
def right_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].hashrate
|
||||
return 0
|
||||
|
||||
@left_board_hashrate.setter
|
||||
def left_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].hashrate
|
||||
return 0
|
||||
|
||||
@center_board_hashrate.setter
|
||||
def center_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].hashrate
|
||||
return 0
|
||||
|
||||
@right_board_hashrate.setter
|
||||
def right_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].temp
|
||||
return 0
|
||||
|
||||
@left_board_temp.setter
|
||||
def left_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].temp
|
||||
return 0
|
||||
|
||||
@center_board_temp.setter
|
||||
def center_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].temp
|
||||
return 0
|
||||
|
||||
@right_board_temp.setter
|
||||
def right_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].chip_temp
|
||||
return 0
|
||||
|
||||
@left_board_chip_temp.setter
|
||||
def left_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].chip_temp
|
||||
return 0
|
||||
|
||||
@center_board_chip_temp.setter
|
||||
def center_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chip_temp
|
||||
return 0
|
||||
|
||||
@right_board_chip_temp.setter
|
||||
def right_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def nominal(self): # noqa - Skip PyCharm inspection
|
||||
return self.ideal_chips == self.total_chips
|
||||
@@ -189,13 +344,9 @@ class MinerData:
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for temp in [
|
||||
self.left_board_chip_temp,
|
||||
self.center_board_chip_temp,
|
||||
self.right_board_chip_temp,
|
||||
]:
|
||||
if temp and not temp == -1:
|
||||
total_temp += temp
|
||||
for hb in self.hashboards:
|
||||
if hb.temp and not hb.temp == -1:
|
||||
total_temp += hb.temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,9 +21,15 @@ class X19Error:
|
||||
|
||||
Attributes:
|
||||
error_message: The error message as a string.
|
||||
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||
"""
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,9 +21,16 @@ class BraiinsOSError:
|
||||
|
||||
Attributes:
|
||||
error_message: The error message as a string.
|
||||
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||
"""
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from dataclasses import dataclass, field, asdict, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -27,6 +27,11 @@ class InnosiliconError:
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from dataclasses import dataclass, field, asdict, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -27,11 +27,25 @@ class WhatsminerError:
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
err_type = int(str(self.error_code)[:-2])
|
||||
err_subtype = int(str(self.error_code)[-2:-1])
|
||||
err_value = int(str(self.error_code)[-1:])
|
||||
try:
|
||||
select_err_subtype = ERROR_CODES[err_type][err_subtype]
|
||||
if err_value in select_err_subtype:
|
||||
return select_err_subtype[err_value]
|
||||
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?
|
||||
else:
|
||||
return "Unknown error type."
|
||||
except KeyError:
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
@@ -42,131 +56,158 @@ class WhatsminerError:
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
110: "Intake fan speed error.",
|
||||
111: "Exhaust fan speed error.",
|
||||
120: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
130: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
140: "Fan speed too high.",
|
||||
200: "Power probing error. No power found.",
|
||||
201: "Power supply and configuration file don't match.",
|
||||
202: "Power output voltage error.",
|
||||
203: "Power protecting due to high environment temperature.",
|
||||
204: "Power current protecting due to high environment temperature.",
|
||||
205: "Power current error.",
|
||||
206: "Power input low voltage error.",
|
||||
207: "Power input current protecting due to bad power input.",
|
||||
210: "Power error.",
|
||||
213: "Power input voltage and current do not match power output.",
|
||||
216: "Power remained unchanged for a long time.",
|
||||
217: "Power set enable error.",
|
||||
218: "Power input voltage is lower than 230V for high power mode.",
|
||||
233: "Power output high temperature protection error.",
|
||||
234: "Power output high temperature protection error.",
|
||||
235: "Power output high temperature protection error.",
|
||||
236: "Power output high current protection error.",
|
||||
237: "Power output high current protection error.",
|
||||
238: "Power output high current protection error.",
|
||||
239: "Power output high voltage protection error.",
|
||||
240: "Power output low voltage protection error.",
|
||||
241: "Power output current imbalance error.",
|
||||
243: "Power input high temperature protection error.",
|
||||
244: "Power input high temperature protection error.",
|
||||
245: "Power input high temperature protection error.",
|
||||
246: "Power input high voltage protection error.",
|
||||
247: "Power input high voltage protection error.",
|
||||
248: "Power input high current protection error.",
|
||||
249: "Power input high current protection error.",
|
||||
250: "Power input low voltage protection error.",
|
||||
251: "Power input low voltage protection error.",
|
||||
253: "Power supply fan error.",
|
||||
254: "Power supply fan error.",
|
||||
255: "Power output high power protection error.",
|
||||
256: "Power output high power protection error.",
|
||||
257: "Input over current protection of power supply on primary side.",
|
||||
263: "Power communication warning.",
|
||||
264: "Power communication error.",
|
||||
267: "Power watchdog protection.",
|
||||
268: "Power output high current protection.",
|
||||
269: "Power input high current protection.",
|
||||
270: "Power input high voltage protection.",
|
||||
271: "Power input low voltage protection.",
|
||||
272: "Excessive power supply output warning.",
|
||||
273: "Power input too high warning.",
|
||||
274: "Power fan warning.",
|
||||
275: "Power high temperature warning.",
|
||||
300: "Right board temperature sensor detection error.",
|
||||
301: "Center board temperature sensor detection error.",
|
||||
302: "Left board temperature sensor detection error.",
|
||||
320: "Right board temperature reading error.",
|
||||
321: "Center board temperature reading error.",
|
||||
322: "Left board temperature reading error.",
|
||||
329: "Control board temperature sensor communication error.",
|
||||
350: "Right board temperature protecting.",
|
||||
351: "Center board temperature protecting.",
|
||||
352: "Left board temperature protecting.",
|
||||
360: "Hashboard high temperature error.",
|
||||
410: "Right board eeprom detection error.",
|
||||
411: "Center board eeprom detection error.",
|
||||
412: "Left board eeprom detection error.",
|
||||
420: "Right board eeprom parsing error.",
|
||||
421: "Center board eeprom parsing error.",
|
||||
422: "Left board eeprom parsing error.",
|
||||
430: "Right board chip bin type error.",
|
||||
431: "Center board chip bin type error.",
|
||||
432: "Left board chip bin type error.",
|
||||
440: "Right board eeprom chip number X error.",
|
||||
441: "Center board eeprom chip number X error.",
|
||||
442: "Left board eeprom chip number X error.",
|
||||
450: "Right board eeprom xfer error.",
|
||||
451: "Center board eeprom xfer error.",
|
||||
452: "Left board eeprom xfer error.",
|
||||
510: "Right board miner type error.",
|
||||
511: "Center board miner type error.",
|
||||
512: "Left board miner type error.",
|
||||
520: "Right board bin type error.",
|
||||
521: "Center board bin type error.",
|
||||
522: "Left board bin type error.",
|
||||
530: "Right board not found.",
|
||||
531: "Center board not found.",
|
||||
532: "Left board not found.",
|
||||
540: "Right board error reading chip id.",
|
||||
541: "Center board error reading chip id.",
|
||||
542: "Left board error reading chip id.",
|
||||
550: "Right board has bad chips.",
|
||||
551: "Center board has bad chips.",
|
||||
552: "Left board has bad chips.",
|
||||
560: "Right board loss of balance error.",
|
||||
561: "Center board loss of balance error.",
|
||||
562: "Left board loss of balance error.",
|
||||
600: "Environment temperature is too high.",
|
||||
610: "Environment temperature is too high for high performance mode.",
|
||||
701: "Control board no support chip.",
|
||||
710: "Control board rebooted as an exception.",
|
||||
712: "Control board rebooted as an exception.",
|
||||
800: "CGMiner checksum error.",
|
||||
801: "System monitor checksum error.",
|
||||
802: "Remote daemon checksum error.",
|
||||
2010: "All pools are disabled.",
|
||||
2020: "Pool 0 connection failed.",
|
||||
2021: "Pool 1 connection failed.",
|
||||
2022: "Pool 2 connection failed.",
|
||||
2023: "Pool 3 connection failed.",
|
||||
2030: "High rejection rate on pool.",
|
||||
2040: "The pool does not support asicboost mode.",
|
||||
2310: "Hashrate is too low.",
|
||||
2320: "Hashrate is too low.",
|
||||
2340: "Hashrate loss is too high.",
|
||||
2350: "Hashrate loss is too high.",
|
||||
5070: "Right hashboard water velocity is abnormal.",
|
||||
5071: "Center hashboard water velocity is abnormal.",
|
||||
5072: "Left hashboard water velocity is abnormal.",
|
||||
5110: "Right hashboard frequency up timeout.",
|
||||
5111: "Center hashboard frequency up timeout.",
|
||||
5112: "Left hashboard frequency up timeout.",
|
||||
8410: "Software version error.",
|
||||
100001: "/antiv/signature illegal.",
|
||||
100002: "/antiv/dig/init.d illegal.",
|
||||
100003: "/antiv/dig/pf_partial.dig illegal.",
|
||||
1: { # Fan error
|
||||
1: { # Fan speed error of 1000+
|
||||
0: "Intake fan speed error.",
|
||||
1: "Exhaust fan speed error.",
|
||||
},
|
||||
2: { # Fan speed error of 2000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
},
|
||||
3: { # Fan speed error of 3000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
},
|
||||
4: {0: "Fan speed too high."}, # High speed
|
||||
},
|
||||
2: { # Power error
|
||||
0: {
|
||||
0: "Power probing error. No power found.",
|
||||
1: "Power supply and configuration file don't match.",
|
||||
2: "Power output voltage error.",
|
||||
3: "Power protecting due to high environment temperature.",
|
||||
4: "Power current protecting due to high environment temperature.",
|
||||
5: "Power current error.",
|
||||
6: "Power input low voltage error.",
|
||||
7: "Power input current protecting due to bad power input.",
|
||||
},
|
||||
1: {
|
||||
0: "Power error.",
|
||||
3: "Power input voltage and current do not match power output.",
|
||||
6: "Power remained unchanged for a long time.",
|
||||
7: "Power set enable error.",
|
||||
8: "Power input voltage is lower than 230V for high power mode.",
|
||||
},
|
||||
3: {
|
||||
3: "Power output high temperature protection error.",
|
||||
4: "Power output high temperature protection error.",
|
||||
5: "Power output high temperature protection error.",
|
||||
6: "Power output high current protection error.",
|
||||
7: "Power output high current protection error.",
|
||||
8: "Power output high current protection error.",
|
||||
9: "Power output high voltage protection error.",
|
||||
},
|
||||
4: {
|
||||
0: "Power output low voltage protection error.",
|
||||
1: "Power output current imbalance error.",
|
||||
3: "Power input high temperature protection error.",
|
||||
4: "Power input high temperature protection error.",
|
||||
5: "Power input high temperature protection error.",
|
||||
6: "Power input high voltage protection error.",
|
||||
7: "Power input high voltage protection error.",
|
||||
8: "Power input high current protection error.",
|
||||
9: "Power input high current protection error.",
|
||||
},
|
||||
5: {
|
||||
0: "Power input low voltage protection error.",
|
||||
1: "Power input low voltage protection error.",
|
||||
3: "Power supply fan error.",
|
||||
4: "Power supply fan error.",
|
||||
5: "Power output high power protection error.",
|
||||
6: "Power output high power protection error.",
|
||||
7: "Input over current protection of power supply on primary side.",
|
||||
},
|
||||
6: {
|
||||
3: "Power communication warning.",
|
||||
4: "Power communication error.",
|
||||
7: "Power watchdog protection.",
|
||||
8: "Power output high current protection.",
|
||||
9: "Power input high current protection.",
|
||||
},
|
||||
7: {
|
||||
0: "Power input high voltage protection.",
|
||||
1: "Power input low voltage protection.",
|
||||
2: "Excessive power supply output warning.",
|
||||
3: "Power input too high warning.",
|
||||
4: "Power fan warning.",
|
||||
5: "Power high temperature warning.",
|
||||
},
|
||||
},
|
||||
3: { # temperature error
|
||||
0: { # sensor detection error
|
||||
"n": "Slot {n} temperature sensor detection error."
|
||||
},
|
||||
2: { # temperature reading error
|
||||
"n": "Slot {n} temperature reading error.",
|
||||
9: "Control board temperature sensor communication error.",
|
||||
},
|
||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
||||
6: {0: "Hashboard high temperature error."}, # high temp
|
||||
},
|
||||
4: { # EEPROM error
|
||||
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
||||
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
||||
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
||||
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
||||
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
|
||||
},
|
||||
5: { # hashboard error
|
||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
||||
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
||||
3: {"n": "Slot {n} not found."}, # board not found error
|
||||
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
||||
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
||||
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
||||
},
|
||||
6: { # env temp error
|
||||
0: {0: "Environment temperature is too high."}, # normal env temp error
|
||||
1: { # high power env temp error
|
||||
0: "Environment temperature is too high for high performance mode."
|
||||
},
|
||||
},
|
||||
7: { # control board error
|
||||
0: {1: "Control board no support chip."},
|
||||
1: {
|
||||
0: "Control board rebooted as an exception.",
|
||||
2: "Control board rebooted as an exception.",
|
||||
},
|
||||
},
|
||||
8: { # checksum error
|
||||
0: {
|
||||
0: "CGMiner checksum error.",
|
||||
1: "System monitor checksum error.",
|
||||
2: "Remote daemon checksum error.",
|
||||
}
|
||||
},
|
||||
20: { # pool error
|
||||
1: {0: "All pools are disabled."}, # all disabled error
|
||||
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
||||
3: {0: "High rejection rate on pool."}, # rejection rate error
|
||||
4: { # asicboost not supported error
|
||||
0: "The pool does not support asicboost mode."
|
||||
},
|
||||
},
|
||||
23: { # hashrate error
|
||||
1: {0: "Hashrate is too low."},
|
||||
2: {0: "Hashrate is too low."},
|
||||
3: {0: "Hashrate loss is too high."},
|
||||
4: {0: "Hashrate loss is too high."},
|
||||
},
|
||||
50: { # water velocity error
|
||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
||||
},
|
||||
51: { # frequency error
|
||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
},
|
||||
84: {
|
||||
1: {0: "Software version error."},
|
||||
},
|
||||
1000: {
|
||||
0: {
|
||||
1: "/antiv/signature illegal.",
|
||||
2: "/antiv/dig/init.d illegal.",
|
||||
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ from typing import Union, List
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.miners.base import BaseMiner
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
|
||||
@@ -185,13 +185,17 @@ class BMMiner(BaseMiner):
|
||||
async def resume_mining(self) -> bool:
|
||||
return False
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = False) -> 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 * 3)
|
||||
data = MinerData(
|
||||
ip=str(self.ip),
|
||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
||||
ideal_hashboards=self.ideal_hashboards,
|
||||
)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
@@ -219,7 +223,7 @@ class BMMiner(BaseMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
"summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -242,7 +246,7 @@ class BMMiner(BaseMiner):
|
||||
if stats:
|
||||
boards = stats.get("STATS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
@@ -252,30 +256,38 @@ class BMMiner(BaseMiner):
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
||||
env_temp_list = []
|
||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||
hashboard = HashBoard(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
|
||||
try:
|
||||
data.left_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
||||
)
|
||||
except ValueError as e:
|
||||
data.left_board_hashrate = round(0.00, 2)
|
||||
try:
|
||||
data.center_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000,
|
||||
2,
|
||||
)
|
||||
except ValueError as e:
|
||||
data.center_board_hashrate = round(0.00, 2)
|
||||
try:
|
||||
data.right_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000,
|
||||
2,
|
||||
)
|
||||
except ValueError as e:
|
||||
data.right_board_hashrate = round(0.00, 2)
|
||||
chip_temp = boards[1].get(f"temp{i}")
|
||||
if chip_temp:
|
||||
hashboard.chip_temp = round(chip_temp)
|
||||
|
||||
temp = boards[1].get(f"temp2_{i}")
|
||||
if temp:
|
||||
hashboard.temp = round(temp)
|
||||
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
if chips:
|
||||
hashboard.chips = chips
|
||||
hashboard.missing = False
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
data.hashboards.append(hashboard)
|
||||
|
||||
if f"temp_pcb{i}" in boards[1].keys():
|
||||
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
if not env_temp_list == []:
|
||||
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
@@ -293,20 +305,6 @@ class BMMiner(BaseMiner):
|
||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
if not env_temp_list == []:
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
|
||||
@@ -25,7 +25,7 @@ from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.errors import APIError
|
||||
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
@@ -266,7 +266,10 @@ class BOSMiner(BaseMiner):
|
||||
logging.warning(e)
|
||||
|
||||
if tunerstatus:
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
try:
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
except KeyError:
|
||||
tuner = tunerstatus.get("TUNERSTATUS")
|
||||
if tuner:
|
||||
if len(tuner) > 0:
|
||||
chain_status = tuner[0].get("TunerChainStatus")
|
||||
@@ -294,13 +297,21 @@ class BOSMiner(BaseMiner):
|
||||
)
|
||||
return errors
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
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 * 3)
|
||||
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)
|
||||
],
|
||||
)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
@@ -331,6 +342,7 @@ class BOSMiner(BaseMiner):
|
||||
"devdetails",
|
||||
"fans",
|
||||
"devs",
|
||||
allow_warning=allow_warning
|
||||
)
|
||||
except APIError as e:
|
||||
if str(e.message) == "Not ready":
|
||||
@@ -361,14 +373,13 @@ class BOSMiner(BaseMiner):
|
||||
temp = temps[0].get("TEMPS")
|
||||
if temp:
|
||||
if len(temp) > 0:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
||||
for board in temp:
|
||||
_id = board["ID"] - offset
|
||||
chip_temp = round(board["Chip"])
|
||||
board_temp = round(board["Board"])
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
data.hashboards[_id].chip_temp = chip_temp
|
||||
data.hashboards[_id].temp = board_temp
|
||||
|
||||
if fans:
|
||||
fan_data = fans[0].get("FANS")
|
||||
@@ -461,27 +472,25 @@ class BOSMiner(BaseMiner):
|
||||
boards = devdetails[0].get("DEVDETAILS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
chips = board["Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
data.hashboards[_id].chips = chips
|
||||
if chips > 0:
|
||||
data.hashboards[_id].missing = False
|
||||
else:
|
||||
data.hashboards[_id].missing = True
|
||||
|
||||
if devs:
|
||||
boards = devs[0].get("DEVS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {
|
||||
0: "left_board_hashrate",
|
||||
1: "center_board_hashrate",
|
||||
2: "right_board_hashrate",
|
||||
}
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
||||
setattr(data, board_map[_id], hashrate)
|
||||
data.hashboards[_id].hashrate = hashrate
|
||||
return data
|
||||
|
||||
async def get_mac(self):
|
||||
|
||||
@@ -21,7 +21,7 @@ from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.errors import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.data.error_codes import WhatsminerError, MinerErrorData
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
@@ -109,7 +109,6 @@ class BTMiner(BaseMiner):
|
||||
if "Code" in data.keys():
|
||||
if data["Code"] == 131:
|
||||
return True
|
||||
print(data)
|
||||
return False
|
||||
|
||||
async def check_light(self) -> bool:
|
||||
@@ -166,28 +165,22 @@ class BTMiner(BaseMiner):
|
||||
for err in err_data["Msg"]["error_code"]:
|
||||
if isinstance(err, dict):
|
||||
for code in err:
|
||||
data.append(
|
||||
WhatsminerError(
|
||||
error_code=int(code)
|
||||
)
|
||||
)
|
||||
data.append(WhatsminerError(error_code=int(code)))
|
||||
else:
|
||||
data.append(
|
||||
WhatsminerError(
|
||||
error_code=int(err)
|
||||
)
|
||||
)
|
||||
data.append(WhatsminerError(error_code=int(err)))
|
||||
except APIError:
|
||||
summary_data = await self.api.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}"]
|
||||
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
|
||||
|
||||
@@ -248,13 +241,17 @@ class BTMiner(BaseMiner):
|
||||
cfg = cfg.from_api(pools["POOLS"])
|
||||
return cfg
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
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 * 3)
|
||||
data = MinerData(
|
||||
ip=str(self.ip),
|
||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
||||
ideal_hashboards=self.ideal_hashboards,
|
||||
)
|
||||
|
||||
mac = None
|
||||
|
||||
@@ -283,7 +280,7 @@ class BTMiner(BaseMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
try:
|
||||
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
||||
miner_data = await self.api.multicommand("summary", "devs", "pools", allow_warning=allow_warning)
|
||||
if miner_data:
|
||||
break
|
||||
except APIError:
|
||||
@@ -361,44 +358,25 @@ class BTMiner(BaseMiner):
|
||||
if isinstance(err, dict):
|
||||
for code in err:
|
||||
data.errors.append(
|
||||
WhatsminerError(
|
||||
error_code=int(code)
|
||||
)
|
||||
WhatsminerError(error_code=int(code))
|
||||
)
|
||||
else:
|
||||
data.errors.append(
|
||||
WhatsminerError(
|
||||
error_code=int(err)
|
||||
)
|
||||
)
|
||||
data.errors.append(WhatsminerError(error_code=int(err)))
|
||||
|
||||
if devs:
|
||||
temp_data = devs.get("DEVS")
|
||||
if temp_data:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
for board in temp_data:
|
||||
_id = board["ASC"]
|
||||
chip_temp = round(board["Chip Temp Avg"])
|
||||
board_temp = round(board["Temperature"])
|
||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
|
||||
|
||||
if devs:
|
||||
boards = devs.get("DEVS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
if "ID" in boards[0].keys():
|
||||
id_key = "ID"
|
||||
else:
|
||||
id_key = "ASC"
|
||||
offset = boards[0][id_key]
|
||||
for board in boards:
|
||||
_id = board[id_key] - offset
|
||||
chips = board["Effective Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
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
|
||||
|
||||
@@ -22,7 +22,7 @@ from pyasic.miners.base import BaseMiner
|
||||
from pyasic.errors import APIError
|
||||
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.settings import PyasicSettings
|
||||
@@ -173,13 +173,17 @@ class CGMiner(BaseMiner):
|
||||
async def get_mac(self) -> str:
|
||||
return "00:00:00:00:00:00"
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = False) -> 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 * 3)
|
||||
data = MinerData(
|
||||
ip=str(self.ip),
|
||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
||||
ideal_hashboards=self.ideal_hashboards,
|
||||
)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
@@ -202,7 +206,7 @@ class CGMiner(BaseMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
"summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -235,19 +239,38 @@ class CGMiner(BaseMiner):
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
||||
env_temp_list = []
|
||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||
hashboard = HashBoard(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
|
||||
data.left_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
||||
)
|
||||
data.center_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
|
||||
)
|
||||
data.right_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
|
||||
)
|
||||
chip_temp = boards[1].get(f"temp{i}")
|
||||
if chip_temp:
|
||||
hashboard.chip_temp = round(chip_temp)
|
||||
|
||||
temp = boards[1].get(f"temp2_{i}")
|
||||
if temp:
|
||||
hashboard.temp = round(temp)
|
||||
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
if chips:
|
||||
hashboard.chips = chips
|
||||
hashboard.missing = False
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
data.hashboards.append(hashboard)
|
||||
|
||||
if f"temp_pcb{i}" in boards[1].keys():
|
||||
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
if not env_temp_list == []:
|
||||
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
@@ -265,19 +288,6 @@ class CGMiner(BaseMiner):
|
||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
|
||||
35
pyasic/miners/_types/whatsminer/M3X/M34S_Plus.py
Normal file
35
pyasic/miners/_types/whatsminer/M3X/M34S_Plus.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright 2022 Upstream Data Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyasic.miners.base import BaseMiner
|
||||
|
||||
|
||||
class M34SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M34S+"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 116
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
class M34SPlusVE10(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M34S+ VE10"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 116
|
||||
self.fan_count = 0
|
||||
@@ -34,3 +34,4 @@ from .M31S_Plus import (
|
||||
|
||||
from .M32 import M32, M32V20
|
||||
from .M32S import M32S
|
||||
from .M34S_Plus import M34SPlus, M34SPlusVE10
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
|
||||
from .M2X import *
|
||||
from .M3X import *
|
||||
from .M5X import *
|
||||
from .M5X import *
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
|
||||
@@ -33,13 +33,17 @@ class HiveonT9(Hiveon, T9):
|
||||
)
|
||||
return mac
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = False) -> 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 * 3)
|
||||
data = MinerData(
|
||||
ip=str(self.ip),
|
||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
||||
ideal_hashboards=self.ideal_hashboards,
|
||||
)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
@@ -67,7 +71,7 @@ class HiveonT9(Hiveon, T9):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
"summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -97,14 +101,17 @@ class HiveonT9(Hiveon, T9):
|
||||
)
|
||||
|
||||
board_map = {
|
||||
"left": [2, 9, 10],
|
||||
"center": [3, 11, 12],
|
||||
"right": [4, 13, 14],
|
||||
0: [2, 9, 10],
|
||||
1: [3, 11, 12],
|
||||
2: [4, 13, 14],
|
||||
}
|
||||
|
||||
env_temp_list = []
|
||||
|
||||
for board in board_map.keys():
|
||||
hashboard = HashBoard(
|
||||
slot=board, expected_chips=self.nominal_chips
|
||||
)
|
||||
chips = 0
|
||||
hashrate = 0
|
||||
chip_temp = 0
|
||||
@@ -121,10 +128,15 @@ class HiveonT9(Hiveon, T9):
|
||||
|
||||
hashrate += boards[1][f"chain_rate{chipset}"]
|
||||
chips += boards[1][f"chain_acn{chipset}"]
|
||||
setattr(data, f"{board}_chips", chips)
|
||||
setattr(data, f"{board}_board_hashrate", hashrate)
|
||||
setattr(data, f"{board}_board_temp", board_temp)
|
||||
setattr(data, f"{board}_board_chip_temp", chip_temp)
|
||||
hashboard.hashrate = hashrate
|
||||
hashboard.chips = chips
|
||||
hashboard.temp = board_temp
|
||||
hashboard.chip_temp = chip_temp
|
||||
hashboard.missing = True
|
||||
if chips and chips > 0:
|
||||
hashboard.missing = False
|
||||
data.hashboards.append(hashboard)
|
||||
|
||||
if not env_temp_list == []:
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.settings import PyasicSettings
|
||||
import re
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -80,8 +80,16 @@ class CGMinerA10X(CGMiner):
|
||||
)
|
||||
return mac
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
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
|
||||
@@ -92,7 +100,7 @@ class CGMinerA10X(CGMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"version", "summary", "pools", "stats"
|
||||
"version", "summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -145,27 +153,20 @@ class CGMinerA10X(CGMiner):
|
||||
f"fan_{fan+1}",
|
||||
int(raw_data[f"Fan{fan+1}"]),
|
||||
)
|
||||
if "MTmax" in raw_data.keys():
|
||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
||||
if "MTavg" in raw_data.keys():
|
||||
data.left_board_temp = int(raw_data["MTavg"][0])
|
||||
data.center_board_temp = int(raw_data["MTavg"][1])
|
||||
data.right_board_temp = int(raw_data["MTavg"][2])
|
||||
for board in range(self.ideal_hashboards):
|
||||
chip_temp = raw_data.get("MTmax")
|
||||
if chip_temp:
|
||||
data.hashboards[board].chip_temp = chip_temp[board]
|
||||
|
||||
if "PVT_T0" in raw_data:
|
||||
data.left_chips = len(
|
||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T1" in raw_data:
|
||||
data.center_chips = len(
|
||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T2" in raw_data:
|
||||
data.right_chips = len(
|
||||
[item for item in raw_data["PVT_T2"] if not item == "0"]
|
||||
)
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.settings import PyasicSettings
|
||||
import re
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -80,8 +80,16 @@ class CGMinerA7X(CGMiner):
|
||||
)
|
||||
return mac
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
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
|
||||
@@ -92,7 +100,7 @@ class CGMinerA7X(CGMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"version", "summary", "pools", "stats"
|
||||
"version", "summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -145,27 +153,20 @@ class CGMinerA7X(CGMiner):
|
||||
f"fan_{fan+1}",
|
||||
int(raw_data[f"Fan{fan+1}"]),
|
||||
)
|
||||
if "MTmax" in raw_data.keys():
|
||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
||||
if "MTavg" in raw_data.keys():
|
||||
data.left_board_temp = int(raw_data["MTavg"][0])
|
||||
data.center_board_temp = int(raw_data["MTavg"][1])
|
||||
data.right_board_temp = int(raw_data["MTavg"][2])
|
||||
for board in range(self.ideal_hashboards):
|
||||
chip_temp = raw_data.get("MTmax")
|
||||
if chip_temp:
|
||||
data.hashboards[board].chip_temp = chip_temp[board]
|
||||
|
||||
if "PVT_T0" in raw_data:
|
||||
data.left_chips = len(
|
||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T1" in raw_data:
|
||||
data.center_chips = len(
|
||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T2" in raw_data:
|
||||
data.right_chips = len(
|
||||
[item for item in raw_data["PVT_T2"] if not item == "0"]
|
||||
)
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.settings import PyasicSettings
|
||||
import re
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -80,8 +80,16 @@ class CGMinerA8X(CGMiner):
|
||||
)
|
||||
return mac
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
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
|
||||
@@ -92,7 +100,7 @@ class CGMinerA8X(CGMiner):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"version", "summary", "pools", "stats"
|
||||
"version", "summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -145,27 +153,20 @@ class CGMinerA8X(CGMiner):
|
||||
f"fan_{fan+1}",
|
||||
int(raw_data[f"Fan{fan+1}"]),
|
||||
)
|
||||
if "MTmax" in raw_data.keys():
|
||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
||||
if "MTavg" in raw_data.keys():
|
||||
data.left_board_temp = int(raw_data["MTavg"][0])
|
||||
data.center_board_temp = int(raw_data["MTavg"][1])
|
||||
data.right_board_temp = int(raw_data["MTavg"][2])
|
||||
for board in range(self.ideal_hashboards):
|
||||
chip_temp = raw_data.get("MTmax")
|
||||
if chip_temp:
|
||||
data.hashboards[board].chip_temp = chip_temp[board]
|
||||
|
||||
if "PVT_T0" in raw_data:
|
||||
data.left_chips = len(
|
||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T1" in raw_data:
|
||||
data.center_chips = len(
|
||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T2" in raw_data:
|
||||
data.right_chips = len(
|
||||
[item for item in raw_data["PVT_T2"] if not item == "0"]
|
||||
)
|
||||
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
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.settings import PyasicSettings
|
||||
import re
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -81,8 +81,16 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
||||
)
|
||||
return mac
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
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
|
||||
@@ -95,7 +103,7 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
||||
miner_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"version", "summary", "pools", "stats"
|
||||
"version", "summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
@@ -146,27 +154,20 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
||||
f"fan_{fan+1}",
|
||||
int(raw_data[f"Fan{fan+1}"]),
|
||||
)
|
||||
if "MTmax" in raw_data.keys():
|
||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
||||
if "MTavg" in raw_data.keys():
|
||||
data.left_board_temp = int(raw_data["MTavg"][0])
|
||||
data.center_board_temp = int(raw_data["MTavg"][1])
|
||||
data.right_board_temp = int(raw_data["MTavg"][2])
|
||||
for board in range(self.ideal_hashboards):
|
||||
chip_temp = raw_data.get("MTmax")
|
||||
if chip_temp:
|
||||
data.hashboards[board].chip_temp = chip_temp[board]
|
||||
|
||||
if "PVT_T0" in raw_data:
|
||||
data.left_chips = len(
|
||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T1" in raw_data:
|
||||
data.center_chips = len(
|
||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
||||
)
|
||||
if "PVT_T2" in raw_data:
|
||||
data.right_chips = len(
|
||||
[item for item in raw_data["PVT_T2"] if not item == "0"]
|
||||
)
|
||||
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
|
||||
|
||||
@@ -43,6 +43,7 @@ class BaseMiner(ABC):
|
||||
self.version = None
|
||||
self.fan_count = 2
|
||||
self.config = None
|
||||
self.ideal_hashboards = 3
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMiner:
|
||||
@@ -191,7 +192,7 @@ class BaseMiner(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_data(self) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = True) -> MinerData:
|
||||
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data import MinerData, HashBoard
|
||||
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
|
||||
from pyasic.settings import PyasicSettings
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -157,8 +157,16 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
||||
errors.append(InnosiliconError(error_code=err))
|
||||
return errors
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
||||
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)
|
||||
],
|
||||
)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
@@ -179,7 +187,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
||||
all_data = None
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
"summary", "pools", "stats", allow_warning=allow_warning
|
||||
)
|
||||
|
||||
if miner_data:
|
||||
@@ -215,32 +223,31 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
||||
if stats:
|
||||
stats = stats[0]
|
||||
if stats.get("STATS"):
|
||||
board_map = {0: "left", 1: "center", 2: "right"}
|
||||
for idx, board in enumerate(stats["STATS"]):
|
||||
data.hashboards[idx].missing = True
|
||||
chips = board.get("Num active chips")
|
||||
if chips:
|
||||
setattr(data, f"{board_map[idx]}_chips", chips)
|
||||
temp = board.get("Temp")
|
||||
if temp:
|
||||
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
|
||||
data.hashboards[idx].chips = chips
|
||||
if chips > 0:
|
||||
data.hashboards[idx].missing = False
|
||||
|
||||
if all_data:
|
||||
if all_data.get("chain"):
|
||||
board_map = {0: "left", 1: "center", 2: "right"}
|
||||
for idx, board in enumerate(all_data["chain"]):
|
||||
temp = board.get("Temp max")
|
||||
temp = board.get("Temp min")
|
||||
if temp:
|
||||
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
|
||||
temp_board = board.get("Temp min")
|
||||
if temp_board:
|
||||
setattr(data, f"{board_map[idx]}_board_temp", temp_board)
|
||||
hr = board.get("Hash Rate H")
|
||||
if hr:
|
||||
setattr(
|
||||
data,
|
||||
f"{board_map[idx]}_board_hashrate",
|
||||
round(hr / 1000000000000, 2),
|
||||
data.hashboards[idx].temp = round(temp)
|
||||
|
||||
hashrate = board.get("Hash Rate H")
|
||||
if hashrate:
|
||||
data.hashboards[idx].hashrate = round(
|
||||
hashrate / 1000000000000, 2
|
||||
)
|
||||
|
||||
chip_temp = board.get("Temp max")
|
||||
if chip_temp:
|
||||
data.hashboards[idx].chip_temp = round(chip_temp)
|
||||
|
||||
if all_data.get("fansSpeed"):
|
||||
speed = round((all_data["fansSpeed"] * 6000) / 100)
|
||||
for fan in range(self.fan_count):
|
||||
|
||||
@@ -216,6 +216,11 @@ MINER_CLASSES = {
|
||||
"BTMiner": BTMinerM32,
|
||||
"20": BTMinerM32V20,
|
||||
},
|
||||
"M34S+": {
|
||||
"Default": BTMinerM34SPlus,
|
||||
"BTMiner": BTMinerM34SPlus,
|
||||
"E10": BTMinerM34SPlusVE10,
|
||||
},
|
||||
"M50": {
|
||||
"Default": BTMinerM50,
|
||||
"BTMiner": BTMinerM50,
|
||||
|
||||
@@ -72,5 +72,5 @@ class UnknownMiner(BaseMiner):
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
return None
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
|
||||
31
pyasic/miners/whatsminer/btminer/M3X/M34S_Plus.py
Normal file
31
pyasic/miners/whatsminer/btminer/M3X/M34S_Plus.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright 2022 Upstream Data Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import (
|
||||
M34SPlus,
|
||||
M34SPlusVE10,
|
||||
) # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM34SPlus(BTMiner, M34SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM34SPlusVE10(BTMiner, M34SPlusVE10):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -45,3 +45,4 @@ from .M31S_Plus import (
|
||||
|
||||
from .M32 import BTMinerM32, BTMinerM32V20
|
||||
from .M32S import BTMinerM32S
|
||||
from .M34S_Plus import BTMinerM34SPlus, BTMinerM34SPlusVE10
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
from .M50 import (
|
||||
BTMinerM50,
|
||||
BTMinerM50VH50,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
|
||||
from .M2X import *
|
||||
from .M3X import *
|
||||
from .M5X import *
|
||||
from .M5X import *
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.19.1"
|
||||
version = "0.20.1"
|
||||
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
|
||||
Reference in New Issue
Block a user