Merge pull request #24 from UpstreamData/crc
Merge in branch refactoring the hashboard data
This commit is contained in:
@@ -230,7 +230,7 @@ These functions are
|
|||||||
|
|
||||||
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
## [`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>
|
<br>
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
# 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"
|
||||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||||
command = "+".join(commands)
|
command = "+".join(commands)
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(command, allow_warning=ignore_x19_error)
|
data = await self.send_command(command, allow_warning=not ignore_x19_error)
|
||||||
except APIError:
|
except APIError:
|
||||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||||
data = await self._x19_multicommand(*command.split("+"))
|
data = await self._x19_multicommand(*command.split("+"))
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
# 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 pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI, APIError
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAPI(BaseMinerAPI):
|
class CGMinerAPI(BaseMinerAPI):
|
||||||
@@ -36,6 +38,37 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
def __init__(self, ip: str, port: int = 4028):
|
def __init__(self, ip: str, port: int = 4028):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
|
|
||||||
|
async def multicommand(
|
||||||
|
self, *commands: str, ignore_x19_error: bool = False
|
||||||
|
) -> 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=ignore_x19_error)
|
||||||
|
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:
|
async def version(self) -> dict:
|
||||||
"""Get miner version info.
|
"""Get miner version info.
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -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 dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict, fields
|
||||||
from typing import Literal, List
|
from typing import Literal, List
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
@@ -37,6 +37,10 @@ class _Pool:
|
|||||||
username: str = ""
|
username: str = ""
|
||||||
password: str = ""
|
password: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
"""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
|
group_name: str = None
|
||||||
pools: List[_Pool] = None
|
pools: List[_Pool] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if not self.group_name:
|
if not self.group_name:
|
||||||
self.group_name = "".join(
|
self.group_name = "".join(
|
||||||
@@ -263,6 +271,10 @@ class MinerConfig:
|
|||||||
dps_shutdown_enabled: bool = None
|
dps_shutdown_enabled: bool = None
|
||||||
dps_shutdown_duration: float = None
|
dps_shutdown_duration: float = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
"""Convert the data in this class to a dict."""
|
"""Convert the data in this class to a dict."""
|
||||||
data_dict = asdict(self)
|
data_dict = asdict(self)
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import Union, List
|
from typing import Union, List
|
||||||
from dataclasses import dataclass, field, asdict
|
from dataclasses import dataclass, field, asdict, fields
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from functools import reduce
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import copy
|
import copy
|
||||||
@@ -22,6 +23,17 @@ import copy
|
|||||||
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError
|
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
|
@dataclass
|
||||||
class MinerData:
|
class MinerData:
|
||||||
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
|
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
|
||||||
@@ -73,17 +85,19 @@ class MinerData:
|
|||||||
model: str = "Unknown"
|
model: str = "Unknown"
|
||||||
hostname: str = "Unknown"
|
hostname: str = "Unknown"
|
||||||
hashrate: float = 0
|
hashrate: float = 0
|
||||||
left_board_hashrate: float = 0.0
|
hashboards: List[HashBoard] = field(default_factory=list)
|
||||||
center_board_hashrate: float = 0.0
|
ideal_hashboards: int = 1
|
||||||
right_board_hashrate: float = 0.0
|
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)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = -1.0
|
env_temp: float = -1.0
|
||||||
left_board_temp: int = -1
|
left_board_temp: int = field(init=False)
|
||||||
left_board_chip_temp: int = -1
|
left_board_chip_temp: int = field(init=False)
|
||||||
center_board_temp: int = -1
|
center_board_temp: int = field(init=False)
|
||||||
center_board_chip_temp: int = -1
|
center_board_chip_temp: int = field(init=False)
|
||||||
right_board_temp: int = -1
|
right_board_temp: int = field(init=False)
|
||||||
right_board_chip_temp: int = -1
|
right_board_chip_temp: int = field(init=False)
|
||||||
wattage: int = -1
|
wattage: int = -1
|
||||||
wattage_limit: int = -1
|
wattage_limit: int = -1
|
||||||
fan_1: int = -1
|
fan_1: int = -1
|
||||||
@@ -91,9 +105,9 @@ class MinerData:
|
|||||||
fan_3: int = -1
|
fan_3: int = -1
|
||||||
fan_4: int = -1
|
fan_4: int = -1
|
||||||
fan_psu: int = -1
|
fan_psu: int = -1
|
||||||
left_chips: int = 0
|
left_chips: int = field(init=False)
|
||||||
center_chips: int = 0
|
center_chips: int = field(init=False)
|
||||||
right_chips: int = 0
|
right_chips: int = field(init=False)
|
||||||
total_chips: int = field(init=False)
|
total_chips: int = field(init=False)
|
||||||
ideal_chips: int = 1
|
ideal_chips: int = 1
|
||||||
percent_ideal: float = field(init=False)
|
percent_ideal: float = field(init=False)
|
||||||
@@ -109,6 +123,11 @@ 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
|
||||||
|
def 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()
|
||||||
|
|
||||||
@@ -163,12 +182,148 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
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
|
@total_chips.setter
|
||||||
def total_chips(self, val):
|
def total_chips(self, val):
|
||||||
pass
|
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
|
@property
|
||||||
def nominal(self): # noqa - Skip PyCharm inspection
|
def nominal(self): # noqa - Skip PyCharm inspection
|
||||||
return self.ideal_chips == self.total_chips
|
return self.ideal_chips == self.total_chips
|
||||||
@@ -189,13 +344,9 @@ class MinerData:
|
|||||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||||
total_temp = 0
|
total_temp = 0
|
||||||
temp_count = 0
|
temp_count = 0
|
||||||
for temp in [
|
for hb in self.hashboards:
|
||||||
self.left_board_chip_temp,
|
if hb.temp and not hb.temp == -1:
|
||||||
self.center_board_chip_temp,
|
total_temp += hb.temp
|
||||||
self.right_board_chip_temp,
|
|
||||||
]:
|
|
||||||
if temp and not temp == -1:
|
|
||||||
total_temp += temp
|
|
||||||
temp_count += 1
|
temp_count += 1
|
||||||
if not temp_count > 0:
|
if not temp_count > 0:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -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 dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -21,9 +21,15 @@ class X19Error:
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
error_message: The error message as a string.
|
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_message: str
|
||||||
|
error_code: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|||||||
@@ -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 dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -21,9 +21,16 @@ class BraiinsOSError:
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
error_message: The error message as a string.
|
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_message: str
|
||||||
|
error_code: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|||||||
@@ -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 dataclasses import dataclass, field, asdict
|
from dataclasses import dataclass, field, asdict, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -27,6 +27,11 @@ class InnosiliconError:
|
|||||||
error_code: int
|
error_code: int
|
||||||
error_message: str = field(init=False)
|
error_message: str = field(init=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def 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:
|
||||||
|
|||||||
@@ -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 dataclasses import dataclass, field, asdict
|
from dataclasses import dataclass, field, asdict, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -27,6 +27,11 @@ class WhatsminerError:
|
|||||||
error_code: int
|
error_code: int
|
||||||
error_message: str = field(init=False)
|
error_message: str = field(init=False)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def 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:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from typing import Union, List
|
|||||||
from pyasic.API.bmminer import BMMinerAPI
|
from pyasic.API.bmminer import BMMinerAPI
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData, HashBoard
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
|
|
||||||
@@ -191,7 +191,11 @@ class BMMiner(BaseMiner):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
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
|
board_offset = -1
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
@@ -242,7 +246,7 @@ class BMMiner(BaseMiner):
|
|||||||
if stats:
|
if stats:
|
||||||
boards = stats.get("STATS")
|
boards = stats.get("STATS")
|
||||||
if boards:
|
if boards:
|
||||||
if len(boards) > 0:
|
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):
|
||||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||||
@@ -252,30 +256,38 @@ class BMMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
env_temp_list = []
|
||||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
hashboard = HashBoard(
|
||||||
|
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
data.left_board_hashrate = round(
|
if chip_temp:
|
||||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
hashboard.chip_temp = round(chip_temp)
|
||||||
)
|
|
||||||
except ValueError as e:
|
temp = boards[1].get(f"temp2_{i}")
|
||||||
data.left_board_hashrate = round(0.00, 2)
|
if temp:
|
||||||
try:
|
hashboard.temp = round(temp)
|
||||||
data.center_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000,
|
hashrate = boards[1].get(f"chain_rate{i}")
|
||||||
2,
|
if hashrate:
|
||||||
)
|
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||||
except ValueError as e:
|
|
||||||
data.center_board_hashrate = round(0.00, 2)
|
chips = boards[1].get(f"chain_acn{i}")
|
||||||
try:
|
if chips:
|
||||||
data.right_board_hashrate = round(
|
hashboard.chips = chips
|
||||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000,
|
hashboard.missing = False
|
||||||
2,
|
if (not chips) or (not chips > 0):
|
||||||
)
|
hashboard.missing = True
|
||||||
except ValueError as e:
|
data.hashboards.append(hashboard)
|
||||||
data.right_board_hashrate = round(0.00, 2)
|
|
||||||
|
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:
|
if stats:
|
||||||
temp = stats.get("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}")
|
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:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
pool_2 = None
|
pool_2 = None
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from pyasic.API.bosminer import BOSMinerAPI
|
|||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
|
|
||||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData, HashBoard
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
@@ -266,7 +266,10 @@ class BOSMiner(BaseMiner):
|
|||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
|
|
||||||
if tunerstatus:
|
if tunerstatus:
|
||||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
try:
|
||||||
|
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||||
|
except KeyError:
|
||||||
|
tuner = tunerstatus.get("TUNERSTATUS")
|
||||||
if tuner:
|
if tuner:
|
||||||
if len(tuner) > 0:
|
if len(tuner) > 0:
|
||||||
chain_status = tuner[0].get("TunerChainStatus")
|
chain_status = tuner[0].get("TunerChainStatus")
|
||||||
@@ -300,7 +303,15 @@ class BOSMiner(BaseMiner):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
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
|
board_offset = -1
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
@@ -361,14 +372,13 @@ class BOSMiner(BaseMiner):
|
|||||||
temp = temps[0].get("TEMPS")
|
temp = temps[0].get("TEMPS")
|
||||||
if temp:
|
if temp:
|
||||||
if len(temp) > 0:
|
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"]
|
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
||||||
for board in temp:
|
for board in temp:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
chip_temp = round(board["Chip"])
|
chip_temp = round(board["Chip"])
|
||||||
board_temp = round(board["Board"])
|
board_temp = round(board["Board"])
|
||||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
data.hashboards[_id].chip_temp = chip_temp
|
||||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
data.hashboards[_id].temp = board_temp
|
||||||
|
|
||||||
if fans:
|
if fans:
|
||||||
fan_data = fans[0].get("FANS")
|
fan_data = fans[0].get("FANS")
|
||||||
@@ -461,27 +471,25 @@ class BOSMiner(BaseMiner):
|
|||||||
boards = devdetails[0].get("DEVDETAILS")
|
boards = devdetails[0].get("DEVDETAILS")
|
||||||
if boards:
|
if boards:
|
||||||
if len(boards) > 0:
|
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"]
|
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||||
for board in boards:
|
for board in boards:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
chips = board["Chips"]
|
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:
|
if devs:
|
||||||
boards = devs[0].get("DEVS")
|
boards = devs[0].get("DEVS")
|
||||||
if boards:
|
if boards:
|
||||||
if len(boards) > 0:
|
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"]
|
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||||
for board in boards:
|
for board in boards:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
||||||
setattr(data, board_map[_id], hashrate)
|
data.hashboards[_id].hashrate = hashrate
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def get_mac(self):
|
async def get_mac(self):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from pyasic.API.btminer import BTMinerAPI
|
|||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
from pyasic.errors import APIError
|
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.data.error_codes import WhatsminerError, MinerErrorData
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
@@ -109,7 +109,6 @@ class BTMiner(BaseMiner):
|
|||||||
if "Code" in data.keys():
|
if "Code" in data.keys():
|
||||||
if data["Code"] == 131:
|
if data["Code"] == 131:
|
||||||
return True
|
return True
|
||||||
print(data)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
async def check_light(self) -> bool:
|
||||||
@@ -166,28 +165,22 @@ class BTMiner(BaseMiner):
|
|||||||
for err in err_data["Msg"]["error_code"]:
|
for err in err_data["Msg"]["error_code"]:
|
||||||
if isinstance(err, dict):
|
if isinstance(err, dict):
|
||||||
for code in err:
|
for code in err:
|
||||||
data.append(
|
data.append(WhatsminerError(error_code=int(code)))
|
||||||
WhatsminerError(
|
|
||||||
error_code=int(code)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
data.append(
|
data.append(WhatsminerError(error_code=int(err)))
|
||||||
WhatsminerError(
|
|
||||||
error_code=int(err)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except APIError:
|
except APIError:
|
||||||
summary_data = await self.api.summary()
|
summary_data = await self.api.summary()
|
||||||
if summary_data[0].get("Error Code Count"):
|
if summary_data.get("SUMMARY"):
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
summary_data = summary_data["SUMMARY"]
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
if summary_data[0].get("Error Code Count"):
|
||||||
if not summary_data[0][f"Error Code {i}"] == "":
|
for i in range(summary_data[0]["Error Code Count"]):
|
||||||
data.append(
|
if summary_data[0].get(f"Error Code {i}"):
|
||||||
WhatsminerError(
|
if not summary_data[0][f"Error Code {i}"] == "":
|
||||||
error_code=summary_data[0][f"Error Code {i}"]
|
data.append(
|
||||||
|
WhatsminerError(
|
||||||
|
error_code=summary_data[0][f"Error Code {i}"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -254,7 +247,11 @@ class BTMiner(BaseMiner):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
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
|
mac = None
|
||||||
|
|
||||||
@@ -361,44 +358,25 @@ class BTMiner(BaseMiner):
|
|||||||
if isinstance(err, dict):
|
if isinstance(err, dict):
|
||||||
for code in err:
|
for code in err:
|
||||||
data.errors.append(
|
data.errors.append(
|
||||||
WhatsminerError(
|
WhatsminerError(error_code=int(code))
|
||||||
error_code=int(code)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data.errors.append(
|
data.errors.append(WhatsminerError(error_code=int(err)))
|
||||||
WhatsminerError(
|
|
||||||
error_code=int(err)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if devs:
|
if devs:
|
||||||
temp_data = devs.get("DEVS")
|
dev_data = devs.get("DEVS")
|
||||||
if temp_data:
|
if dev_data:
|
||||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
for board in dev_data:
|
||||||
for board in temp_data:
|
temp_board = HashBoard(
|
||||||
_id = board["ASC"]
|
slot=board["ASC"],
|
||||||
chip_temp = round(board["Chip Temp Avg"])
|
chip_temp=round(board["Chip Temp Avg"]),
|
||||||
board_temp = round(board["Temperature"])
|
temp=round(board["Temperature"]),
|
||||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
hashrate=round(board["MHS 1m"] / 1000000, 2),
|
||||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
chips=board["Effective Chips"],
|
||||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
missing=False if board["Effective Chips"] > 0 else True,
|
||||||
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
|
expected_chips=self.nominal_chips,
|
||||||
|
)
|
||||||
if devs:
|
data.hashboards.append(temp_board)
|
||||||
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)
|
|
||||||
|
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from pyasic.miners.base import BaseMiner
|
|||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
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.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
@@ -179,7 +179,11 @@ class CGMiner(BaseMiner):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
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
|
board_offset = -1
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
@@ -202,7 +206,7 @@ class CGMiner(BaseMiner):
|
|||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"summary", "pools", "stats", ignore_x19_error=True
|
"summary", "pools", "stats",
|
||||||
)
|
)
|
||||||
if miner_data:
|
if miner_data:
|
||||||
break
|
break
|
||||||
@@ -235,19 +239,38 @@ class CGMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
env_temp_list = []
|
||||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
hashboard = HashBoard(
|
||||||
|
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||||
|
)
|
||||||
|
|
||||||
data.left_board_hashrate = round(
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
if chip_temp:
|
||||||
)
|
hashboard.chip_temp = round(chip_temp)
|
||||||
data.center_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
|
temp = boards[1].get(f"temp2_{i}")
|
||||||
)
|
if temp:
|
||||||
data.right_board_hashrate = round(
|
hashboard.temp = round(temp)
|
||||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
|
|
||||||
)
|
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:
|
if stats:
|
||||||
temp = stats.get("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}")
|
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:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
pool_2 = None
|
pool_2 = None
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ class M34SPlus(BaseMiner):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M34S+"
|
self.model = "M34S+"
|
||||||
# TODO: has 4 boards instead of 3
|
self.ideal_hashboards = 4
|
||||||
self.nominal_chips = 464
|
self.nominal_chips = 116
|
||||||
self.fan_count = 2
|
self.fan_count = 0
|
||||||
|
|
||||||
|
|
||||||
class M34SPlusVE10(BaseMiner):
|
class M34SPlusVE10(BaseMiner):
|
||||||
@@ -30,5 +30,6 @@ class M34SPlusVE10(BaseMiner):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M34S+ VE10"
|
self.model = "M34S+ VE10"
|
||||||
self.nominal_chips = 464
|
self.ideal_hashboards = 4
|
||||||
self.fan_count = 2
|
self.nominal_chips = 116
|
||||||
|
self.fan_count = 0
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
|
|
||||||
from .M2X import *
|
from .M2X import *
|
||||||
from .M3X 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._backends import Hiveon # noqa - Ignore access to _module
|
||||||
from pyasic.miners._types import T9 # noqa - Ignore access to _module
|
from pyasic.miners._types import T9 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData, HashBoard
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,11 @@ class HiveonT9(Hiveon, T9):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
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
|
board_offset = -1
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
@@ -97,14 +101,17 @@ class HiveonT9(Hiveon, T9):
|
|||||||
)
|
)
|
||||||
|
|
||||||
board_map = {
|
board_map = {
|
||||||
"left": [2, 9, 10],
|
0: [2, 9, 10],
|
||||||
"center": [3, 11, 12],
|
1: [3, 11, 12],
|
||||||
"right": [4, 13, 14],
|
2: [4, 13, 14],
|
||||||
}
|
}
|
||||||
|
|
||||||
env_temp_list = []
|
env_temp_list = []
|
||||||
|
|
||||||
for board in board_map.keys():
|
for board in board_map.keys():
|
||||||
|
hashboard = HashBoard(
|
||||||
|
slot=board, expected_chips=self.nominal_chips
|
||||||
|
)
|
||||||
chips = 0
|
chips = 0
|
||||||
hashrate = 0
|
hashrate = 0
|
||||||
chip_temp = 0
|
chip_temp = 0
|
||||||
@@ -121,10 +128,15 @@ class HiveonT9(Hiveon, T9):
|
|||||||
|
|
||||||
hashrate += boards[1][f"chain_rate{chipset}"]
|
hashrate += boards[1][f"chain_rate{chipset}"]
|
||||||
chips += boards[1][f"chain_acn{chipset}"]
|
chips += boards[1][f"chain_acn{chipset}"]
|
||||||
setattr(data, f"{board}_chips", chips)
|
hashboard.hashrate = hashrate
|
||||||
setattr(data, f"{board}_board_hashrate", hashrate)
|
hashboard.chips = chips
|
||||||
setattr(data, f"{board}_board_temp", board_temp)
|
hashboard.temp = board_temp
|
||||||
setattr(data, f"{board}_board_chip_temp", chip_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 == []:
|
if not env_temp_list == []:
|
||||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,15 @@ class CGMinerA10X(CGMiner):
|
|||||||
return mac
|
return mac
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self):
|
||||||
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)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
model = await self.get_model()
|
model = await self.get_model()
|
||||||
mac = None
|
mac = None
|
||||||
@@ -145,27 +153,20 @@ class CGMinerA10X(CGMiner):
|
|||||||
f"fan_{fan+1}",
|
f"fan_{fan+1}",
|
||||||
int(raw_data[f"Fan{fan+1}"]),
|
int(raw_data[f"Fan{fan+1}"]),
|
||||||
)
|
)
|
||||||
if "MTmax" in raw_data.keys():
|
for board in range(self.ideal_hashboards):
|
||||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
chip_temp = raw_data.get("MTmax")
|
||||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
if chip_temp:
|
||||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
data.hashboards[board].chip_temp = chip_temp[board]
|
||||||
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])
|
|
||||||
|
|
||||||
if "PVT_T0" in raw_data:
|
temp = raw_data.get("MTavg")
|
||||||
data.left_chips = len(
|
if temp:
|
||||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
data.hashboards[board].temp = temp[board]
|
||||||
)
|
|
||||||
if "PVT_T1" in raw_data:
|
chips = raw_data.get(f"PVT_T{board}")
|
||||||
data.center_chips = len(
|
if chips:
|
||||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
data.hashboards[board].chips = len(
|
||||||
)
|
[item for item in chips 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"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
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
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -81,7 +81,15 @@ class CGMinerA7X(CGMiner):
|
|||||||
return mac
|
return mac
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self):
|
||||||
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)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
model = await self.get_model()
|
model = await self.get_model()
|
||||||
mac = None
|
mac = None
|
||||||
@@ -145,27 +153,20 @@ class CGMinerA7X(CGMiner):
|
|||||||
f"fan_{fan+1}",
|
f"fan_{fan+1}",
|
||||||
int(raw_data[f"Fan{fan+1}"]),
|
int(raw_data[f"Fan{fan+1}"]),
|
||||||
)
|
)
|
||||||
if "MTmax" in raw_data.keys():
|
for board in range(self.ideal_hashboards):
|
||||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
chip_temp = raw_data.get("MTmax")
|
||||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
if chip_temp:
|
||||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
data.hashboards[board].chip_temp = chip_temp[board]
|
||||||
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])
|
|
||||||
|
|
||||||
if "PVT_T0" in raw_data:
|
temp = raw_data.get("MTavg")
|
||||||
data.left_chips = len(
|
if temp:
|
||||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
data.hashboards[board].temp = temp[board]
|
||||||
)
|
|
||||||
if "PVT_T1" in raw_data:
|
chips = raw_data.get(f"PVT_T{board}")
|
||||||
data.center_chips = len(
|
if chips:
|
||||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
data.hashboards[board].chips = len(
|
||||||
)
|
[item for item in chips 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"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
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
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -81,7 +81,15 @@ class CGMinerA8X(CGMiner):
|
|||||||
return mac
|
return mac
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self):
|
||||||
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)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
model = await self.get_model()
|
model = await self.get_model()
|
||||||
mac = None
|
mac = None
|
||||||
@@ -145,27 +153,20 @@ class CGMinerA8X(CGMiner):
|
|||||||
f"fan_{fan+1}",
|
f"fan_{fan+1}",
|
||||||
int(raw_data[f"Fan{fan+1}"]),
|
int(raw_data[f"Fan{fan+1}"]),
|
||||||
)
|
)
|
||||||
if "MTmax" in raw_data.keys():
|
for board in range(self.ideal_hashboards):
|
||||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
chip_temp = raw_data.get("MTmax")
|
||||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
if chip_temp:
|
||||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
data.hashboards[board].chip_temp = chip_temp[board]
|
||||||
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])
|
|
||||||
|
|
||||||
if "PVT_T0" in raw_data:
|
temp = raw_data.get("MTavg")
|
||||||
data.left_chips = len(
|
if temp:
|
||||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
data.hashboards[board].temp = temp[board]
|
||||||
)
|
|
||||||
if "PVT_T1" in raw_data:
|
chips = raw_data.get(f"PVT_T{board}")
|
||||||
data.center_chips = len(
|
if chips:
|
||||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
data.hashboards[board].chips = len(
|
||||||
)
|
[item for item in chips 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"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||||
from pyasic.miners._types import Avalon921 # 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
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -82,7 +82,15 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
|||||||
return mac
|
return mac
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self):
|
||||||
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)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
model = await self.get_model()
|
model = await self.get_model()
|
||||||
mac = None
|
mac = None
|
||||||
@@ -146,27 +154,20 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
|||||||
f"fan_{fan+1}",
|
f"fan_{fan+1}",
|
||||||
int(raw_data[f"Fan{fan+1}"]),
|
int(raw_data[f"Fan{fan+1}"]),
|
||||||
)
|
)
|
||||||
if "MTmax" in raw_data.keys():
|
for board in range(self.ideal_hashboards):
|
||||||
data.left_board_chip_temp = int(raw_data["MTmax"][0])
|
chip_temp = raw_data.get("MTmax")
|
||||||
data.center_board_chip_temp = int(raw_data["MTmax"][1])
|
if chip_temp:
|
||||||
data.right_board_chip_temp = int(raw_data["MTmax"][2])
|
data.hashboards[board].chip_temp = chip_temp[board]
|
||||||
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])
|
|
||||||
|
|
||||||
if "PVT_T0" in raw_data:
|
temp = raw_data.get("MTavg")
|
||||||
data.left_chips = len(
|
if temp:
|
||||||
[item for item in raw_data["PVT_T0"] if not item == "0"]
|
data.hashboards[board].temp = temp[board]
|
||||||
)
|
|
||||||
if "PVT_T1" in raw_data:
|
chips = raw_data.get(f"PVT_T{board}")
|
||||||
data.center_chips = len(
|
if chips:
|
||||||
[item for item in raw_data["PVT_T1"] if not item == "0"]
|
data.hashboards[board].chips = len(
|
||||||
)
|
[item for item in chips 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"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if pools:
|
if pools:
|
||||||
pool_1 = None
|
pool_1 = None
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class BaseMiner(ABC):
|
|||||||
self.version = None
|
self.version = None
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
self.config = None
|
self.config = None
|
||||||
|
self.ideal_hashboards = 3
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls is BaseMiner:
|
if cls is BaseMiner:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||||
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
|
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData, HashBoard
|
||||||
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
|
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
@@ -158,7 +158,15 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
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
|
board_offset = -1
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
@@ -215,32 +223,31 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
if stats:
|
if stats:
|
||||||
stats = stats[0]
|
stats = stats[0]
|
||||||
if stats.get("STATS"):
|
if stats.get("STATS"):
|
||||||
board_map = {0: "left", 1: "center", 2: "right"}
|
|
||||||
for idx, board in enumerate(stats["STATS"]):
|
for idx, board in enumerate(stats["STATS"]):
|
||||||
|
data.hashboards[idx].missing = True
|
||||||
chips = board.get("Num active chips")
|
chips = board.get("Num active chips")
|
||||||
if chips:
|
if chips:
|
||||||
setattr(data, f"{board_map[idx]}_chips", chips)
|
data.hashboards[idx].chips = chips
|
||||||
temp = board.get("Temp")
|
if chips > 0:
|
||||||
if temp:
|
data.hashboards[idx].missing = False
|
||||||
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
|
|
||||||
|
|
||||||
if all_data:
|
if all_data:
|
||||||
if all_data.get("chain"):
|
if all_data.get("chain"):
|
||||||
board_map = {0: "left", 1: "center", 2: "right"}
|
|
||||||
for idx, board in enumerate(all_data["chain"]):
|
for idx, board in enumerate(all_data["chain"]):
|
||||||
temp = board.get("Temp max")
|
temp = board.get("Temp min")
|
||||||
if temp:
|
if temp:
|
||||||
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
|
data.hashboards[idx].temp = round(temp)
|
||||||
temp_board = board.get("Temp min")
|
|
||||||
if temp_board:
|
hashrate = board.get("Hash Rate H")
|
||||||
setattr(data, f"{board_map[idx]}_board_temp", temp_board)
|
if hashrate:
|
||||||
hr = board.get("Hash Rate H")
|
data.hashboards[idx].hashrate = round(
|
||||||
if hr:
|
hashrate / 1000000000000, 2
|
||||||
setattr(
|
|
||||||
data,
|
|
||||||
f"{board_map[idx]}_board_hashrate",
|
|
||||||
round(hr / 1000000000000, 2),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
chip_temp = board.get("Temp max")
|
||||||
|
if chip_temp:
|
||||||
|
data.hashboards[idx].chip_temp = round(chip_temp)
|
||||||
|
|
||||||
if all_data.get("fansSpeed"):
|
if all_data.get("fansSpeed"):
|
||||||
speed = round((all_data["fansSpeed"] * 6000) / 100)
|
speed = round((all_data["fansSpeed"] * 6000) / 100)
|
||||||
for fan in range(self.fan_count):
|
for fan in range(self.fan_count):
|
||||||
|
|||||||
@@ -15,4 +15,4 @@
|
|||||||
from .M50 import (
|
from .M50 import (
|
||||||
BTMinerM50,
|
BTMinerM50,
|
||||||
BTMinerM50VH50,
|
BTMinerM50VH50,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
|
|
||||||
from .M2X import *
|
from .M2X import *
|
||||||
from .M3X import *
|
from .M3X import *
|
||||||
from .M5X import *
|
from .M5X import *
|
||||||
|
|||||||
Reference in New Issue
Block a user