Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3fd94e79e | ||
|
|
2924a8d67b | ||
|
|
9f4c4bb9cf | ||
|
|
3d6eebf06e | ||
|
|
b3d9b6ff7e | ||
|
|
60facacc48 | ||
|
|
b8a6063838 | ||
|
|
bcba2be524 | ||
|
|
f7187d2017 | ||
|
|
d91b7c4406 | ||
|
|
248a7e6d69 | ||
|
|
4f2c3e772a | ||
|
|
95f7146eef | ||
|
|
9d5d19cc6b | ||
|
|
cc38129571 | ||
|
|
3dfd9f237d | ||
|
|
f3fe478dbb | ||
|
|
e10f32ae3d | ||
|
|
4e0924aa0e | ||
|
|
d0d3fd3117 | ||
|
|
4de950d8f4 | ||
|
|
03f2a1f9ba | ||
|
|
2653db90e3 | ||
|
|
ddc8c53eb9 | ||
|
|
eb5d1a24ea | ||
|
|
6c0e80265b | ||
|
|
ad3a4ae414 | ||
|
|
3484d43510 | ||
|
|
dd7e352391 | ||
|
|
a32b61fe5d | ||
|
|
597a178009 | ||
|
|
409b2527f0 | ||
|
|
58234fcf7f | ||
|
|
1bf863cca8 | ||
|
|
6482d04185 | ||
|
|
3b58b11501 | ||
|
|
7485b8ef77 | ||
|
|
d2bea227db | ||
|
|
1b7afaaf7e | ||
|
|
96898d639c |
@@ -15,6 +15,7 @@ Use these instead -
|
|||||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||||
|
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
|
||||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
7
docs/API/luxminer.md
Normal file
7
docs/API/luxminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## LUXMinerAPI
|
||||||
|
::: pyasic.API.luxminer.LUXMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -76,13 +76,14 @@ This function will return an instance of the dataclass [`MinerData`][pyasic.data
|
|||||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic import get_miner
|
||||||
|
|
||||||
async def gather_miner_data():
|
async def gather_miner_data():
|
||||||
miner = await MinerFactory().get_miner("192.168.1.75")
|
miner = await get_miner("192.168.1.75")
|
||||||
miner_data = await miner.get_data()
|
if miner is not None:
|
||||||
print(miner_data) # all data from the dataclass
|
miner_data = await miner.get_data()
|
||||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
print(miner_data) # all data from the dataclass
|
||||||
|
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(gather_miner_data())
|
asyncio.run(gather_miner_data())
|
||||||
|
|||||||
8
docs/miners/backends/luxminer.md
Normal file
8
docs/miners/backends/luxminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## LUXMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.luxminer.LUXMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/vnish.md
Normal file
8
docs/miners/backends/vnish.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## VNish Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.vnish.VNish
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
10
mkdocs.yml
10
mkdocs.yml
@@ -20,6 +20,7 @@ nav:
|
|||||||
- BOSMiner: "API/bosminer.md"
|
- BOSMiner: "API/bosminer.md"
|
||||||
- BTMiner: "API/btminer.md"
|
- BTMiner: "API/btminer.md"
|
||||||
- CGMiner: "API/cgminer.md"
|
- CGMiner: "API/cgminer.md"
|
||||||
|
- LUXMiner: "API/luxminer.md"
|
||||||
- Unknown: "API/unknown.md"
|
- Unknown: "API/unknown.md"
|
||||||
- Backends:
|
- Backends:
|
||||||
- BMMiner: "miners/backends/bmminer.md"
|
- BMMiner: "miners/backends/bmminer.md"
|
||||||
@@ -27,6 +28,8 @@ nav:
|
|||||||
- BFGMiner: "miners/backends/bfgminer.md"
|
- BFGMiner: "miners/backends/bfgminer.md"
|
||||||
- BTMiner: "miners/backends/btminer.md"
|
- BTMiner: "miners/backends/btminer.md"
|
||||||
- CGMiner: "miners/backends/cgminer.md"
|
- CGMiner: "miners/backends/cgminer.md"
|
||||||
|
- LUXMiner: "miners/backends/luxminer.md"
|
||||||
|
- VNish: "miners/backends/vnish.md"
|
||||||
- Hiveon: "miners/backends/hiveon.md"
|
- Hiveon: "miners/backends/hiveon.md"
|
||||||
- Classes:
|
- Classes:
|
||||||
- Antminer X3: "miners/antminer/X3.md"
|
- Antminer X3: "miners/antminer/X3.md"
|
||||||
@@ -40,14 +43,15 @@ nav:
|
|||||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||||
- Avalon 9X: "miners/avalonminer/A9X.md"
|
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||||
|
- Avalon 11X: "miners/avalonminer/A11X.md"
|
||||||
|
- Avalon 12X: "miners/avalonminer/A12X.md"
|
||||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||||
- Goldshell CKX: "miners/goldshell/CKX.md"
|
- Goldshell X5: "miners/goldshell/X5.md"
|
||||||
- Goldshell HSX: "miners/goldshell/HSX.md"
|
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||||
- Goldshell KDX: "miners/goldshell/KDX.md"
|
|
||||||
- Base Miner: "miners/base_miner.md"
|
- Base Miner: "miners/base_miner.md"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Union
|
from typing import Tuple, Union
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
|
|
||||||
@@ -128,6 +128,18 @@ class BaseMinerAPI:
|
|||||||
data["multicommand"] = True
|
data["multicommand"] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def _handle_multicommand(self, command: str, allow_warning: bool = True):
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
if not "+" in command:
|
||||||
|
return {command: [data]}
|
||||||
|
return data
|
||||||
|
|
||||||
|
except APIError:
|
||||||
|
if "+" in command:
|
||||||
|
return {command: [{}] for command in command.split("+")}
|
||||||
|
return {command: [{}]}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def commands(self) -> list:
|
def commands(self) -> list:
|
||||||
return self.get_commands()
|
return self.get_commands()
|
||||||
@@ -171,7 +183,11 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
)
|
)
|
||||||
return return_commands
|
return return_commands
|
||||||
|
|
||||||
async def _send_bytes(self, data: bytes, timeout: int = 100) -> bytes:
|
async def _send_bytes(
|
||||||
|
self,
|
||||||
|
data: bytes,
|
||||||
|
timeout: int = 100,
|
||||||
|
) -> bytes:
|
||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -56,19 +56,19 @@ class BFGMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands) -> dict:
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -57,21 +58,19 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(
|
|
||||||
await self.send_command(cmd, allow_warning=allow_warning)
|
|
||||||
)
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -203,27 +203,35 @@ class BTMinerAPI(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"
|
||||||
# commands starting with "get_" aren't supported, but we can fake that
|
# commands starting with "get_" and the "status" command aren't supported, but we can fake that
|
||||||
get_commands_data = {}
|
|
||||||
|
tasks = []
|
||||||
|
|
||||||
for command in list(commands):
|
for command in list(commands):
|
||||||
if command.startswith("get_"):
|
if command.startswith("get_") or command == "status":
|
||||||
commands.remove(command)
|
commands.remove(command)
|
||||||
# send seperately and append later
|
# send seperately and append later
|
||||||
try:
|
tasks.append(
|
||||||
get_commands_data[command] = [
|
asyncio.create_task(
|
||||||
await self.send_command(command, allow_warning=allow_warning)
|
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||||
]
|
)
|
||||||
except APIError:
|
)
|
||||||
get_commands_data[command] = [{}]
|
|
||||||
|
|
||||||
command = "+".join(commands)
|
command = "+".join(commands)
|
||||||
try:
|
tasks.append(
|
||||||
main_data = await self.send_command(command, allow_warning=allow_warning)
|
asyncio.create_task(
|
||||||
except APIError:
|
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||||
main_data = {command: [{}] for command in commands}
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
|
|
||||||
data = dict(**main_data, **get_commands_data)
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
data["multicommand"] = True
|
data["multicommand"] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -56,19 +56,19 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands) -> dict:
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -560,9 +560,6 @@ class MinerConfig:
|
|||||||
if self.fan_speed:
|
if self.fan_speed:
|
||||||
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
||||||
|
|
||||||
if self.miner_mode == X19PowerMode.Sleep:
|
|
||||||
cfg["freq-level"] = "0"
|
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def as_x17(self, user_suffix: str = None) -> dict:
|
def as_x17(self, user_suffix: str = None) -> dict:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
from dataclasses import asdict, dataclass, field, fields
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import List, Union
|
from typing import Any, List, Union
|
||||||
|
|
||||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||||
|
|
||||||
@@ -40,13 +40,28 @@ class HashBoard:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
slot: int = 0
|
slot: int = 0
|
||||||
hashrate: float = 0.0
|
hashrate: float = None
|
||||||
temp: int = -1
|
temp: int = None
|
||||||
chip_temp: int = -1
|
chip_temp: int = None
|
||||||
chips: int = 0
|
chips: int = None
|
||||||
expected_chips: int = 0
|
expected_chips: int = None
|
||||||
missing: bool = True
|
missing: bool = True
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Fan:
|
class Fan:
|
||||||
@@ -56,7 +71,22 @@ class Fan:
|
|||||||
speed: The speed of the fan.
|
speed: The speed of the fan.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
speed: int = -1
|
speed: int = None
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -102,26 +132,26 @@ class MinerData:
|
|||||||
|
|
||||||
ip: str
|
ip: str
|
||||||
datetime: datetime = None
|
datetime: datetime = None
|
||||||
uptime: int = 0
|
uptime: int = None
|
||||||
mac: str = "00:00:00:00:00:00"
|
mac: str = None
|
||||||
model: str = "Unknown"
|
model: str = None
|
||||||
make: str = "Unknown"
|
make: str = None
|
||||||
api_ver: str = "Unknown"
|
api_ver: str = None
|
||||||
fw_ver: str = "Unknown"
|
fw_ver: str = None
|
||||||
hostname: str = "Unknown"
|
hostname: str = None
|
||||||
hashrate: float = field(init=False)
|
hashrate: float = field(init=False)
|
||||||
_hashrate: float = 0
|
_hashrate: float = None
|
||||||
nominal_hashrate: float = 0
|
nominal_hashrate: float = None
|
||||||
hashboards: List[HashBoard] = field(default_factory=list)
|
hashboards: List[HashBoard] = field(default_factory=list)
|
||||||
ideal_hashboards: int = 1
|
ideal_hashboards: int = None
|
||||||
temperature_avg: int = field(init=False)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = -1.0
|
env_temp: float = None
|
||||||
wattage: int = -1
|
wattage: int = None
|
||||||
wattage_limit: int = -1
|
wattage_limit: int = None
|
||||||
fans: List[Fan] = field(default_factory=list)
|
fans: List[Fan] = field(default_factory=list)
|
||||||
fan_psu: int = -1
|
fan_psu: int = None
|
||||||
total_chips: int = field(init=False)
|
total_chips: int = field(init=False)
|
||||||
ideal_chips: int = 1
|
ideal_chips: int = None
|
||||||
percent_ideal_chips: float = field(init=False)
|
percent_ideal_chips: float = field(init=False)
|
||||||
percent_ideal_hashrate: float = field(init=False)
|
percent_ideal_hashrate: float = field(init=False)
|
||||||
percent_ideal_wattage: float = field(init=False)
|
percent_ideal_wattage: float = field(init=False)
|
||||||
@@ -145,7 +175,16 @@ class MinerData:
|
|||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
try:
|
try:
|
||||||
return getattr(self, item)
|
return getattr(self, item)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -197,7 +236,12 @@ class MinerData:
|
|||||||
@property
|
@property
|
||||||
def hashrate(self): # noqa - Skip PyCharm inspection
|
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
if len(self.hashboards) > 0:
|
if len(self.hashboards) > 0:
|
||||||
return round(sum(map(lambda x: x.hashrate, self.hashboards)), 2)
|
hr_data = []
|
||||||
|
for item in self.hashboards:
|
||||||
|
if item.hashrate is not None:
|
||||||
|
hr_data.append(item.hashrate)
|
||||||
|
if len(hr_data) > 0:
|
||||||
|
return sum(hr_data)
|
||||||
return self._hashrate
|
return self._hashrate
|
||||||
|
|
||||||
@hashrate.setter
|
@hashrate.setter
|
||||||
@@ -206,7 +250,14 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||||
return sum([hb.chips for hb in self.hashboards])
|
if len(self.hashboards) > 0:
|
||||||
|
chip_data = []
|
||||||
|
for item in self.hashboards:
|
||||||
|
if item.chips is not None:
|
||||||
|
chip_data.append(item.chips)
|
||||||
|
if len(chip_data) > 0:
|
||||||
|
return sum(chip_data)
|
||||||
|
return None
|
||||||
|
|
||||||
@total_chips.setter
|
@total_chips.setter
|
||||||
def total_chips(self, val):
|
def total_chips(self, val):
|
||||||
@@ -214,6 +265,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def nominal(self): # noqa - Skip PyCharm inspection
|
def nominal(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.total_chips is None or self.ideal_chips is None:
|
||||||
|
return None
|
||||||
return self.ideal_chips == self.total_chips
|
return self.ideal_chips == self.total_chips
|
||||||
|
|
||||||
@nominal.setter
|
@nominal.setter
|
||||||
@@ -222,6 +275,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.total_chips is None or self.ideal_chips is None:
|
||||||
|
return None
|
||||||
if self.total_chips == 0 or self.ideal_chips == 0:
|
if self.total_chips == 0 or self.ideal_chips == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.total_chips / self.ideal_chips) * 100)
|
return round((self.total_chips / self.ideal_chips) * 100)
|
||||||
@@ -232,6 +287,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.hashrate is None or self.nominal_hashrate is None:
|
||||||
|
return None
|
||||||
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.hashrate / self.nominal_hashrate) * 100)
|
return round((self.hashrate / self.nominal_hashrate) * 100)
|
||||||
@@ -242,6 +299,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.wattage_limit is None or self.wattage is None:
|
||||||
|
return None
|
||||||
if self.wattage_limit == 0 or self.wattage == 0:
|
if self.wattage_limit == 0 or self.wattage == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.wattage / self.wattage_limit) * 100)
|
return round((self.wattage / self.wattage_limit) * 100)
|
||||||
@@ -255,11 +314,11 @@ class MinerData:
|
|||||||
total_temp = 0
|
total_temp = 0
|
||||||
temp_count = 0
|
temp_count = 0
|
||||||
for hb in self.hashboards:
|
for hb in self.hashboards:
|
||||||
if hb.temp and not hb.temp == -1:
|
if hb.temp is not None:
|
||||||
total_temp += hb.temp
|
total_temp += hb.temp
|
||||||
temp_count += 1
|
temp_count += 1
|
||||||
if not temp_count > 0:
|
if not temp_count > 0:
|
||||||
return 0
|
return None
|
||||||
return round(total_temp / temp_count)
|
return round(total_temp / temp_count)
|
||||||
|
|
||||||
@temperature_avg.setter
|
@temperature_avg.setter
|
||||||
@@ -268,7 +327,9 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||||
if self.hashrate == 0 or self.wattage == -1:
|
if self.hashrate is None or self.wattage is None:
|
||||||
|
return None
|
||||||
|
if self.hashrate == 0 or self.wattage == 0:
|
||||||
return 0
|
return 0
|
||||||
return round(self.wattage / self.hashrate)
|
return round(self.wattage / self.hashrate)
|
||||||
|
|
||||||
@@ -328,7 +389,7 @@ class MinerData:
|
|||||||
tags = ["ip", "mac", "model", "hostname"]
|
tags = ["ip", "mac", "model", "hostname"]
|
||||||
for attribute in self:
|
for attribute in self:
|
||||||
if attribute in tags:
|
if attribute in tags:
|
||||||
escaped_data = self[attribute].replace(" ", "\\ ")
|
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||||
tag_data.append(f"{attribute}={escaped_data}")
|
tag_data.append(f"{attribute}={escaped_data}")
|
||||||
continue
|
continue
|
||||||
elif str(attribute).startswith("_"):
|
elif str(attribute).startswith("_"):
|
||||||
@@ -345,26 +406,28 @@ class MinerData:
|
|||||||
elif isinstance(self[attribute], float):
|
elif isinstance(self[attribute], float):
|
||||||
field_data.append(f"{attribute}={self[attribute]}")
|
field_data.append(f"{attribute}={self[attribute]}")
|
||||||
continue
|
continue
|
||||||
elif attribute == "fault_light" and not self[attribute]:
|
|
||||||
field_data.append(f"{attribute}=false")
|
|
||||||
continue
|
|
||||||
elif attribute == "errors":
|
elif attribute == "errors":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||||
elif attribute == "hashboards":
|
elif attribute == "hashboards":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f"hashboard_{idx+1}_hashrate={item.hashrate}")
|
|
||||||
field_data.append(f"hashboard_{idx+1}_temperature={item.temp}")
|
|
||||||
field_data.append(
|
field_data.append(
|
||||||
f"hashboard_{idx+1}_chip_temperature={item.chip_temp}"
|
f"hashboard_{idx+1}_hashrate={item.get('hashrate', 0.0)}"
|
||||||
)
|
)
|
||||||
field_data.append(f"hashboard_{idx+1}_chips={item.chips}")
|
|
||||||
field_data.append(
|
field_data.append(
|
||||||
f"hashboard_{idx+1}_expected_chips={item.expected_chips}"
|
f"hashboard_{idx+1}_temperature={item.get('temp', 0)}"
|
||||||
|
)
|
||||||
|
field_data.append(
|
||||||
|
f"hashboard_{idx+1}_chip_temperature={item.get('chip_temp', 0)}"
|
||||||
|
)
|
||||||
|
field_data.append(f"hashboard_{idx+1}_chips={item.get('chips', 0)}")
|
||||||
|
field_data.append(
|
||||||
|
f"hashboard_{idx+1}_expected_chips={item.get('expected_chips', 0)}"
|
||||||
)
|
)
|
||||||
elif attribute == "fans":
|
elif attribute == "fans":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f"fan_{idx+1}={item.speed}")
|
if item.speed is not None:
|
||||||
|
field_data.append(f"fan_{idx+1}={item.speed}")
|
||||||
|
|
||||||
tags_str = ",".join(tag_data)
|
tags_str = ",".join(tag_data)
|
||||||
field_str = ",".join(field_data)
|
field_str = ",".join(field_data)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
from dataclasses import asdict, dataclass, field, fields
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
|
||||||
C_N_CODES = ["52", "53", "54", "55", "56"]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WhatsminerError:
|
class WhatsminerError:
|
||||||
@@ -37,10 +35,8 @@ class WhatsminerError:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
if len(str(self.error_code)) > 3 and str(self.error_code)[:2] in C_N_CODES:
|
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
||||||
# 55 error code base has chip numbers, so the format is
|
err_type = int(str(self.error_code)[:2])
|
||||||
# 55 -> board num len 1 -> chip num len 3
|
|
||||||
err_type = 55
|
|
||||||
err_subtype = int(str(self.error_code)[2:3])
|
err_subtype = int(str(self.error_code)[2:3])
|
||||||
err_value = int(str(self.error_code)[3:])
|
err_value = int(str(self.error_code)[3:])
|
||||||
else:
|
else:
|
||||||
@@ -88,7 +84,9 @@ class WhatsminerError:
|
|||||||
|
|
||||||
ERROR_CODES = {
|
ERROR_CODES = {
|
||||||
1: { # Fan error
|
1: { # Fan error
|
||||||
0: {0: "Fan unknown."},
|
0: {
|
||||||
|
0: "Fan unknown.",
|
||||||
|
},
|
||||||
1: { # Fan speed error of 1000+
|
1: { # Fan speed error of 1000+
|
||||||
0: "Intake fan speed error.",
|
0: "Intake fan speed error.",
|
||||||
1: "Exhaust fan speed error.",
|
1: "Exhaust fan speed error.",
|
||||||
@@ -101,7 +99,9 @@ ERROR_CODES = {
|
|||||||
0: "Intake fan speed error. Fan speed deviates by more than 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.",
|
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||||
},
|
},
|
||||||
4: {0: "Fan speed too high."}, # High speed
|
4: {
|
||||||
|
0: "Fan speed too high.",
|
||||||
|
}, # High speed
|
||||||
},
|
},
|
||||||
2: { # Power error
|
2: { # Power error
|
||||||
0: {
|
0: {
|
||||||
@@ -126,6 +126,7 @@ ERROR_CODES = {
|
|||||||
6: "Power remained unchanged for a long time.",
|
6: "Power remained unchanged for a long time.",
|
||||||
7: "Power set enable error.",
|
7: "Power set enable error.",
|
||||||
8: "Power input voltage is lower than 230V for high power mode.",
|
8: "Power input voltage is lower than 230V for high power mode.",
|
||||||
|
9: "Power input current is incorrect.",
|
||||||
},
|
},
|
||||||
3: {
|
3: {
|
||||||
3: "Power output high temperature protection error.",
|
3: "Power output high temperature protection error.",
|
||||||
@@ -159,6 +160,8 @@ ERROR_CODES = {
|
|||||||
6: {
|
6: {
|
||||||
3: "Power communication warning.",
|
3: "Power communication warning.",
|
||||||
4: "Power communication error.",
|
4: "Power communication error.",
|
||||||
|
5: "Power unknown error.",
|
||||||
|
6: "Power unknown error.",
|
||||||
7: "Power watchdog protection.",
|
7: "Power watchdog protection.",
|
||||||
8: "Power output high current protection.",
|
8: "Power output high current protection.",
|
||||||
9: "Power input high current protection.",
|
9: "Power input high current protection.",
|
||||||
@@ -170,57 +173,134 @@ ERROR_CODES = {
|
|||||||
3: "Power input too high warning.",
|
3: "Power input too high warning.",
|
||||||
4: "Power fan warning.",
|
4: "Power fan warning.",
|
||||||
5: "Power high temperature warning.",
|
5: "Power high temperature warning.",
|
||||||
|
6: "Power unknown error.",
|
||||||
|
7: "Power unknown error.",
|
||||||
|
8: "Power unknown error.",
|
||||||
|
9: "Power unknown error.",
|
||||||
|
},
|
||||||
|
8: {
|
||||||
|
0: "Power unknown error.",
|
||||||
|
1: "Power vendor status 1 bit 0 error.",
|
||||||
|
2: "Power vendor status 1 bit 1 error.",
|
||||||
|
3: "Power vendor status 1 bit 2 error.",
|
||||||
|
4: "Power vendor status 1 bit 3 error.",
|
||||||
|
5: "Power vendor status 1 bit 4 error.",
|
||||||
|
6: "Power vendor status 1 bit 5 error.",
|
||||||
|
7: "Power vendor status 1 bit 6 error.",
|
||||||
|
8: "Power vendor status 1 bit 7 error.",
|
||||||
|
9: "Power vendor status 2 bit 0 error.",
|
||||||
|
},
|
||||||
|
9: {
|
||||||
|
0: "Power vendor status 2 bit 1 error.",
|
||||||
|
1: "Power vendor status 2 bit 2 error.",
|
||||||
|
2: "Power vendor status 2 bit 3 error.",
|
||||||
|
3: "Power vendor status 2 bit 4 error.",
|
||||||
|
4: "Power vendor status 2 bit 5 error.",
|
||||||
|
5: "Power vendor status 2 bit 6 error.",
|
||||||
|
6: "Power vendor status 2 bit 7 error.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
3: { # temperature error
|
3: { # temperature error
|
||||||
0: { # sensor detection error
|
0: { # sensor detection error
|
||||||
"n": "Slot {n} temperature sensor detection error."
|
"n": "Slot {n} temperature sensor detection error.",
|
||||||
},
|
},
|
||||||
2: { # temperature reading error
|
2: { # temperature reading error
|
||||||
"n": "Slot {n} temperature reading error.",
|
"n": "Slot {n} temperature reading error.",
|
||||||
9: "Control board temperature sensor communication error.",
|
9: "Control board temperature sensor communication error.",
|
||||||
},
|
},
|
||||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
5: {
|
||||||
6: {0: "Hashboard high temperature error."}, # high temp
|
"n": "Slot {n} temperature protecting.",
|
||||||
|
}, # temperature protection
|
||||||
|
6: {
|
||||||
|
0: "Hashboard high temperature error.",
|
||||||
|
1: "Hashboard high temperature error.",
|
||||||
|
2: "Hashboard high temperature error.",
|
||||||
|
3: "Hashboard high temperature error.",
|
||||||
|
}, # high temp
|
||||||
|
7: {
|
||||||
|
0: "The environment temperature fluctuates too much.",
|
||||||
|
}, # env temp
|
||||||
8: {
|
8: {
|
||||||
0: "Humidity sensor not found.",
|
0: "Humidity sensor not found.",
|
||||||
1: "Humidity sensor read error.",
|
1: "Humidity sensor read error.",
|
||||||
2: "Humidity sensor read error.",
|
2: "Humidity sensor read error.",
|
||||||
3: "Humidity sensor protecting.",
|
3: "Humidity sensor protecting.",
|
||||||
},
|
}, # humidity
|
||||||
},
|
},
|
||||||
4: { # EEPROM error
|
4: { # EEPROM error
|
||||||
0: {0: "Eeprom unknown error."},
|
0: {
|
||||||
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
0: "Eeprom unknown error.",
|
||||||
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
},
|
||||||
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
1: {
|
||||||
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
"n": "Slot {n} eeprom detection error.",
|
||||||
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer 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
|
5: { # hashboard error
|
||||||
0: {0: "Board unknown error."},
|
0: {
|
||||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
0: "Board unknown error.",
|
||||||
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
},
|
||||||
3: {"n": "Slot {n} not found."}, # board not found error
|
1: {
|
||||||
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
"n": "Slot {n} miner type error.",
|
||||||
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
}, # board miner type error
|
||||||
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
2: {
|
||||||
7: {"n": "Slot {n} xfer error chip."}, # xfer error
|
"n": "Slot {n} bin type error.",
|
||||||
8: {"n": "Slot {n} reset error."}, # reset error
|
}, # chip bin type error
|
||||||
9: {"n": "Slot {n} frequency too low."}, # freq 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
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} xfer error chip.",
|
||||||
|
}, # xfer error
|
||||||
|
8: {
|
||||||
|
"n": "Slot {n} reset error.",
|
||||||
|
}, # reset error
|
||||||
|
9: {
|
||||||
|
"n": "Slot {n} frequency too low.",
|
||||||
|
}, # freq error
|
||||||
},
|
},
|
||||||
6: { # env temp error
|
6: { # env temp error
|
||||||
0: {0: "Environment temperature is too high."}, # normal env temp error
|
0: {
|
||||||
|
0: "Environment temperature is too high.",
|
||||||
|
}, # normal env temp error
|
||||||
1: { # high power env temp error
|
1: { # high power env temp error
|
||||||
0: "Environment temperature is too high for high performance mode."
|
0: "Environment temperature is too high for high performance mode.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
7: { # control board error
|
7: { # control board error
|
||||||
0: {0: "MAC address invalid", 1: "Control board no support chip."},
|
0: {
|
||||||
|
0: "MAC address invalid",
|
||||||
|
1: "Control board no support chip.",
|
||||||
|
},
|
||||||
1: {
|
1: {
|
||||||
0: "Control board rebooted as an exception.",
|
0: "Control board rebooted as an exception.",
|
||||||
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||||
2: "Control board rebooted as an exception.",
|
2: "Control board rebooted as an exception.",
|
||||||
|
3: "The network is unstable, change time.",
|
||||||
|
4: "Unknown error.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"n": "Control board slot {n} frame error.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
8: { # checksum error
|
8: { # checksum error
|
||||||
@@ -228,63 +308,152 @@ ERROR_CODES = {
|
|||||||
0: "CGMiner checksum error.",
|
0: "CGMiner checksum error.",
|
||||||
1: "System monitor checksum error.",
|
1: "System monitor checksum error.",
|
||||||
2: "Remote daemon checksum error.",
|
2: "Remote daemon checksum error.",
|
||||||
}
|
},
|
||||||
|
1: {0: "Air to liquid PCB serial # does not match."},
|
||||||
},
|
},
|
||||||
9: {0: {1: "Power rate error."}}, # power rate error
|
9: {
|
||||||
|
0: {0: "Unknown error.", 1: "Power rate error.", 2: "Unknown error."}
|
||||||
|
}, # power rate error
|
||||||
20: { # pool error
|
20: { # pool error
|
||||||
1: {0: "All pools are disabled."}, # all disabled error
|
0: {
|
||||||
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
0: "No pool information configured.",
|
||||||
3: {0: "High rejection rate on pool."}, # rejection rate 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
|
4: { # asicboost not supported error
|
||||||
0: "The pool does not support asicboost mode."
|
0: "The pool does not support asicboost mode.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
21: {1: {"n": "Slot {n} factory test step failed."}},
|
21: {
|
||||||
|
1: {
|
||||||
|
"n": "Slot {n} factory test step failed.",
|
||||||
|
}
|
||||||
|
},
|
||||||
23: { # hashrate error
|
23: { # hashrate error
|
||||||
1: {0: "Hashrate is too low."},
|
1: {
|
||||||
2: {0: "Hashrate is too low."},
|
0: "Hashrate is too low.",
|
||||||
3: {0: "Hashrate loss is too high."},
|
},
|
||||||
4: {0: "Hashrate loss is too high."},
|
2: {
|
||||||
5: {0: "Hashrate loss."},
|
0: "Hashrate is too low.",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
0: "Hashrate loss is too high.",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
0: "Hashrate loss is too high.",
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
0: "Hashrate loss.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
50: { # water velocity error/voltage error
|
50: { # water velocity error/voltage error
|
||||||
1: {"n": "Slot {n} chip voltage too low."},
|
1: {
|
||||||
2: {"n": "Slot {n} chip voltage changed."},
|
"n": "Slot {n} chip voltage too low.",
|
||||||
3: {"n": "Slot {n} chip temperature difference is too large."},
|
},
|
||||||
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
2: {
|
||||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
"n": "Slot {n} chip voltage changed.",
|
||||||
8: {0: "Chip temp calibration failed, please restore factory settings."},
|
},
|
||||||
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
3: {
|
||||||
|
"n": "Slot {n} chip temperature difference is too large.",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
"n": "Slot {n} chip hottest temperature difference is too large.",
|
||||||
|
},
|
||||||
|
5: {"n": "Slot {n} stopped hashing, chips temperature protecting."},
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} water velocity is abnormal.",
|
||||||
|
}, # abnormal water velocity
|
||||||
|
8: {
|
||||||
|
0: "Chip temp calibration failed, please restore factory settings.",
|
||||||
|
},
|
||||||
|
9: {
|
||||||
|
"n": "Slot {n} chip temp calibration check no balance.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
51: { # frequency error
|
51: { # frequency error
|
||||||
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
1: {
|
||||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
"n": "Slot {n} frequency up timeout.",
|
||||||
|
}, # frequency up timeout
|
||||||
|
2: {"n": "Slot {n} too many CRC errors."},
|
||||||
|
3: {"n": "Slot {n} unstable."},
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} frequency up timeout.",
|
||||||
|
}, # frequency up timeout
|
||||||
|
},
|
||||||
|
52: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} error nonce.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
53: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} too few nonce.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
54: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} temp protected.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
55: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} has been reset.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
56: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} zero nonce.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
52: {"n": {"c": "Slot {n} chip {c} error nonce."}},
|
|
||||||
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
|
||||||
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
|
||||||
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
|
||||||
56: {"n": {"c": "Slot {n} chip {c} does not return to the nonce."}},
|
|
||||||
80: {
|
80: {
|
||||||
0: {0: "The tool version is too low, please update."},
|
0: {
|
||||||
1: {0: "Low freq."},
|
0: "The tool version is too low, please update.",
|
||||||
2: {0: "Low hashrate."},
|
},
|
||||||
3: {5: "High env temp."},
|
1: {
|
||||||
|
0: "Low freq.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
0: "Low hashrate.",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
5: "High env temp.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
81: {
|
81: {
|
||||||
0: {0: "Chip data error."},
|
0: {
|
||||||
|
0: "Chip data error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
82: {
|
82: {
|
||||||
0: {0: "Power version error."},
|
0: {
|
||||||
1: {0: "Miner type error."},
|
0: "Power version error.",
|
||||||
2: {0: "Version info error."},
|
},
|
||||||
|
1: {
|
||||||
|
0: "Miner type error.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
0: "Version info error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
83: {
|
83: {
|
||||||
0: {0: "Empty level error."},
|
0: {
|
||||||
|
0: "Empty level error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
84: {
|
84: {
|
||||||
0: {0: "Old firmware."},
|
0: {
|
||||||
1: {0: "Software version error."},
|
0: "Old firmware.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Software version error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
85: {
|
85: {
|
||||||
"n": {
|
"n": {
|
||||||
@@ -296,8 +465,12 @@ ERROR_CODES = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
86: {
|
86: {
|
||||||
0: {0: "Missing product serial #."},
|
0: {
|
||||||
1: {0: "Missing product type."},
|
0: "Missing product serial #.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Missing product type.",
|
||||||
|
},
|
||||||
2: {
|
2: {
|
||||||
0: "Missing miner serial #.",
|
0: "Missing miner serial #.",
|
||||||
1: "Wrong miner serial # length.",
|
1: "Wrong miner serial # length.",
|
||||||
@@ -314,12 +487,34 @@ ERROR_CODES = {
|
|||||||
3: "Wrong power model rate.",
|
3: "Wrong power model rate.",
|
||||||
4: "Wrong power model format.",
|
4: "Wrong power model format.",
|
||||||
},
|
},
|
||||||
5: {0: "Wrong hash board struct."},
|
5: {
|
||||||
6: {0: "Wrong miner cooling type."},
|
0: "Wrong hash board struct.",
|
||||||
7: {0: "Missing PCB serial #."},
|
},
|
||||||
|
6: {
|
||||||
|
0: "Wrong miner cooling type.",
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
0: "Missing PCB serial #.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
87: {
|
||||||
|
0: {
|
||||||
|
0: "Miner power mismatch.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
90: {
|
||||||
|
0: {
|
||||||
|
0: "Process error, exited with signal: 3.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Process error, exited with signal: 3.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
99: {
|
||||||
|
9: {
|
||||||
|
9: "Miner unknown error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
87: {0: {0: "Miner power mismatch."}},
|
|
||||||
99: {9: {9: "Miner unknown error."}},
|
|
||||||
1000: {
|
1000: {
|
||||||
0: {
|
0: {
|
||||||
0: "Security library error, please upgrade firmware",
|
0: "Security library error, please upgrade firmware",
|
||||||
@@ -328,7 +523,11 @@ ERROR_CODES = {
|
|||||||
3: "/antiv/dig/pf_partial.dig illegal.",
|
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
1001: {0: {0: "Security BTMiner removed, please upgrade firmware."}},
|
1001: {
|
||||||
|
0: {
|
||||||
|
0: "Security BTMiner removed, please upgrade firmware.",
|
||||||
|
},
|
||||||
|
},
|
||||||
1100: {
|
1100: {
|
||||||
0: {
|
0: {
|
||||||
0: "Security illegal file, please upgrade firmware.",
|
0: "Security illegal file, please upgrade firmware.",
|
||||||
|
|||||||
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
|
|||||||
not self.miners[data_point.ip]["shutdown"]
|
not self.miners[data_point.ip]["shutdown"]
|
||||||
):
|
):
|
||||||
# cant do anything with it so need to find a semi-accurate power limit
|
# cant do anything with it so need to find a semi-accurate power limit
|
||||||
if not data_point.wattage_limit == -1:
|
if not data_point.wattage_limit == None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||||
elif not data_point.wattage == -1:
|
elif not data_point.wattage == None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class HiveonT9(Hiveon, T9):
|
|||||||
hashrate = 0
|
hashrate = 0
|
||||||
chips = 0
|
chips = 0
|
||||||
for chipset in board_map[board]:
|
for chipset in board_map[board]:
|
||||||
if hashboard.chip_temp == -1:
|
if hashboard.chip_temp == None:
|
||||||
try:
|
try:
|
||||||
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
|
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
|
||||||
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
|
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
|
||||||
|
|||||||
@@ -26,11 +26,17 @@ from pyasic.miners.backends.cgminer import CGMiner
|
|||||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||||
|
|
||||||
ANTMINER_MODERN_DATA_LOC = {
|
ANTMINER_MODERN_DATA_LOC = {
|
||||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
"mac": {
|
||||||
|
"cmd": "get_mac",
|
||||||
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
|
},
|
||||||
"model": {"cmd": "get_model", "kwargs": {}},
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {
|
||||||
|
"cmd": "get_hostname",
|
||||||
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
|
},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"nominal_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_nominal_hashrate",
|
||||||
@@ -42,8 +48,11 @@ ANTMINER_MODERN_DATA_LOC = {
|
|||||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
"fault_light": {
|
||||||
|
"cmd": "get_fault_light",
|
||||||
|
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
|
||||||
|
},
|
||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {
|
"is_mining": {
|
||||||
"cmd": "is_mining",
|
"cmd": "is_mining",
|
||||||
@@ -121,21 +130,31 @@ class AntminerModern(BMMiner):
|
|||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
async def get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
try:
|
if not web_get_system_info:
|
||||||
data = await self.web.get_system_info()
|
try:
|
||||||
if data:
|
web_get_system_info = await self.web.get_system_info()
|
||||||
return data["hostname"]
|
except APIError:
|
||||||
except KeyError:
|
pass
|
||||||
pass
|
|
||||||
|
|
||||||
async def get_mac(self) -> Union[str, None]:
|
if web_get_system_info:
|
||||||
try:
|
try:
|
||||||
data = await self.web.get_system_info()
|
return web_get_system_info["hostname"]
|
||||||
if data:
|
except KeyError:
|
||||||
return data["macaddr"]
|
pass
|
||||||
except KeyError:
|
|
||||||
pass
|
async def get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
|
if not web_get_system_info:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info:
|
||||||
|
try:
|
||||||
|
return web_get_system_info["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await self.web.get_network_info()
|
data = await self.web.get_network_info()
|
||||||
@@ -144,12 +163,17 @@ class AntminerModern(BMMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
errors = []
|
if not web_summary:
|
||||||
data = await self.web.summary()
|
|
||||||
if data:
|
|
||||||
try:
|
try:
|
||||||
for item in data["SUMMARY"][0]["status"]:
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
for item in web_summary["SUMMARY"][0]["status"]:
|
||||||
try:
|
try:
|
||||||
if not item["status"] == "s":
|
if not item["status"] == "s":
|
||||||
errors.append(X19Error(item["msg"]))
|
errors.append(X19Error(item["msg"]))
|
||||||
@@ -159,15 +183,21 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
try:
|
|
||||||
data = await self.web.get_blink_status()
|
if not web_get_blink_status:
|
||||||
if data:
|
try:
|
||||||
self.light = data["blink"]
|
web_get_blink_status = await self.web.get_blink_status()
|
||||||
except KeyError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if web_get_blink_status:
|
||||||
|
try:
|
||||||
|
self.light = web_get_blink_status["blink"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ class BOSMiner(BaseMiner):
|
|||||||
if graphql_version:
|
if graphql_version:
|
||||||
try:
|
try:
|
||||||
fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"]
|
fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"]
|
||||||
except KeyError:
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not fw_ver:
|
if not fw_ver:
|
||||||
@@ -523,7 +523,7 @@ class BOSMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
hostname = graphql_hostname["data"]["bos"]["hostname"]
|
hostname = graphql_hostname["data"]["bos"]["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
except KeyError:
|
except (TypeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -563,7 +563,7 @@ class BOSMiner(BaseMiner):
|
|||||||
),
|
),
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
except (KeyError, IndexError, ValueError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# get hr from API
|
# get hr from API
|
||||||
@@ -617,7 +617,7 @@ class BOSMiner(BaseMiner):
|
|||||||
boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][
|
boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][
|
||||||
"childSolvers"
|
"childSolvers"
|
||||||
]
|
]
|
||||||
except (KeyError, IndexError):
|
except (TypeError, LookupError):
|
||||||
boards = None
|
boards = None
|
||||||
|
|
||||||
if boards:
|
if boards:
|
||||||
@@ -732,7 +732,7 @@ class BOSMiner(BaseMiner):
|
|||||||
return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
|
return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
|
||||||
"power"
|
"power"
|
||||||
]["approxConsumptionW"]
|
]["approxConsumptionW"]
|
||||||
except (KeyError, TypeError):
|
except (LookupError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not api_tunerstatus:
|
if not api_tunerstatus:
|
||||||
@@ -765,7 +765,7 @@ class BOSMiner(BaseMiner):
|
|||||||
return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][
|
return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][
|
||||||
"power"
|
"power"
|
||||||
]["limitW"]
|
]["limitW"]
|
||||||
except (KeyError, TypeError):
|
except (LookupError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not api_tunerstatus:
|
if not api_tunerstatus:
|
||||||
@@ -801,7 +801,7 @@ class BOSMiner(BaseMiner):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except KeyError:
|
except (LookupError, TypeError):
|
||||||
pass
|
pass
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
@@ -941,7 +941,7 @@ class BOSMiner(BaseMiner):
|
|||||||
boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
|
boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
|
||||||
"childSolvers"
|
"childSolvers"
|
||||||
]
|
]
|
||||||
except (KeyError, IndexError):
|
except (LookupError, TypeError):
|
||||||
boards = None
|
boards = None
|
||||||
|
|
||||||
if boards:
|
if boards:
|
||||||
@@ -1034,17 +1034,20 @@ class BOSMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
self.light = graphql_fault_light["data"]["bos"]["faultLight"]
|
self.light = graphql_fault_light["data"]["bos"]["faultLight"]
|
||||||
return self.light
|
return self.light
|
||||||
except (TypeError, KeyError, ValueError, IndexError):
|
except (TypeError, ValueError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# get light via ssh if that fails (10x slower)
|
# get light via ssh if that fails (10x slower)
|
||||||
data = (
|
try:
|
||||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
data = (
|
||||||
).strip()
|
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||||
self.light = False
|
).strip()
|
||||||
if data == "50":
|
self.light = False
|
||||||
self.light = True
|
if data == "50":
|
||||||
return self.light
|
self.light = True
|
||||||
|
return self.light
|
||||||
|
except TypeError:
|
||||||
|
return self.light
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||||
if not api_devs:
|
if not api_devs:
|
||||||
@@ -1075,7 +1078,9 @@ class BOSMiner(BaseMiner):
|
|||||||
async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||||
if not api_devdetails:
|
if not api_devdetails:
|
||||||
try:
|
try:
|
||||||
api_devdetails = await self.api.send_command("devdetails", ignore_errors=True, allow_warning=False)
|
api_devdetails = await self.api.send_command(
|
||||||
|
"devdetails", ignore_errors=True, allow_warning=False
|
||||||
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -179,11 +179,12 @@ class CGMinerAvalon(CGMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
||||||
if not mac:
|
return None
|
||||||
mac = await self.get_mac()
|
# if not mac:
|
||||||
|
# mac = await self.get_mac()
|
||||||
if mac:
|
#
|
||||||
return f"Avalon{mac.replace(':', '')[-6:]}"
|
# if mac:
|
||||||
|
# return f"Avalon{mac.replace(':', '')[-6:]}"
|
||||||
|
|
||||||
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||||
if not api_devs:
|
if not api_devs:
|
||||||
|
|||||||
@@ -74,6 +74,24 @@ class VNish(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
data = await self.web.stop_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
data = await self.web.resume_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
if data:
|
if data:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
@@ -33,6 +33,8 @@ class BaseMiner(ABC):
|
|||||||
self.api = None
|
self.api = None
|
||||||
self.web = None
|
self.web = None
|
||||||
|
|
||||||
|
self.ssh_pwd = "root"
|
||||||
|
|
||||||
# static data
|
# static data
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.api_type = None
|
self.api_type = None
|
||||||
@@ -89,6 +91,7 @@ class BaseMiner(ABC):
|
|||||||
|
|
||||||
@pwd.setter
|
@pwd.setter
|
||||||
def pwd(self, val):
|
def pwd(self, val):
|
||||||
|
self.ssh_pwd = val
|
||||||
try:
|
try:
|
||||||
if self.web is not None:
|
if self.web is not None:
|
||||||
self.web.pwd = val
|
self.web.pwd = val
|
||||||
@@ -125,7 +128,7 @@ class BaseMiner(ABC):
|
|||||||
str(self.ip),
|
str(self.ip),
|
||||||
known_hosts=None,
|
known_hosts=None,
|
||||||
username="root",
|
username="root",
|
||||||
password="root",
|
password=self.ssh_pwd,
|
||||||
server_host_key_algs=["ssh-rsa"],
|
server_host_key_algs=["ssh-rsa"],
|
||||||
)
|
)
|
||||||
return conn
|
return conn
|
||||||
@@ -410,65 +413,57 @@ class BaseMiner(ABC):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_data(self, allow_warning: bool, data_to_get: list = None) -> dict:
|
async def _get_data(
|
||||||
if not data_to_get:
|
self, allow_warning: bool, include: list = None, exclude: list = None
|
||||||
|
) -> dict:
|
||||||
|
if include is None:
|
||||||
# everything
|
# everything
|
||||||
data_to_get = [
|
include = list(self.data_locations.keys())
|
||||||
"mac",
|
|
||||||
"model",
|
if exclude is not None:
|
||||||
"api_ver",
|
for item in exclude:
|
||||||
"fw_ver",
|
if item in include:
|
||||||
"hostname",
|
include.remove(item)
|
||||||
"hashrate",
|
|
||||||
"nominal_hashrate",
|
api_multicommand = set()
|
||||||
"hashboards",
|
|
||||||
"env_temp",
|
|
||||||
"wattage",
|
|
||||||
"wattage_limit",
|
|
||||||
"fans",
|
|
||||||
"fan_psu",
|
|
||||||
"errors",
|
|
||||||
"fault_light",
|
|
||||||
"pools",
|
|
||||||
"is_mining",
|
|
||||||
"uptime",
|
|
||||||
]
|
|
||||||
api_multicommand = []
|
|
||||||
web_multicommand = []
|
web_multicommand = []
|
||||||
for data_name in data_to_get:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
fn_args = self.data_locations[data_name]["kwargs"]
|
fn_args = self.data_locations[data_name]["kwargs"]
|
||||||
for arg_name in fn_args:
|
for arg_name in fn_args:
|
||||||
if fn_args[arg_name].get("api"):
|
if fn_args[arg_name].get("api"):
|
||||||
api_multicommand.append(fn_args[arg_name]["api"])
|
api_multicommand.add(fn_args[arg_name]["api"])
|
||||||
if fn_args[arg_name].get("web"):
|
if fn_args[arg_name].get("web"):
|
||||||
web_multicommand.append(fn_args[arg_name]["web"])
|
if not fn_args[arg_name]["web"] in web_multicommand:
|
||||||
|
web_multicommand.append(fn_args[arg_name]["web"])
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error(e, data_name)
|
logger.error(e, data_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
api_multicommand = list(set(api_multicommand))
|
|
||||||
_web_multicommand = web_multicommand
|
|
||||||
for item in web_multicommand:
|
|
||||||
if item not in _web_multicommand:
|
|
||||||
_web_multicommand.append(item)
|
|
||||||
web_multicommand = _web_multicommand
|
|
||||||
if len(api_multicommand) > 0:
|
if len(api_multicommand) > 0:
|
||||||
api_command_data = await self.api.multicommand(
|
api_command_task = asyncio.create_task(
|
||||||
*api_multicommand, allow_warning=allow_warning
|
self.api.multicommand(*api_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
api_command_data = {}
|
api_command_task = asyncio.sleep(0)
|
||||||
if len(web_multicommand) > 0:
|
if len(web_multicommand) > 0:
|
||||||
web_command_data = await self.web.multicommand(
|
web_command_task = asyncio.create_task(
|
||||||
*web_multicommand, allow_warning=allow_warning
|
self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
web_command_task = asyncio.sleep(0)
|
||||||
|
|
||||||
|
web_command_data = await web_command_task
|
||||||
|
if web_command_data is None:
|
||||||
web_command_data = {}
|
web_command_data = {}
|
||||||
|
|
||||||
|
api_command_data = await api_command_task
|
||||||
|
if api_command_data is None:
|
||||||
|
api_command_data = {}
|
||||||
|
|
||||||
miner_data = {}
|
miner_data = {}
|
||||||
|
|
||||||
for data_name in data_to_get:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
fn_args = self.data_locations[data_name]["kwargs"]
|
fn_args = self.data_locations[data_name]["kwargs"]
|
||||||
args_to_send = {k: None for k in fn_args}
|
args_to_send = {k: None for k in fn_args}
|
||||||
@@ -492,7 +487,7 @@ class BaseMiner(ABC):
|
|||||||
args_to_send[arg_name] = web_command_data
|
args_to_send[arg_name] = web_command_data
|
||||||
except LookupError:
|
except LookupError:
|
||||||
args_to_send[arg_name] = None
|
args_to_send[arg_name] = None
|
||||||
except LookupError as e:
|
except LookupError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
function = getattr(self, self.data_locations[data_name]["cmd"])
|
function = getattr(self, self.data_locations[data_name]["cmd"])
|
||||||
@@ -522,13 +517,14 @@ class BaseMiner(ABC):
|
|||||||
return miner_data
|
return miner_data
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
self, allow_warning: bool = False, data_to_get: list = None
|
self, allow_warning: bool = False, include: list = None, exclude: list = None
|
||||||
) -> MinerData:
|
) -> MinerData:
|
||||||
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
allow_warning: Allow warning when an API command fails.
|
allow_warning: Allow warning when an API command fails.
|
||||||
data_to_get: Names of data items you want to gather. Defaults to all data.
|
include: Names of data items you want to gather. Defaults to all data.
|
||||||
|
exclude: Names of data items to exclude. Exclusion happens after considering included items.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
|
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
|
||||||
@@ -544,7 +540,9 @@ class BaseMiner(ABC):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
gathered_data = await self._get_data(allow_warning, data_to_get=data_to_get)
|
gathered_data = await self._get_data(
|
||||||
|
allow_warning, include=include, exclude=exclude
|
||||||
|
)
|
||||||
for item in gathered_data:
|
for item in gathered_data:
|
||||||
if gathered_data[item] is not None:
|
if gathered_data[item] is not None:
|
||||||
setattr(data, item, gathered_data[item])
|
setattr(data, item, gathered_data[item])
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
from typing import Callable, List, Optional, Tuple, Union
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import aiohttp
|
import httpx
|
||||||
|
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.antminer import *
|
from pyasic.miners.antminer import *
|
||||||
@@ -319,6 +319,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19J": BOSMinerS19j,
|
"ANTMINER S19J": BOSMinerS19j,
|
||||||
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
||||||
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
||||||
|
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
||||||
"ANTMINER T19": BOSMinerT19,
|
"ANTMINER T19": BOSMinerT19,
|
||||||
},
|
},
|
||||||
MinerTypes.VNISH: {
|
MinerTypes.VNISH: {
|
||||||
@@ -455,7 +456,7 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def _get_miner_web(self, ip: str):
|
async def _get_miner_web(self, ip: str):
|
||||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
||||||
|
|
||||||
text, resp = await concurrent_get_first_result(
|
text, resp = await concurrent_get_first_result(
|
||||||
@@ -466,26 +467,26 @@ class MinerFactory:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _web_ping(
|
async def _web_ping(
|
||||||
session: aiohttp.ClientSession, url: str
|
session: httpx.AsyncClient, url: str
|
||||||
) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]:
|
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
||||||
try:
|
try:
|
||||||
resp = await session.get(url, allow_redirects=False)
|
resp = await session.get(url, follow_redirects=False)
|
||||||
return await resp.text(), resp
|
return resp.text, resp
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_web_type(web_text: str, web_resp: aiohttp.ClientResponse) -> MinerTypes:
|
def _parse_web_type(web_text: str, web_resp: httpx.Response) -> MinerTypes:
|
||||||
if web_resp.status == 401 and 'realm="antMiner' in web_resp.headers.get(
|
if web_resp.status_code == 401 and 'realm="antMiner' in web_resp.headers.get(
|
||||||
"www-authenticate", ""
|
"www-authenticate", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.ANTMINER
|
return MinerTypes.ANTMINER
|
||||||
if web_resp.status == 307 and "https://" in web_resp.headers.get(
|
if web_resp.status_code == 307 and "https://" in web_resp.headers.get(
|
||||||
"location", ""
|
"location", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.WHATSMINER
|
return MinerTypes.WHATSMINER
|
||||||
if "Braiins OS" in web_text or 'href="/cgi-bin/luci"' in web_text:
|
if "Braiins OS" in web_text:
|
||||||
return MinerTypes.BRAIINS_OS
|
return MinerTypes.BRAIINS_OS
|
||||||
if "cloud-box" in web_text:
|
if "cloud-box" in web_text:
|
||||||
return MinerTypes.GOLDSHELL
|
return MinerTypes.GOLDSHELL
|
||||||
@@ -576,26 +577,26 @@ class MinerFactory:
|
|||||||
self,
|
self,
|
||||||
ip: Union[ipaddress.ip_address, str],
|
ip: Union[ipaddress.ip_address, str],
|
||||||
location: str,
|
location: str,
|
||||||
auth: Optional[aiohttp.BasicAuth] = None,
|
auth: Optional[httpx.DigestAuth] = None,
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
try:
|
try:
|
||||||
data = await session.get(
|
data = await session.get(
|
||||||
f"http://{str(ip)}{location}",
|
f"http://{str(ip)}{location}",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
logger.info(f"{ip}: Web command timeout.")
|
logger.info(f"{ip}: Web command timeout.")
|
||||||
return
|
return
|
||||||
if data is None:
|
if data is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
json_data = await data.json()
|
json_data = data.json()
|
||||||
except (aiohttp.ContentTypeError, asyncio.TimeoutError):
|
except (json.JSONDecodeError, asyncio.TimeoutError):
|
||||||
try:
|
try:
|
||||||
return json.loads(await data.text())
|
return json.loads(data.text)
|
||||||
except (json.JSONDecodeError, aiohttp.ClientError):
|
except (json.JSONDecodeError, httpx.HTTPError):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return json_data
|
return json_data
|
||||||
@@ -691,6 +692,28 @@ class MinerFactory:
|
|||||||
return UnknownMiner(str(ip))
|
return UnknownMiner(str(ip))
|
||||||
|
|
||||||
async def get_miner_model_antminer(self, ip: str):
|
async def get_miner_model_antminer(self, ip: str):
|
||||||
|
tasks = [
|
||||||
|
asyncio.create_task(self._get_model_antminer_web(ip)),
|
||||||
|
asyncio.create_task(self._get_model_antminer_sock(ip)),
|
||||||
|
]
|
||||||
|
|
||||||
|
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||||
|
|
||||||
|
async def _get_model_antminer_web(self, ip: str):
|
||||||
|
# last resort, this is slow
|
||||||
|
auth = httpx.DigestAuth("root", "root")
|
||||||
|
web_json_data = await self.send_web_command(
|
||||||
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
|
|
||||||
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_model_antminer_sock(self, ip: str):
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||||
@@ -715,19 +738,6 @@ class MinerFactory:
|
|||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# last resort, this is slow
|
|
||||||
auth = aiohttp.BasicAuth("root", "root")
|
|
||||||
web_json_data = await self.send_web_command(
|
|
||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
miner_model = web_json_data["minertype"]
|
|
||||||
|
|
||||||
return miner_model
|
|
||||||
except (TypeError, LookupError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def get_miner_model_goldshell(self, ip: str):
|
async def get_miner_model_goldshell(self, ip: str):
|
||||||
json_data = await self.send_web_command(ip, "/mcb/status")
|
json_data = await self.send_web_command(ip, "/mcb/status")
|
||||||
|
|
||||||
@@ -760,22 +770,20 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
auth_req = await session.post(
|
auth_req = await session.post(
|
||||||
f"http://{ip}/api/auth",
|
f"http://{ip}/api/auth",
|
||||||
data={"username": "admin", "password": "admin"},
|
data={"username": "admin", "password": "admin"},
|
||||||
)
|
)
|
||||||
auth = (await auth_req.json())["jwt"]
|
auth = auth_req.json()["jwt"]
|
||||||
|
|
||||||
web_data = await (
|
web_data = (await session.post(
|
||||||
await session.post(
|
|
||||||
f"http://{ip}/api/type",
|
f"http://{ip}/api/type",
|
||||||
headers={"Authorization": "Bearer " + auth},
|
headers={"Authorization": "Bearer " + auth},
|
||||||
data={},
|
data={},
|
||||||
)
|
)).json()
|
||||||
).json()
|
|
||||||
return web_data["type"]
|
return web_data["type"]
|
||||||
except (aiohttp.ClientError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
||||||
@@ -790,16 +798,16 @@ class MinerFactory:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
d = await session.post(
|
d = await session.post(
|
||||||
f"http://{ip}/graphql",
|
f"http://{ip}/graphql",
|
||||||
json={"query": "{bosminer {info{modelName}}}"},
|
json={"query": "{bosminer {info{modelName}}}"},
|
||||||
)
|
)
|
||||||
if d.status == 200:
|
if d.status_code == 200:
|
||||||
json_data = await d.json()
|
json_data = d.json()
|
||||||
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (aiohttp.ClientError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
||||||
@@ -813,6 +821,9 @@ class MinerFactory:
|
|||||||
if "(88)" in miner_model:
|
if "(88)" in miner_model:
|
||||||
miner_model = miner_model.replace("(88)", "NOPIC")
|
miner_model = miner_model.replace("(88)", "NOPIC")
|
||||||
|
|
||||||
|
if " AML" in miner_model:
|
||||||
|
miner_model = miner_model.replace(" AML", "")
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -24,8 +24,5 @@ class M29V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M29 V10"
|
self.model = "M29 V10"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 50
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M29V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -87,10 +87,7 @@ class M50VH60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M50 VH60"
|
self.model = "M50 VH60"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 84
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M50 VH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from pyasic.errors import APIWarning
|
|||||||
class BaseWebAPI(ABC):
|
class BaseWebAPI(ABC):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
# ip address of the miner
|
# ip address of the miner
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ip # ipaddress.ip_address(ip)
|
||||||
self.username = "root"
|
self.username = "root"
|
||||||
self.pwd = "root"
|
self.pwd = "root"
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
@@ -56,25 +57,37 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
async def multicommand(
|
async def multicommand(
|
||||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
) -> dict:
|
) -> dict:
|
||||||
data = {k: None for k in commands}
|
|
||||||
data["multicommand"] = True
|
|
||||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for command in commands:
|
tasks = [
|
||||||
try:
|
asyncio.create_task(self._handle_multicommand(client, command))
|
||||||
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
for command in commands
|
||||||
ret = await client.get(url, auth=auth)
|
]
|
||||||
except httpx.HTTPError:
|
all_data = await asyncio.gather(*tasks)
|
||||||
pass
|
|
||||||
else:
|
data = {}
|
||||||
if ret.status_code == 200:
|
for item in all_data:
|
||||||
try:
|
data.update(item)
|
||||||
json_data = ret.json()
|
|
||||||
data[command] = json_data
|
data["multicommand"] = True
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def _handle_multicommand(self, client: httpx.AsyncClient, command: str):
|
||||||
|
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||||
|
ret = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if ret.status_code == 200:
|
||||||
|
try:
|
||||||
|
json_data = ret.json()
|
||||||
|
return {command: json_data}
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return {command: {}}
|
||||||
|
|
||||||
async def get_miner_conf(self) -> dict:
|
async def get_miner_conf(self) -> dict:
|
||||||
return await self.send_command("get_miner_conf")
|
return await self.send_command("get_miner_conf")
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,32 @@ class VNishWebAPI(BaseWebAPI):
|
|||||||
async def reboot(self) -> dict:
|
async def reboot(self) -> dict:
|
||||||
return await self.send_command("system/reboot", post=True)
|
return await self.send_command("system/reboot", post=True)
|
||||||
|
|
||||||
|
async def pause_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/pause", post=True)
|
||||||
|
|
||||||
|
async def resume_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/resume", post=True)
|
||||||
|
|
||||||
|
async def stop_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/stop", post=True)
|
||||||
|
|
||||||
|
async def start_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/start", post=True)
|
||||||
|
|
||||||
async def info(self):
|
async def info(self):
|
||||||
return await self.send_command("info")
|
return await self.send_command("info")
|
||||||
|
|
||||||
async def summary(self):
|
async def summary(self):
|
||||||
return await self.send_command("summary")
|
return await self.send_command("summary")
|
||||||
|
|
||||||
|
async def chips(self):
|
||||||
|
return await self.send_command("chips")
|
||||||
|
|
||||||
|
async def layout(self):
|
||||||
|
return await self.send_command("layout")
|
||||||
|
|
||||||
|
async def status(self):
|
||||||
|
return await self.send_command("status")
|
||||||
|
|
||||||
|
async def settings(self):
|
||||||
|
return await self.send_command("settings")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.36.8"
|
version = "0.38.0"
|
||||||
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
||||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||||
repository = "https://github.com/UpstreamData/pyasic"
|
repository = "https://github.com/UpstreamData/pyasic"
|
||||||
@@ -14,7 +14,6 @@ httpx = "^0.24.0"
|
|||||||
passlib = "^1.7.4"
|
passlib = "^1.7.4"
|
||||||
pyaml = "^23.5.9"
|
pyaml = "^23.5.9"
|
||||||
toml = "^0.10.2"
|
toml = "^0.10.2"
|
||||||
aiohttp = "^3.8.4"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev]
|
[tool.poetry.group.dev]
|
||||||
optional = true
|
optional = true
|
||||||
|
|||||||
Reference in New Issue
Block a user