Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
689b34611e | ||
|
|
d55c3f45ef | ||
|
|
5ac533616f | ||
|
|
96ea5f5d16 | ||
|
|
87526f5efc | ||
|
|
d31bafbc0e | ||
|
|
66bae47bb9 | ||
|
|
7a09b66d4e | ||
|
|
de5932184f | ||
|
|
2cba62e050 | ||
|
|
c7520d98e0 | ||
|
|
92e9f7bc08 | ||
|
|
a65c4ba215 | ||
|
|
4cd0c3357b | ||
|
|
d5cabf8af5 | ||
|
|
3120de757d | ||
|
|
0b69fe591e | ||
|
|
032288d062 | ||
|
|
5f67b987a0 | ||
|
|
5842b3c4aa | ||
|
|
e2e1d2f2fd | ||
|
|
dd205c0f06 | ||
|
|
3b2b586420 |
@@ -1,3 +1,3 @@
|
||||
jinja2<3.1.3
|
||||
jinja2<3.1.4
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
|
||||
@@ -156,6 +156,7 @@ class VOptAlgo(MinerConfigValue):
|
||||
return "VoltageOptimizer"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChipTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="chip_tune")
|
||||
|
||||
@@ -249,6 +250,8 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="hashrate_tuning")
|
||||
hashrate: int = None
|
||||
throttle_limit: int = None
|
||||
throttle_step: int = None
|
||||
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||
|
||||
@classmethod
|
||||
@@ -256,6 +259,10 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
cls_conf = {}
|
||||
if dict_conf.get("hashrate"):
|
||||
cls_conf["hashrate"] = dict_conf["hashrate"]
|
||||
if dict_conf.get("throttle_limit"):
|
||||
cls_conf["throttle_limit"] = dict_conf["throttle_limit"]
|
||||
if dict_conf.get("throttle_step"):
|
||||
cls_conf["throttle_step"] = dict_conf["throttle_step"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
|
||||
|
||||
@@ -292,7 +299,17 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
|
||||
mode = {
|
||||
"ptune": {
|
||||
"algo": self.algo.as_epic(),
|
||||
"target": self.hashrate,
|
||||
}
|
||||
}
|
||||
if self.throttle_limit is not None:
|
||||
mode["ptune"]["min_throttle"] = self.throttle_limit
|
||||
if self.throttle_step is not None:
|
||||
mode["ptune"]["throttle_step"] = self.throttle_step
|
||||
return mode
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
@@ -416,13 +433,19 @@ class MiningModeConfig(MinerConfigOption):
|
||||
algo_info = web_conf["PerpetualTune"]["Algorithm"]
|
||||
if algo_info.get("VoltageOptimizer") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=algo_info["VoltageOptimizer"]["Target"],
|
||||
algo=TunerAlgo.voltage_optimizer,
|
||||
hashrate=algo_info["VoltageOptimizer"].get("Target"),
|
||||
throttle_limit=algo_info["VoltageOptimizer"].get(
|
||||
"Min Throttle Target"
|
||||
),
|
||||
throttle_step=algo_info["VoltageOptimizer"].get(
|
||||
"Throttle Step"
|
||||
),
|
||||
algo=TunerAlgo.voltage_optimizer(),
|
||||
)
|
||||
else:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=algo_info["ChipTune"]["Target"],
|
||||
algo=TunerAlgo.chip_tune,
|
||||
algo=TunerAlgo.chip_tune(),
|
||||
)
|
||||
else:
|
||||
return cls.normal()
|
||||
|
||||
@@ -49,7 +49,9 @@ class TemperatureConfig(MinerConfigValue):
|
||||
else:
|
||||
temps_config["fans"]["Auto"]["Target Temperature"] = 60
|
||||
if self.danger is not None:
|
||||
temps_config["temps"]["shutdown"] = self.danger
|
||||
temps_config["temps"]["critical"] = self.danger
|
||||
if self.hot is not None:
|
||||
temps_config["temps"]["shutdown"] = self.hot
|
||||
return temps_config
|
||||
|
||||
@classmethod
|
||||
@@ -74,16 +76,20 @@ class TemperatureConfig(MinerConfigValue):
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||
dangerous_temp = web_conf["Misc"]["Critical Temp"]
|
||||
except KeyError:
|
||||
dangerous_temp = None
|
||||
try:
|
||||
hot_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||
except KeyError:
|
||||
hot_temp = None
|
||||
# Need to do this in two blocks to avoid KeyError if one is missing
|
||||
try:
|
||||
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
|
||||
except KeyError:
|
||||
target_temp = None
|
||||
|
||||
return cls(target=target_temp, danger=dangerous_temp)
|
||||
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
|
||||
|
||||
@@ -50,6 +50,7 @@ class MinerData:
|
||||
temperature_avg: The average temperature across the boards. Calculated automatically.
|
||||
env_temp: The environment temps as a float.
|
||||
wattage: Current power draw of the miner as an int.
|
||||
voltage: Current output voltage of the PSU as an float.
|
||||
wattage_limit: Power limit of the miner as an int.
|
||||
fans: A list of fans on the miner with their speeds.
|
||||
fan_psu: The speed of the PSU on the fan if the miner collects it.
|
||||
@@ -84,6 +85,7 @@ class MinerData:
|
||||
env_temp: float = None
|
||||
wattage: int = None
|
||||
wattage_limit: int = field(init=False)
|
||||
voltage: float = None
|
||||
_wattage_limit: int = field(repr=False, default=None)
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_psu: int = None
|
||||
|
||||
@@ -58,6 +58,10 @@ EPIC_DATA_LOC = DataLocations(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.VOLTAGE): DataFunction(
|
||||
"_get_voltage",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
@@ -119,6 +123,7 @@ class ePIC(BaseMiner):
|
||||
# Temps
|
||||
if not conf.get("temps", {}) == {}:
|
||||
await self.web.set_shutdown_temp(conf["temps"]["shutdown"])
|
||||
await self.web.set_critical_temp(conf["temps"]["critical"])
|
||||
# Fans
|
||||
# set with sub-keys instead of conf["fans"] because sometimes both can be set
|
||||
if not conf["fans"].get("Manual", {}) == {}:
|
||||
@@ -129,7 +134,7 @@ class ePIC(BaseMiner):
|
||||
# Mining Mode -- Need to handle that you may not be able to change while miner is tuning
|
||||
if conf["ptune"].get("enabled", True):
|
||||
await self.web.set_ptune_enable(True)
|
||||
await self.web.set_ptune_algo(**conf["ptune"])
|
||||
await self.web.set_ptune_algo(conf["ptune"])
|
||||
|
||||
## Pools
|
||||
await self.web.set_pools(conf["pools"])
|
||||
@@ -216,6 +221,20 @@ class ePIC(BaseMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_voltage(self, web_summary: dict = None) -> Optional[float]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
voltage = web_summary["Power Supply Stats"]["Output Voltage"]
|
||||
return voltage
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
@@ -306,16 +325,27 @@ class ePIC(BaseMiner):
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
if web_summary.get("HBs") is not None:
|
||||
for hb in web_summary["HBs"]:
|
||||
num_of_chips = web_capabilities["Performance Estimator"]["Chip Count"]
|
||||
hashrate = hb["Hashrate"][0]
|
||||
# Update the Hashboard object
|
||||
hb_list[hb["Index"]].missing = False
|
||||
hb_list[hb["Index"]].hashrate = round(hashrate / 1000000, 2)
|
||||
hb_list[hb["Index"]].chips = num_of_chips
|
||||
hb_list[hb["Index"]].temp = hb["Temperature"]
|
||||
return hb_list
|
||||
if web_summary is not None and web_capabilities is not None:
|
||||
if web_summary.get("HBs") is not None:
|
||||
for hb in web_summary["HBs"]:
|
||||
if web_capabilities.get("Performance Estimator") is not None:
|
||||
num_of_chips = web_capabilities["Performance Estimator"][
|
||||
"Chip Count"
|
||||
]
|
||||
else:
|
||||
num_of_chips = self.expected_chips
|
||||
if web_capabilities.get("Board Serial Numbers") is not None:
|
||||
if hb["Index"] < len(web_capabilities["Board Serial Numbers"]):
|
||||
hb_list[hb["Index"]].serial_number = web_capabilities[
|
||||
"Board Serial Numbers"
|
||||
][hb["Index"]]
|
||||
hashrate = hb["Hashrate"][0]
|
||||
# Update the Hashboard object
|
||||
hb_list[hb["Index"]].missing = False
|
||||
hb_list[hb["Index"]].hashrate = round(hashrate / 1000000, 2)
|
||||
hb_list[hb["Index"]].chips = num_of_chips
|
||||
hb_list[hb["Index"]].temp = hb["Temperature"]
|
||||
return hb_list
|
||||
|
||||
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
|
||||
if web_summary is None:
|
||||
|
||||
@@ -249,6 +249,14 @@ class MinerProtocol(Protocol):
|
||||
"""
|
||||
return await self._get_wattage()
|
||||
|
||||
async def get_voltage(self) -> Optional[float]:
|
||||
"""Get output voltage of the PSU as a float.
|
||||
|
||||
Returns:
|
||||
Output voltage of the PSU as an float.
|
||||
"""
|
||||
return await self._get_voltage()
|
||||
|
||||
async def get_wattage_limit(self) -> Optional[int]:
|
||||
"""Get wattage limit from the miner as an int.
|
||||
|
||||
@@ -337,6 +345,9 @@ class MinerProtocol(Protocol):
|
||||
async def _get_wattage(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
async def _get_voltage(self) -> Optional[float]:
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class DataOptions(Enum):
|
||||
IS_MINING = "is_mining"
|
||||
UPTIME = "uptime"
|
||||
CONFIG = "config"
|
||||
VOLTAGE = "voltage"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
@@ -364,6 +364,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19J PRO PLUS": BOSMinerS19jProPlus,
|
||||
"ANTMINER S19J PRO PLUS NOPIC": BOSMinerS19jProPlusNoPIC,
|
||||
"ANTMINER S19K PRO NOPIC": BOSMinerS19kProNoPIC,
|
||||
"ANTMINER S19K PRO": BOSMinerS19kProNoPIC,
|
||||
"ANTMINER S19 XP": BOSMinerS19XP,
|
||||
"ANTMINER T19": BOSMinerT19,
|
||||
"ANTMINER S21": BOSMinerS21,
|
||||
|
||||
@@ -32,6 +32,10 @@ class MinerNetwork:
|
||||
|
||||
def __init__(self, hosts: List[ipaddress.IPv4Address]):
|
||||
self.hosts = hosts
|
||||
semaphore_limit = settings.get("network_scan_semaphore", 255)
|
||||
if semaphore_limit is None:
|
||||
semaphore_limit = 255
|
||||
self.semaphore = asyncio.Semaphore(semaphore_limit)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.hosts)
|
||||
@@ -153,8 +157,16 @@ class MinerNetwork:
|
||||
except TimeoutError:
|
||||
yield None
|
||||
|
||||
async def ping_and_get_miner(
|
||||
self, ip: ipaddress.ip_address
|
||||
) -> Union[None, AnyMiner]:
|
||||
if settings.get("network_scan_semaphore") is None:
|
||||
return await self._ping_and_get_miner(ip)
|
||||
async with self.semaphore:
|
||||
return await self._ping_and_get_miner(ip)
|
||||
|
||||
@staticmethod
|
||||
async def ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
|
||||
async def _ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
|
||||
try:
|
||||
return await ping_and_get_miner(ip)
|
||||
except ConnectionRefusedError:
|
||||
|
||||
@@ -24,6 +24,7 @@ from httpx import AsyncHTTPTransport
|
||||
_settings = { # defaults
|
||||
"network_ping_retries": 1,
|
||||
"network_ping_timeout": 3,
|
||||
"network_scan_semaphore": None,
|
||||
"factory_get_retries": 1,
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
|
||||
@@ -97,6 +97,9 @@ class ePICWebAPI(BaseWebAPI):
|
||||
async def set_shutdown_temp(self, params: int) -> dict:
|
||||
return await self.send_command("shutdowntemp", param=params)
|
||||
|
||||
async def set_critical_temp(self, params: int) -> dict:
|
||||
return await self.send_command("criticaltemp", param=params)
|
||||
|
||||
async def set_fan(self, params: dict) -> dict:
|
||||
return await self.send_command("fanspeed", param=params)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.55.2"
|
||||
version = "0.56.0"
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
|
||||
Reference in New Issue
Block a user