Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bafa91cc47 | ||
|
|
16399510fa | ||
|
|
e9f54eec0a | ||
|
|
fbbbc9f215 | ||
|
|
69e4f575c0 | ||
|
|
e95659d2e0 | ||
|
|
35f34310ec | ||
|
|
acc18e20fd | ||
|
|
72460eac08 | ||
|
|
3e5f9d4eca | ||
|
|
e873fa252c | ||
|
|
ff2c083a19 | ||
|
|
a30a84c34b | ||
|
|
97d2023298 |
@@ -117,10 +117,11 @@ These functions are
|
||||
[`get_hostname`](#get-hostname),
|
||||
[`get_model`](#get-model),
|
||||
[`reboot`](#reboot),
|
||||
[`restart_backend`](#restart-backend), and
|
||||
[`stop_mining`](#stop-mining), and
|
||||
[`resume_mining`](#resume-mining), and
|
||||
[`send_config`](#send-config).
|
||||
[`restart_backend`](#restart-backend),
|
||||
[`stop_mining`](#stop-mining),
|
||||
[`resume_mining`](#resume-mining),
|
||||
[`send_config`](#send-config), and
|
||||
[`set_power_limit`](#set-power-limit).
|
||||
|
||||
<br>
|
||||
|
||||
@@ -228,6 +229,14 @@ These functions are
|
||||
|
||||
<br>
|
||||
|
||||
### Set Power Limit
|
||||
::: pyasic.miners.BaseMiner.set_power_limit
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
## [`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. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
|
||||
|
||||
@@ -90,6 +90,7 @@ details {
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
|
||||
<summary><a href="../whatsminer/M3X/#m31sv70">M31SV70</a></summary>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m31s_1">M31S+</a></summary>
|
||||
|
||||
@@ -108,6 +108,14 @@
|
||||
|
||||
## M31S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31SV70
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
|
||||
handler: python
|
||||
options:
|
||||
|
||||
@@ -581,7 +581,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
return await self.send_privileged_command("set_hostname", hostname=hostname)
|
||||
|
||||
async def set_power_pct(self, percent: int) -> dict:
|
||||
"""Set the power percentage of the miner.
|
||||
"""Set the power percentage of the miner based on current power. Used for temporary adjustment.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
@@ -641,6 +641,119 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
"pre_power_on", complete="false", msg=msg
|
||||
)
|
||||
|
||||
### ADDED IN V2.0.5 Whatsminer API ###
|
||||
async def set_temp_offset(self, temp_offset: int):
|
||||
"""Set the offset of miner hash board target temperature.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the offset of miner hash board target temperature, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
temp_offset: Target temperature offset.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting temp offset.
|
||||
</details>
|
||||
|
||||
"""
|
||||
if not -30 < temp_offset < 0:
|
||||
raise APIError(
|
||||
f"Temp offset is outside of the allowed "
|
||||
f"range. Please set a number between -30 and "
|
||||
f"0."
|
||||
)
|
||||
|
||||
return await self.send_privileged_command("set_temp_offset", temp_offset=temp_offset)
|
||||
|
||||
async def adjust_power_limit(self, power_limit: int):
|
||||
"""Set the upper limit of the miner's power. Cannot be higher than the ordinary power of the machine.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the upper limit of the miner's power, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
The miner will reboot after this is set.
|
||||
|
||||
Parameters:
|
||||
power_limit: New power limit.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting power limit.
|
||||
</details>
|
||||
|
||||
"""
|
||||
return await self.send_privileged_command("adjust_power_limit", power_limit=power_limit)
|
||||
|
||||
|
||||
async def adjust_upfreq_speed(self, upfreq_speed: int):
|
||||
"""Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
The faster the speed, the greater the final hash rate and power deviation, and the stability
|
||||
may be impacted. Fast boot mode cannot be used at the same time.
|
||||
|
||||
Parameters:
|
||||
upfreq_speed: New upfreq speed.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting upfreq speed.
|
||||
</details>
|
||||
"""
|
||||
if not 0 < upfreq_speed < 9:
|
||||
raise APIError(
|
||||
f"Upfreq speed is outside of the allowed "
|
||||
f"range. Please set a number between 0 (Normal) and "
|
||||
f"9 (Fastest)."
|
||||
)
|
||||
return await self.send_privileged_command("adjust_upfreq_speed", upfreq_speed=upfreq_speed)
|
||||
|
||||
async def set_poweroff_cool(self, poweroff_cool: bool):
|
||||
"""Set whether to cool the machine when mining is stopped.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set whether to cool the machine when mining is stopped, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
poweroff_cool: Whether to cool the miner during power off mode.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting power off cooling mode.
|
||||
</details>
|
||||
"""
|
||||
|
||||
return await self.send_privileged_command("set_poweroff_cool", poweroff_cool=int(poweroff_cool))
|
||||
|
||||
async def set_fan_zero_speed(self, fan_zero_speed: bool):
|
||||
"""Sets whether the fan speed supports the lowest 0 speed.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Sets whether the fan speed supports the lowest 0 speed, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
fan_zero_speed: Whether the fan is allowed to support 0 speed.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting fan minimum speed.
|
||||
</details>
|
||||
|
||||
"""
|
||||
return await self.send_privileged_command("set_fan_zero_speed", fan_zero_speed=int(fan_zero_speed))
|
||||
|
||||
|
||||
#### END privileged COMMANDS ####
|
||||
|
||||
async def summary(self) -> dict:
|
||||
|
||||
@@ -17,7 +17,7 @@ import random
|
||||
import string
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from typing import List, Literal
|
||||
from typing import List, Literal, Dict
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
@@ -193,7 +193,7 @@ class _PoolGroup:
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a list usable by an Whatsminer device.
|
||||
"""Convert the data in this class to a list usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
@@ -399,13 +399,13 @@ class MinerConfig:
|
||||
"""
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a config usable by an Whatsminer device.
|
||||
def as_wm(self, user_suffix: str = None) -> Dict[str, int]:
|
||||
"""Convert the data in this class to a config usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
return self.pool_groups[0].as_wm(user_suffix=user_suffix)
|
||||
return {"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix), "wattage": self.autotuning_wattage}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an Innosilicon device.
|
||||
|
||||
@@ -349,3 +349,7 @@ class BMMiner(BaseMiner):
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
@@ -548,6 +548,8 @@ class BOSMiner(BaseMiner):
|
||||
if not query_data:
|
||||
return None
|
||||
query_data = query_data["data"]
|
||||
if not query_data:
|
||||
return None
|
||||
|
||||
data.mac = await self.get_mac()
|
||||
data.model = await self.get_model()
|
||||
@@ -669,3 +671,14 @@ class BOSMiner(BaseMiner):
|
||||
async def get_mac(self):
|
||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
return result.upper().strip()
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
cfg = await self.get_config()
|
||||
cfg.autotuning_wattage = wattage
|
||||
await self.send_config(cfg)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} set_power_limit: {e}")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -106,3 +106,6 @@ class BOSMinerOld(BaseMiner):
|
||||
|
||||
async def get_data(self, **kwargs) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
@@ -211,18 +211,24 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
conf = config.as_wm(user_suffix=user_suffix)
|
||||
pools_conf = conf["pools"]
|
||||
|
||||
await self.api.update_pools(
|
||||
conf[0]["url"],
|
||||
conf[0]["user"],
|
||||
conf[0]["pass"],
|
||||
conf[1]["url"],
|
||||
conf[1]["user"],
|
||||
conf[1]["pass"],
|
||||
conf[2]["url"],
|
||||
conf[2]["user"],
|
||||
conf[2]["pass"],
|
||||
pools_conf[0]["url"],
|
||||
pools_conf[0]["user"],
|
||||
pools_conf[0]["pass"],
|
||||
pools_conf[1]["url"],
|
||||
pools_conf[1]["user"],
|
||||
pools_conf[1]["pass"],
|
||||
pools_conf[2]["url"],
|
||||
pools_conf[2]["user"],
|
||||
pools_conf[2]["pass"],
|
||||
)
|
||||
try:
|
||||
await self.api.adjust_power_limit(conf["wattage"])
|
||||
except APIError:
|
||||
# cannot set wattage
|
||||
pass
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
pools = None
|
||||
@@ -432,3 +438,13 @@ class BTMiner(BaseMiner):
|
||||
data.mac = mac
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
await self.api.adjust_power_limit(wattage)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} set_power_limit: {e}")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -335,3 +335,6 @@ class CGMiner(BaseMiner):
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
@@ -61,3 +61,6 @@ class Hiveon(BMMiner):
|
||||
bad_boards[board] = []
|
||||
bad_boards[board].append(chain)
|
||||
return bad_boards
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
@@ -20,5 +20,14 @@ class M31S(BaseMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S"
|
||||
# TODO: Add chip count for this miner (per board) - self.nominal_chips
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M31SV70(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S V70"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -20,7 +20,7 @@ from .M30S_Plus_Plus import (
|
||||
M30SPlusPlusVG40,
|
||||
M30SPlusPlusVH60,
|
||||
)
|
||||
from .M31S import M31S
|
||||
from .M31S import M31S, M31SV70
|
||||
from .M31S_Plus import (
|
||||
M31SPlus,
|
||||
M31SPlusV30,
|
||||
|
||||
@@ -216,4 +216,12 @@ class BaseMiner(ABC):
|
||||
pass
|
||||
|
||||
|
||||
@abstractmethod
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
"""Set the power limit to be used by the miner.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of setting the power limit.
|
||||
"""
|
||||
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
|
||||
@@ -190,10 +190,12 @@ MINER_CLASSES = {
|
||||
"BTMiner": BTMinerM30SPlusPlus,
|
||||
"G40": BTMinerM30SPlusPlusVG40,
|
||||
"G30": BTMinerM30SPlusPlusVG30,
|
||||
"H60": BTMinerM30SPlusPlusVH60,
|
||||
},
|
||||
"M31S": {
|
||||
"Default": BTMinerM31S,
|
||||
"BTMiner": BTMinerM31S,
|
||||
"V70": BTMinerM31SV70,
|
||||
},
|
||||
"M31S+": {
|
||||
"Default": BTMinerM31SPlus,
|
||||
|
||||
@@ -74,3 +74,6 @@ class UnknownMiner(BaseMiner):
|
||||
|
||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
@@ -13,10 +13,16 @@
|
||||
# limitations under the License.
|
||||
|
||||
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import M31S # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import M31S, M31SV70 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31S(BTMiner, M31S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
|
||||
class BTMinerM31SV70(BTMiner, M31SV70):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
@@ -31,7 +31,7 @@ from .M30S_Plus_Plus import (
|
||||
BTMinerM30SPlusPlusVG40,
|
||||
BTMinerM30SPlusPlusVH60,
|
||||
)
|
||||
from .M31S import BTMinerM31S
|
||||
from .M31S import BTMinerM31S, BTMinerM31SV70
|
||||
from .M31S_Plus import (
|
||||
BTMinerM31SPlus,
|
||||
BTMinerM31SPlusV30,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.21.16"
|
||||
version = "0.23.0"
|
||||
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
|
||||
Reference in New Issue
Block a user