Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cd2566d0a | ||
|
|
1f1e5f23a2 | ||
|
|
3394234e4f | ||
|
|
e9882124ff | ||
|
|
1e5b19c149 | ||
|
|
c9339fec2f | ||
|
|
018c09e84f | ||
|
|
46e7f9a569 | ||
|
|
996ab58252 | ||
|
|
04974d5287 | ||
|
|
1a8a5ccb0e | ||
|
|
4c61c4c345 |
@@ -173,9 +173,14 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
writer.write(data)
|
writer.write(data)
|
||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
ret_data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||||
# instantiate data
|
try:
|
||||||
ret_data = b""
|
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||||
|
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||||
|
# append that data if there is more, and then onto the main loop.
|
||||||
|
ret_data += await asyncio.wait_for(reader.read(1), timeout=1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
return ret_data
|
||||||
|
|
||||||
# loop to receive all the data
|
# loop to receive all the data
|
||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||||
@@ -244,6 +249,8 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
str_data = str_data.replace("}{", "},{")
|
str_data = str_data.replace("}{", "},{")
|
||||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||||
str_data = str_data.replace("[,{", "[{")
|
str_data = str_data.replace("[,{", "[{")
|
||||||
|
# fix an error with a btminer return having a missing comma. (2023-01-06 version)
|
||||||
|
str_data = str_data.replace('""temp0', '","temp0')
|
||||||
# fix an error with Avalonminers returning inf and nan
|
# fix an error with Avalonminers returning inf and nan
|
||||||
str_data = str_data.replace("info", "1nfo")
|
str_data = str_data.replace("info", "1nfo")
|
||||||
str_data = str_data.replace("inf", "0")
|
str_data = str_data.replace("inf", "0")
|
||||||
|
|||||||
@@ -247,18 +247,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
try:
|
try:
|
||||||
data = await self._send_bytes(enc_command, timeout)
|
data = await self._send_bytes(enc_command, timeout)
|
||||||
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||||
if command["cmd"] in ["reboot", "restart_btminer", "power_on", "power_off"]:
|
|
||||||
logging.info(
|
|
||||||
f"{self} - (reboot/restart_btminer/power_on/power_off) - Whatsminers currently break this. "
|
|
||||||
f"Ignoring exception. Command probably worked."
|
|
||||||
)
|
|
||||||
# FAKING IT HERE
|
|
||||||
data = (
|
|
||||||
b'{"STATUS": "S", "When": 1670966423, "Code": 131, "Msg": "API command OK", "Description": "'
|
|
||||||
+ command["cmd"].encode("utf-8")
|
|
||||||
+ b'"}'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if ignore_errors:
|
if ignore_errors:
|
||||||
return {}
|
return {}
|
||||||
raise APIError("No data was returned from the API.")
|
raise APIError("No data was returned from the API.")
|
||||||
|
|||||||
@@ -483,10 +483,9 @@ class BOSMiner(BaseMiner):
|
|||||||
api_devs = d["devs"][0]
|
api_devs = d["devs"][0]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
api_devs = None
|
api_devs = None
|
||||||
|
|
||||||
if api_temps:
|
if api_temps:
|
||||||
try:
|
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"]:
|
for board in api_temps["TEMPS"]:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
@@ -499,7 +498,7 @@ class BOSMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_devdetails:
|
if api_devdetails:
|
||||||
try:
|
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"]:
|
for board in api_devdetails["DEVDETAILS"]:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
@@ -511,7 +510,7 @@ class BOSMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_devs:
|
if api_devs:
|
||||||
try:
|
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"]:
|
for board in api_devs["DEVS"]:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
|
|||||||
@@ -14,9 +14,54 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||||
from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
||||||
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS9(BMMiner, S9):
|
class BMMinerS9(BMMiner, S9):
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
|
super().__init__(ip, api_ver=api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.uname = "root"
|
||||||
|
self.pwd = PyasicSettings().global_x19_password
|
||||||
|
|
||||||
|
async def send_web_command(
|
||||||
|
self, command: str, params: dict = None
|
||||||
|
) -> Optional[dict]:
|
||||||
|
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||||
|
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
if params:
|
||||||
|
data = await client.post(url, data=params, auth=auth)
|
||||||
|
else:
|
||||||
|
data = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if data.status_code == 200:
|
||||||
|
try:
|
||||||
|
return data.json()
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_mac(self) -> Union[str, None]:
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command("get_system_info")
|
||||||
|
if data:
|
||||||
|
return data["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = await self.send_web_command("get_network_info")
|
||||||
|
if data:
|
||||||
|
return data["macaddr"]
|
||||||
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -136,10 +136,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
|
|
||||||
async def get_mac(
|
async def get_mac(
|
||||||
self,
|
self,
|
||||||
web_getAll: dict = None,
|
web_getAll: dict = None, # noqa
|
||||||
web_overview: dict = None, # noqa: named this way for automatic functionality
|
web_overview: dict = None, # noqa: named this way for automatic functionality
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
web_all_data = web_getAll
|
web_all_data = web_getAll.get("all")
|
||||||
if not web_all_data and not web_overview:
|
if not web_all_data and not web_overview:
|
||||||
try:
|
try:
|
||||||
web_overview = await self.send_web_command("overview")
|
web_overview = await self.send_web_command("overview")
|
||||||
@@ -183,7 +183,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
api_summary: dict = None,
|
api_summary: dict = None,
|
||||||
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
web_all_data = web_getAll
|
web_all_data = web_getAll.get("all")
|
||||||
if not api_summary and not web_all_data:
|
if not api_summary and not web_all_data:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
api_summary = await self.api.summary()
|
||||||
@@ -209,7 +209,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
api_stats: dict = None,
|
api_stats: dict = None,
|
||||||
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
||||||
) -> List[HashBoard]:
|
) -> List[HashBoard]:
|
||||||
web_all_data = web_getAll
|
web_all_data = web_getAll.get("all")
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.ideal_hashboards)
|
||||||
@@ -231,8 +231,9 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
if api_stats.get("STATS"):
|
if api_stats.get("STATS"):
|
||||||
for idx, board in enumerate(api_stats["STATS"]):
|
for board in api_stats["STATS"]:
|
||||||
try:
|
try:
|
||||||
|
idx = board["Chain ID"]
|
||||||
chips = board["Num active chips"]
|
chips = board["Num active chips"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
@@ -242,14 +243,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
|
|
||||||
if web_all_data:
|
if web_all_data:
|
||||||
if web_all_data.get("chain"):
|
if web_all_data.get("chain"):
|
||||||
for idx, board in enumerate(web_all_data["chain"]):
|
for board in web_all_data["chain"]:
|
||||||
|
idx = board.get("ASC")
|
||||||
|
if idx is not None:
|
||||||
temp = board.get("Temp min")
|
temp = board.get("Temp min")
|
||||||
if temp:
|
if temp:
|
||||||
hashboards[idx].temp = round(temp)
|
hashboards[idx].temp = round(temp)
|
||||||
|
|
||||||
hashrate = board.get("Hash Rate H")
|
hashrate = board.get("Hash Rate H")
|
||||||
if hashrate:
|
if hashrate:
|
||||||
hashboards[idx].hashrate = round(hashrate / 1000000000000, 2)
|
hashboards[idx].hashrate = round(
|
||||||
|
hashrate / 1000000000000, 2
|
||||||
|
)
|
||||||
|
|
||||||
chip_temp = board.get("Temp max")
|
chip_temp = board.get("Temp max")
|
||||||
if chip_temp:
|
if chip_temp:
|
||||||
@@ -258,9 +263,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def get_wattage(
|
async def get_wattage(
|
||||||
self, web_getAll: dict = None
|
self,
|
||||||
) -> Optional[int]: # noqa: named this way for automatic functionality
|
web_getAll: dict = None,
|
||||||
web_all_data = web_getAll
|
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:
|
if not web_all_data:
|
||||||
try:
|
try:
|
||||||
web_all_data = await self.send_web_command("getAll")
|
web_all_data = await self.send_web_command("getAll")
|
||||||
@@ -275,11 +282,28 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
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(
|
async def get_fans(
|
||||||
self,
|
self,
|
||||||
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
web_getAll: dict = None, # noqa: named this way for automatic functionality
|
||||||
) -> List[Fan]:
|
) -> List[Fan]:
|
||||||
web_all_data = web_getAll
|
web_all_data = web_getAll.get("all")
|
||||||
if not web_all_data:
|
if not web_all_data:
|
||||||
try:
|
try:
|
||||||
web_all_data = await self.send_web_command("getAll")
|
web_all_data = await self.send_web_command("getAll")
|
||||||
@@ -350,3 +374,24 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
|
|||||||
if not err == 0:
|
if not err == 0:
|
||||||
errors.append(InnosiliconError(error_code=err))
|
errors.append(InnosiliconError(error_code=err))
|
||||||
return errors
|
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
|
||||||
|
|||||||
@@ -183,14 +183,14 @@ class MinerNetwork:
|
|||||||
miner = await ping_and_get_miner(ip)
|
miner = await ping_and_get_miner(ip)
|
||||||
if miner:
|
if miner:
|
||||||
return miner
|
return miner
|
||||||
except ConnectionRefusedError:
|
except (ConnectionRefusedError, OSError):
|
||||||
tasks = [ping_and_get_miner(ip, port=port) for port in [4029, 8889]]
|
tasks = [ping_and_get_miner(ip, port=port) for port in [4029, 8889]]
|
||||||
for miner in asyncio.as_completed(tasks):
|
for miner in asyncio.as_completed(tasks):
|
||||||
try:
|
try:
|
||||||
miner = await miner
|
miner = await miner
|
||||||
if miner:
|
if miner:
|
||||||
return miner
|
return miner
|
||||||
except ConnectionRefusedError:
|
except (ConnectionRefusedError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -250,6 +250,5 @@ async def ping_and_get_miner(
|
|||||||
# ping failed, likely with an exception
|
# ping failed, likely with an exception
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{str(ip)}: Ping And Get Miner Exception: {e}")
|
logging.warning(f"{str(ip)}: Ping And Get Miner Exception: {e}")
|
||||||
raise e
|
raise ConnectionRefusedError
|
||||||
continue
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.28.1"
|
version = "0.28.7"
|
||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user