Compare commits

...

14 Commits

Author SHA1 Message Date
Upstream Data
ea55fed8d1 version: bump version number. 2023-02-18 09:45:59 -07:00
Upstream Data
f5fd539eba bug: fix weird configuration format with BOS+ machines. 2023-02-18 09:45:38 -07:00
Upstream Data
51a56f441c version: bump version number. 2023-02-18 09:34:29 -07:00
Upstream Data
54310b1d79 bug: fix bad capitalization on S99Bosminer. 2023-02-18 09:34:03 -07:00
Upstream Data
cb30b761dc version: bump version number. 2023-02-16 18:36:38 -07:00
Upstream Data
cb50a7cac8 feature: add support for configuring BOS+ BBB, and add support for new BOS+ config version 2.0. 2023-02-16 18:36:03 -07:00
UpstreamData
738af245cb version: bump version number. 2023-02-16 13:35:45 -07:00
UpstreamData
71e9af1b91 format: improve warning locations to remove warnings when connections are refused. 2023-02-16 13:35:20 -07:00
UpstreamData
1cd2566d0a version: bump version number. 2023-02-16 12:23:29 -07:00
UpstreamData
1f1e5f23a2 bug: fix a bug where not all errors could be handled when scanning. 2023-02-16 12:22:58 -07:00
UpstreamData
3394234e4f version: bump version number. 2023-02-16 08:58:17 -07:00
UpstreamData
e9882124ff formatting: removed print statements. 2023-02-16 08:57:51 -07:00
UpstreamData
1e5b19c149 version: bump version number. 2023-02-16 08:47:15 -07:00
UpstreamData
c9339fec2f bug: fix issues with new versions of braiins OS, and fix bugs with innosilicon miners not returning much data at all. 2023-02-16 08:46:32 -07:00
5 changed files with 131 additions and 62 deletions

View File

@@ -245,7 +245,9 @@ class MinerConfig:
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
asicboost: Whether or not to enable asicboost.
autotuning_enabled: Whether or not to enable autotuning.
autotuning_mode: Autotuning mode, either "wattage" or "hashrate".
autotuning_wattage: The wattage to use when autotuning.
autotuning_hashrate: The hashrate to use when autotuning.
dps_enabled: Whether or not to enable dynamic power scaling.
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
dps_min_power: The minimum power to reduce autotuning to.
@@ -266,7 +268,9 @@ class MinerConfig:
asicboost: bool = None
autotuning_enabled: bool = True
autotuning_wattage: int = 900
autotuning_mode: Literal["power", "hashrate"] = None
autotuning_wattage: int = None
autotuning_hashrate: int = None
dps_enabled: bool = None
dps_power_step: int = None
@@ -349,14 +353,20 @@ class MinerConfig:
self.autotuning_enabled = data[key][_key]
elif _key == "psu_power_limit":
self.autotuning_wattage = data[key][_key]
elif _key == "power_target":
self.autotuning_wattage = data[key][_key]
elif _key == "hashrate_target":
self.autotuning_hashrate = data[key][_key]
elif _key == "mode":
self.autotuning_mode = data[key][_key].replace("_target", "")
if key == "power_scaling":
if key in ["power_scaling", "performance_scaling"]:
for _key in data[key].keys():
if _key == "enabled":
self.dps_enabled = data[key][_key]
elif _key == "power_step":
self.dps_power_step = data[key][_key]
elif _key == "min_psu_power_limit":
elif _key in ["min_psu_power_limit", "min_power_target"]:
self.dps_min_power = data[key][_key]
elif _key == "shutdown_enabled":
self.dps_shutdown_enabled = data[key][_key]
@@ -481,8 +491,8 @@ class MinerConfig:
cfg = {
"format": {
"version": "1.2+",
"model": f"Antminer {model}",
"generator": "Upstream Config Utility",
"model": f"Antminer {model.replace('j', 'J')}",
"generator": "pyasic",
"timestamp": int(time.time()),
},
"group": [
@@ -499,9 +509,19 @@ class MinerConfig:
if self.autotuning_enabled or self.autotuning_wattage:
cfg["autotuning"] = {}
if self.autotuning_enabled:
cfg["autotuning"]["enabled"] = self.autotuning_enabled
if self.autotuning_wattage:
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
cfg["autotuning"]["enabled"] = True
else:
cfg["autotuning"]["enabled"] = False
if self.autotuning_mode:
cfg["format"]["version"] = "2.0"
cfg["autotuning"]["mode"] = self.autotuning_mode + "_target"
if self.autotuning_wattage:
cfg["autotuning"]["power_target"] = self.autotuning_wattage
elif self.autotuning_hashrate:
cfg["autotuning"]["hashrate_target"] = self.autotuning_hashrate
else:
if self.autotuning_wattage:
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
if self.asicboost:
cfg["hash_chain_global"] = {}
@@ -525,7 +545,10 @@ class MinerConfig:
if self.dps_power_step:
cfg["power_scaling"]["power_step"] = self.dps_power_step
if self.dps_min_power:
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
if cfg["format"]["version"] == "2.0":
cfg["power_scaling"]["min_power_target"] = self.dps_min_power
else:
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
if self.dps_shutdown_enabled:
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
if self.dps_shutdown_duration:

View File

@@ -198,11 +198,8 @@ class BOSMiner(BaseMiner):
return self.config
if conn:
async with conn:
logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp:
logging.debug(f"{self}: Reading config file.")
async with sftp.open("/etc/bosminer.toml") as file:
toml_data = toml.loads(await file.read())
# good ol' BBB compatibility :/
toml_data = toml.loads((await conn.run("cat /etc/bosminer.toml")).stdout)
logging.debug(f"{self}: Converting config file.")
cfg = MinerConfig().from_raw(toml_data)
self.config = cfg
@@ -219,14 +216,28 @@ class BOSMiner(BaseMiner):
except (asyncssh.Error, OSError):
return None
async with conn:
await conn.run("/etc/init.d/bosminer stop")
logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp:
logging.debug(f"{self}: Opening config file.")
async with sftp.open("/etc/bosminer.toml", "w+") as file:
await file.write(toml_conf)
logging.debug(f"{self}: Restarting BOSMiner")
await conn.run("/etc/init.d/bosminer start")
# BBB check because bitmain suxx
bbb_check = await conn.run("if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;")
bbb = bbb_check.stdout.strip() == "1"
if not bbb:
await conn.run("/etc/init.d/bosminer stop")
logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp:
logging.debug(f"{self}: Opening config file.")
async with sftp.open("/etc/bosminer.toml", "w+") as file:
await file.write(toml_conf)
logging.debug(f"{self}: Restarting BOSMiner")
await conn.run("/etc/init.d/bosminer start")
# I really hate BBB, please get rid of it if you have it
else:
await conn.run("/etc/init.d/S99bosminer stop")
logging.debug(f"{self}: BBB sending config")
await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml")
logging.debug(f"{self}: BBB restarting bosminer.")
await conn.run("/etc/init.d/S99bosminer start")
async def set_power_limit(self, wattage: int) -> bool:
try:
@@ -483,10 +494,9 @@ class BOSMiner(BaseMiner):
api_devs = d["devs"][0]
except (KeyError, IndexError):
api_devs = None
if api_temps:
try:
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 0
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
for board in api_temps["TEMPS"]:
_id = board["ID"] - offset
@@ -499,7 +509,7 @@ class BOSMiner(BaseMiner):
if api_devdetails:
try:
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 0
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devdetails["DEVDETAILS"]:
_id = board["ID"] - offset
@@ -511,7 +521,7 @@ class BOSMiner(BaseMiner):
if api_devs:
try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devs["DEVS"]:
_id = board["ID"] - offset

View File

@@ -136,10 +136,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
async def get_mac(
self,
web_getAll: dict = None,
web_getAll: dict = None, # noqa
web_overview: dict = None, # noqa: named this way for automatic functionality
) -> Optional[str]:
web_all_data = web_getAll
web_all_data = web_getAll.get("all")
if not web_all_data and not web_overview:
try:
web_overview = await self.send_web_command("overview")
@@ -183,7 +183,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
api_summary: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> Optional[float]:
web_all_data = web_getAll
web_all_data = web_getAll.get("all")
if not api_summary and not web_all_data:
try:
api_summary = await self.api.summary()
@@ -209,7 +209,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
api_stats: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[HashBoard]:
web_all_data = web_getAll
web_all_data = web_getAll.get("all")
hashboards = [
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
@@ -231,8 +231,9 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if api_stats:
if api_stats.get("STATS"):
for idx, board in enumerate(api_stats["STATS"]):
for board in api_stats["STATS"]:
try:
idx = board["Chain ID"]
chips = board["Num active chips"]
except KeyError:
pass
@@ -242,25 +243,31 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if web_all_data:
if web_all_data.get("chain"):
for idx, board in enumerate(web_all_data["chain"]):
temp = board.get("Temp min")
if temp:
hashboards[idx].temp = round(temp)
for board in web_all_data["chain"]:
idx = board.get("ASC")
if idx is not None:
temp = board.get("Temp min")
if temp:
hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
hashboards[idx].hashrate = round(hashrate / 1000000000000, 2)
hashrate = board.get("Hash Rate H")
if hashrate:
hashboards[idx].hashrate = round(
hashrate / 1000000000000, 2
)
chip_temp = board.get("Temp max")
if chip_temp:
hashboards[idx].chip_temp = round(chip_temp)
chip_temp = board.get("Temp max")
if chip_temp:
hashboards[idx].chip_temp = round(chip_temp)
return hashboards
async def get_wattage(
self, web_getAll: dict = None
) -> Optional[int]: # noqa: named this way for automatic functionality
web_all_data = web_getAll
self,
web_getAll: dict = None,
api_stats: dict = None, # noqa: named this way for automatic functionality
) -> Optional[int]:
web_all_data = web_getAll.get("all")
if not web_all_data:
try:
web_all_data = await self.send_web_command("getAll")
@@ -275,11 +282,28 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
except KeyError:
pass
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
if api_stats.get("STATS"):
for board in api_stats["STATS"]:
try:
wattage = board["power"]
except KeyError:
pass
else:
wattage = int(wattage)
return wattage
async def get_fans(
self,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[Fan]:
web_all_data = web_getAll
web_all_data = web_getAll.get("all")
if not web_all_data:
try:
web_all_data = await self.send_web_command("getAll")
@@ -350,3 +374,24 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def get_wattage_limit(self, web_getAll: dict = None) -> Optional[int]:
web_all_data = web_getAll.get("all")
if not web_all_data:
try:
web_all_data = await self.send_web_command("getAll")
except APIError:
pass
else:
web_all_data = web_all_data["all"]
if web_all_data:
try:
level = web_all_data["running_mode"]["level"]
except KeyError:
pass
else:
# this is very possibly not correct.
level = int(level)
limit = 1250 + (250 * level)
return limit

View File

@@ -108,10 +108,6 @@ class MinerNetwork:
# clear cached miners
MinerFactory().clear_cached_miners()
# create a list of tasks and miner IPs
scan_tasks = []
miners = []
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
miners = await asyncio.gather(
*[self.ping_and_get_miner(host, limit) for host in local_network.hosts()]
@@ -140,8 +136,6 @@ class MinerNetwork:
local_network = self.get_network()
# create a list of scan tasks
scan_tasks = []
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
miners = asyncio.as_completed(
[
@@ -213,14 +207,13 @@ async def ping_miner(
except asyncio.exceptions.TimeoutError:
# ping failed if we time out
continue
except ConnectionRefusedError:
except (ConnectionRefusedError, OSError):
# handle for other connection errors
logging.debug(f"{str(ip)}: Connection Refused.")
raise ConnectionRefusedError
# ping failed, likely with an exception
except Exception as e:
logging.warning(f"{str(ip)}: {e}")
continue
logging.warning(f"{str(ip)}: Ping And Get Miner Exception: {e}")
raise ConnectionRefusedError
return
@@ -228,8 +221,8 @@ async def ping_and_get_miner(
ip: ipaddress.ip_address, port=4028
) -> Union[None, AnyMiner]:
for i in range(PyasicSettings().network_ping_retries):
connection_fut = asyncio.open_connection(str(ip), port)
try:
connection_fut = asyncio.open_connection(str(ip), port)
# get the read and write streams from the connection
reader, writer = await asyncio.wait_for(
connection_fut, timeout=PyasicSettings().network_ping_timeout
@@ -243,13 +236,11 @@ async def ping_and_get_miner(
except asyncio.exceptions.TimeoutError:
# ping failed if we time out
continue
except ConnectionRefusedError as e:
except (ConnectionRefusedError, OSError):
# handle for other connection errors
logging.debug(f"{str(ip)}: Connection Refused.")
raise e
# ping failed, likely with an exception
raise ConnectionRefusedError
except Exception as e:
logging.warning(f"{str(ip)}: Ping And Get Miner Exception: {e}")
raise e
continue
raise ConnectionRefusedError
return

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.28.4"
version = "0.29.2"
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"