Update get_data to us get_some_data sub functions. (#27)

This commit is contained in:
UpstreamData
2023-01-26 22:18:03 -07:00
committed by GitHub
parent 67b3e2f312
commit 2d4c063dfa
147 changed files with 3639 additions and 2658 deletions

View File

@@ -11,15 +11,16 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import logging
import warnings
from typing import List, Union
from typing import List, Union, Optional, Tuple
from collections import namedtuple
import httpx
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import HashBoard
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
@@ -28,7 +29,7 @@ from pyasic.settings import PyasicSettings
class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
def __init__(self, ip: str, api_ver: str = "1.0.0") -> None:
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver=api_ver)
self.ip = ip
self.uname = "admin"
@@ -42,7 +43,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
f"http://{self.ip}/api/auth",
data={"username": self.uname, "password": self.pwd},
)
except Exception:
except httpx.HTTPError:
warnings.warn(f"Could not authenticate web token with miner: {self}")
else:
json_auth = auth.json()
@@ -56,28 +57,33 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
data = {}
async with httpx.AsyncClient() as client:
for i in range(PyasicSettings().miner_get_data_retries):
response = await client.post(
f"http://{self.ip}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt},
timeout=5,
data=data,
)
json_data = response.json()
if (
not json_data.get("success")
and "token" in json_data
and json_data.get("token") == "expired"
):
# refresh the token, retry
await self.auth()
continue
if not json_data.get("success"):
if json_data.get("msg"):
raise APIError(json_data["msg"])
elif json_data.get("message"):
raise APIError(json_data["message"])
raise APIError("Innosilicon web api command failed.")
return json_data
try:
response = await client.post(
f"http://{self.ip}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt},
timeout=5,
data=data,
)
json_data = response.json()
if (
not json_data.get("success")
and "token" in json_data
and json_data.get("token") == "expired"
):
# refresh the token, retry
await self.auth()
continue
if not json_data.get("success"):
if json_data.get("msg"):
raise APIError(json_data["msg"])
elif json_data.get("message"):
raise APIError(json_data["message"])
raise APIError("Innosilicon web api command failed.")
return json_data
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
async def fault_light_on(self) -> bool:
return False
@@ -85,39 +91,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
async def fault_light_off(self) -> bool:
return False
async def get_config(self) -> MinerConfig:
pools = None
cfg = MinerConfig()
async def get_config(self, api_pools: dict = None) -> MinerConfig:
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError as e:
logging.warning(e)
try:
pools = await self.api.pools()
except APIError as e:
logging.warning(e)
if pools:
if "POOLS" in pools.keys():
cfg = cfg.from_api(pools["POOLS"])
return cfg
async def get_mac(self) -> Union[str, None]:
try:
data = await self.send_web_command("overview")
except APIError:
pass
else:
if data.get("version"):
return data["version"].get("ethaddr").upper()
async def get_hostname(self) -> Union[str, None]:
return None
async def get_model(self) -> Union[str, None]:
try:
data = await self.send_web_command("type")
except APIError:
pass
else:
return data["type"]
if api_pools:
if "POOLS" in api_pools.keys():
cfg = MinerConfig().from_api(api_pools["POOLS"])
self.config = cfg
return self.config
async def reboot(self) -> bool:
try:
@@ -143,190 +128,342 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
"updatePools", data=config.as_inno(user_suffix=user_suffix)
)
async def get_errors(self) -> List[MinerErrorData]:
##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
##################################################
async def get_mac(
self, web_all_data: dict = None, web_overview: dict = None
) -> Optional[str]:
if not web_all_data and not web_overview:
try:
web_overview = await self.send_web_command("overview")
except APIError:
pass
if web_all_data:
try:
mac = web_all_data["mac"]
return mac.upper()
except KeyError:
pass
if web_overview:
try:
mac = web_overview["version"]["ethaddr"]
return mac.upper()
except KeyError:
pass
async def get_model(self, web_type: dict = None) -> Optional[str]:
if self.model:
logging.debug(f"Found model for {self.ip}: {self.model}")
return self.model
if not web_type:
try:
web_type = await self.send_web_command("type")
except APIError:
pass
if web_type:
try:
self.model = web_type["type"]
return self.model
except KeyError:
pass
async def get_hashrate(
self, api_summary: dict = None, web_all_data: dict = None
) -> Optional[float]:
if not api_summary and not web_all_data:
try:
api_summary = await self.api.summary()
except APIError:
pass
if web_all_data:
try:
return round(
float(web_all_data["total_hash"]["Hash Rate H"] / 1000000000000), 2
)
except KeyError:
pass
if api_summary:
try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError):
pass
async def get_hashboards(
self, api_stats: dict = None, web_all_data: dict = None
) -> List[HashBoard]:
hashboards = [
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
]
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
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 api_stats:
if api_stats.get("STATS"):
for idx, board in enumerate(api_stats["STATS"]):
try:
chips = board["Num active chips"]
except KeyError:
pass
else:
hashboards[idx].chips = chips
hashboards[idx].missing = False
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)
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)
return hashboards
async def get_wattage(self, web_all_data: dict = None) -> Optional[int]:
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:
return web_all_data["power"]
except KeyError:
pass
async def get_fans(
self, web_all_data: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
fans = fan_speeds(None, None, None, None)
psu_fans = psu_fan_speeds(None)
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:
spd = web_all_data["fansSpeed"]
except KeyError:
pass
else:
f = [None, None, None, None]
round((int(spd) * 6000) / 100)
for i in range(self.fan_count):
f[i] = spd
fans = fan_speeds(*f)
return miner_fan_speeds(fans, psu_fans)
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self, web_error_details: dict = None) -> List[MinerErrorData]:
errors = []
try:
data = await self.send_web_command("getErrorDetail")
except APIError:
pass
else:
if "code" in data:
err = data["code"]
if isinstance(err, str):
err = int(err)
if not web_error_details:
try:
web_error_details = await self.send_web_command("getErrorDetail")
except APIError:
pass
if web_error_details:
try:
# only 1 error?
# TODO: check if this should be a loop, can't remember.
err = web_error_details["code"]
except KeyError:
pass
else:
err = int(err)
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def get_data(self, allow_warning: bool = False) -> MinerData:
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model()
hostname = await self.get_hostname()
if model:
data.model = model
if hostname:
data.hostname = hostname
data.errors = await self.get_errors()
data.fault_light = await self.check_light()
await self.get_version()
data.api_ver = self.api_ver
data.fw_ver = self.fw_ver
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
all_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"summary", "pools", "stats", allow_warning=allow_warning
)
try:
miner_data = await self.api.multicommand(
"summary",
"version",
"pools",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
try:
all_data = (await self.send_web_command("getAll"))["all"]
except APIError:
pass
if not (miner_data or all_data):
return data
summary = miner_data.get("summary")
pools = miner_data.get("pools")
stats = miner_data.get("stats")
if summary:
summary = summary[0]
hr = summary.get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
if hr:
data.hashrate = round(hr / 1000000, 2)
elif all_data:
if all_data.get("total_hash"):
print(all_data["total_hash"])
hr = all_data["total_hash"].get("Hash Rate H")
if hr:
data.hashrate = round(hr / 1000000000000, 2)
if stats:
stats = stats[0]
if stats.get("STATS"):
for idx, board in enumerate(stats["STATS"]):
data.hashboards[idx].missing = True
chips = board.get("Num active chips")
if chips:
data.hashboards[idx].chips = chips
if chips > 0:
data.hashboards[idx].missing = False
if all_data:
if all_data.get("chain"):
for idx, board in enumerate(all_data["chain"]):
temp = board.get("Temp min")
if temp:
data.hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
data.hashboards[idx].hashrate = round(
hashrate / 1000000000000, 2
)
chip_temp = board.get("Temp max")
if chip_temp:
data.hashboards[idx].chip_temp = round(chip_temp)
if all_data.get("fansSpeed"):
speed = round((all_data["fansSpeed"] * 6000) / 100)
for fan in range(self.fan_count):
setattr(data, f"fan_{fan+1}", speed)
if all_data.get("mac"):
data.mac = all_data["mac"].upper()
else:
mac = await self.get_mac()
if mac:
data.mac = mac
if all_data.get("power"):
data.wattage = all_data["power"]
if pools or all_data.get("pools_config"):
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
for pool in pools.get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
elif all_data.get("pools_config"):
print(all_data["pools_config"])
for pool in all_data["pools_config"]:
if not pool_1_user:
pool_1_user = pool.get("user")
pool_1 = pool["url"]
elif not pool_2_user:
pool_2_user = pool.get("user")
pool_2 = pool["url"]
if not pool.get("user") == pool_1_user:
if not pool_2_user == pool.get("user"):
pool_2_user = pool.get("user")
pool_2 = pool["url"]
version = miner_data.get("version")
if version:
version = version[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, version, stats = (None for _ in range(4))
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
try:
web_all_data = await self.send_web_command("getAll")
except APIError:
web_all_data = None
try:
web_type = await self.send_web_command("type")
except APIError:
web_type = None
try:
web_error_details = await self.send_web_command("getErrorDetail")
except APIError:
web_error_details = None
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_1_url = pool_1
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(web_all_data=web_all_data),
"model": await self.get_model(web_type=web_type),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(
api_summary=summary, web_all_data=web_all_data
),
"hashboards": await self.get_hashboards(
api_stats=stats, web_all_data=web_all_data
),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(web_all_data=web_all_data),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(web_error_details=web_error_details),
"fault_light": await self.get_fault_light(),
}
if pool_1_user:
data.pool_1_user = pool_1_user
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans(web_all_data=web_all_data)
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
"stratum2+tcp://", ""
)
data.pool_2_url = pool_2
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
if pool_2_user:
data.pool_2_user = pool_2_user
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
if quota:
data.pool_split = str(quota)
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data