refactor: fix merge.

This commit is contained in:
UpstreamData
2024-01-15 13:09:23 -07:00
parent f1501718a3
commit e45e51ce65
19 changed files with 373 additions and 279 deletions

View File

@@ -17,7 +17,13 @@ from dataclasses import dataclass, field
from typing import Union from typing import Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
DpsPowerTarget,
DpsTarget,
Hours,
Power,
SetDpsRequest,
)
@dataclass @dataclass
@@ -37,13 +43,8 @@ class PowerScalingShutdownEnabled(MinerConfigValue):
return cfg return cfg
def as_bos_grpc(self) -> dict: def as_boser(self) -> dict:
cfg = {"enable_shutdown ": True} return {"enable_shutdown": True, "shutdown_duration": self.duration}
if self.duration is not None:
cfg["shutdown_duration"] = Hours(self.duration)
return cfg
@dataclass @dataclass
@@ -57,7 +58,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue):
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
return {"shutdown_enabled": False} return {"shutdown_enabled": False}
def as_bos_grpc(self) -> dict: def as_boser(self) -> dict:
return {"enable_shutdown ": False} return {"enable_shutdown ": False}
@@ -88,6 +89,19 @@ class PowerScalingShutdown(MinerConfigOption):
return cls.disabled() return cls.disabled()
return None return None
@classmethod
def from_boser(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdownEnabled")
if sd_enabled is not None:
if sd_enabled:
try:
return cls.enabled(power_scaling_conf["shutdownDuration"]["hours"])
except KeyError:
return cls.enabled()
else:
return cls.disabled()
return None
@dataclass @dataclass
class PowerScalingEnabled(MinerConfigValue): class PowerScalingEnabled(MinerConfigValue):
@@ -133,20 +147,19 @@ class PowerScalingEnabled(MinerConfigValue):
return {"power_scaling": cfg} return {"power_scaling": cfg}
def as_bos_grpc(self) -> dict: def as_boser(self) -> dict:
cfg = {"enable": True} return {
target_conf = {} "set_dps": SetDpsRequest(
if self.power_step is not None: enable=True,
target_conf["power_step"] = self.power_step **self.shutdown_enabled.as_boser(),
if self.minimum_power is not None: target=DpsTarget(
target_conf["min_power_target"] = self.minimum_power power_target=DpsPowerTarget(
power_step=Power(self.power_step),
cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf)) min_power_target=Power(self.minimum_power),
)
if self.shutdown_enabled is not None: ),
cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()} ),
}
return {"dps": cfg}
@dataclass @dataclass
@@ -187,3 +200,21 @@ class PowerScalingConfig(MinerConfigOption):
return cls.disabled() return cls.disabled()
return cls.default() return cls.default()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
dps_conf = grpc_miner_conf["dps"]
if not dps_conf.get("enabled", False):
return cls.disabled()
except LookupError:
return cls.default()
conf = {}
conf["shutdown_enabled"] = PowerScalingShutdown.from_boser(dps_conf)
if dps_conf.get("minPowerTarget") is not None:
conf["minimum_power"] = dps_conf["minPowerTarget"]["watt"]
if dps_conf.get("powerStep") is not None:
conf["power_step"] = dps_conf["powerStep"]["watt"]
return cls.enabled(**conf)

View File

@@ -41,7 +41,7 @@ class HiveonT9(Hiveon, T9):
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError): except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
pass pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=board, expected_chips=self.expected_chips) HashBoard(slot=board, expected_chips=self.expected_chips)
for board in range(self.expected_hashboards) for board in range(self.expected_hashboards)
@@ -83,7 +83,7 @@ class HiveonT9(Hiveon, T9):
return hashboards return hashboards
async def get_wattage(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
@@ -100,7 +100,7 @@ class HiveonT9(Hiveon, T9):
# parse wattage position out of raw data # parse wattage position out of raw data
return round(float(wattage_raw.split(" ")[0])) return round(float(wattage_raw.split(" ")[0]))
async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
env_temp_list = [] env_temp_list = []
board_map = { board_map = {
0: [2, 9, 10], 0: [2, 9, 10],

View File

@@ -45,51 +45,52 @@ BOSMINER_DATA_LOC = DataLocations(
"_get_mac", "_get_mac",
[WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")],
), ),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), str(DataOptions.FW_VERSION): DataFunction(
str(DataOptions.HOSTNAME): DataFunction("get_hostname"), "_get_fw_ver", [WebAPICommand("web_bos_info", "bos/info")]
),
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] "_get_expected_hashrate", [RPCAPICommand("api_devs", "devs")]
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", "_get_hashboards",
[ [
RPCAPICommand("api_temps", "temps"), RPCAPICommand("api_temps", "temps"),
RPCAPICommand("api_devdetails", "devdetails"), RPCAPICommand("api_devdetails", "devdetails"),
RPCAPICommand("api_devs", "devs"), RPCAPICommand("api_devs", "devs"),
], ],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"get_wattage", "_get_wattage",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", "_get_wattage_limit",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"get_fans", "_get_fans",
[RPCAPICommand("api_fans", "fans")], [RPCAPICommand("api_fans", "fans")],
), ),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"get_errors", "_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"is_mining", [RPCAPICommand("api_devdetails", "devdetails")] "_is_mining", [RPCAPICommand("api_devdetails", "devdetails")]
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_summary", "summary")] "_get_uptime", [RPCAPICommand("api_summary", "summary")]
), ),
str(DataOptions.CONFIG): DataFunction("get_config"), str(DataOptions.CONFIG): DataFunction("get_config"),
} }
@@ -288,16 +289,17 @@ class BOSMiner(BaseMiner):
gateway: str, gateway: str,
subnet_mask: str = "255.255.255.0", subnet_mask: str = "255.255.255.0",
): ):
cfg_data_lan = ( cfg_data_lan = "\n\t".join(
"config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'static'\n\toption ipaddr '" [
+ ip "config interface 'lan'",
+ "'\n\toption netmask '" "option type 'bridge'",
+ subnet_mask "option ifname 'eth0'",
+ "'\n\toption gateway '" "option proto 'static'",
+ gateway f"option ipaddr '{ip}'",
+ "'\n\toption dns '" f"option netmask '{subnet_mask}'",
+ dns f"option gateway '{gateway}'",
+ "'" f"option dns '{dns}'",
]
) )
data = await self.send_ssh_command("cat /etc/config/network") data = await self.send_ssh_command("cat /etc/config/network")
@@ -313,7 +315,14 @@ class BOSMiner(BaseMiner):
await conn.run("echo '" + config + "' > /etc/config/network") await conn.run("echo '" + config + "' > /etc/config/network")
async def set_dhcp(self): async def set_dhcp(self):
cfg_data_lan = "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'dhcp'" cfg_data_lan = "\n\t".join(
[
"config interface 'lan'",
"option type 'bridge'",
"option ifname 'eth0'",
"option proto 'dhcp'",
]
)
data = await self.send_ssh_command("cat /etc/config/network") data = await self.send_ssh_command("cat /etc/config/network")
split_data = data.split("\n\n") split_data = data.split("\n\n")
@@ -331,12 +340,10 @@ class BOSMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
if not web_net_conf: if not web_net_conf:
try: try:
web_net_conf = await self.web.luci.send_command( web_net_conf = await self.web.luci.get_net_conf()
"admin/network/iface_status/lan"
)
except APIError: except APIError:
pass pass
@@ -354,21 +361,7 @@ class BOSMiner(BaseMiner):
# if result: # if result:
# return result.upper().strip() # return result.upper().strip()
async def get_model(self) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if self.model is not None:
return self.model + " (BOS)"
return "? (BOS)"
async def get_version(
self, api_version: dict = None, graphql_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
api_ver_t = asyncio.create_task(self.get_api_ver(api_version))
fw_ver_t = asyncio.create_task(self.get_fw_ver())
await asyncio.gather(api_ver_t, fw_ver_t)
return miner_version(api_ver=api_ver_t.result(), fw_ver=fw_ver_t.result())
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if not api_version:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
@@ -386,19 +379,28 @@ class BOSMiner(BaseMiner):
return self.api_ver return self.api_ver
async def get_fw_ver(self) -> Optional[str]: async def _get_fw_ver(self, web_bos_info: dict) -> Optional[str]:
fw_ver = await self.send_ssh_command("cat /etc/bos_version") if web_bos_info is None:
try:
web_bos_info = await self.web.luci.get_bos_info()
except APIError:
return None
# if we get the version data, parse it if isinstance(web_bos_info, dict):
if fw_ver is not None: if "bos/info" in web_bos_info.keys():
ver = fw_ver.split("-")[5] web_bos_info = web_bos_info["bos/info"]
try:
ver = web_bos_info["version"].split("-")[5]
if "." in ver: if "." in ver:
self.fw_ver = ver self.fw_ver = ver
logging.debug(f"Found version for {self.ip}: {self.fw_ver}") logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
except (LookupError, AttributeError):
return None
return self.fw_ver return self.fw_ver
async def get_hostname(self) -> Union[str, None]: async def _get_hostname(self) -> Union[str, None]:
try: try:
hostname = ( hostname = (
await self.send_ssh_command("cat /proc/sys/kernel/hostname") await self.send_ssh_command("cat /proc/sys/kernel/hostname")
@@ -408,7 +410,7 @@ class BOSMiner(BaseMiner):
return None return None
return hostname return hostname
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API # get hr from API
if not api_summary: if not api_summary:
try: try:
@@ -422,7 +424,7 @@ class BOSMiner(BaseMiner):
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def get_hashboards( async def _get_hashboards(
self, self,
api_temps: dict = None, api_temps: dict = None,
api_devdetails: dict = None, api_devdetails: dict = None,
@@ -495,10 +497,10 @@ class BOSMiner(BaseMiner):
return hashboards return hashboards
async def get_env_temp(self) -> Optional[float]: async def _get_env_temp(self) -> Optional[float]:
return None return None
async def get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
if not api_tunerstatus: if not api_tunerstatus:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
@@ -513,7 +515,7 @@ class BOSMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]:
if not api_tunerstatus: if not api_tunerstatus:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
@@ -526,7 +528,7 @@ class BOSMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def get_fans(self, api_fans: dict = None) -> List[Fan]: async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
if not api_fans: if not api_fans:
try: try:
api_fans = await self.api.fans() api_fans = await self.api.fans()
@@ -543,10 +545,10 @@ class BOSMiner(BaseMiner):
return fans return fans
return [Fan() for _ in range(self.fan_count)] return [Fan() for _ in range(self.fan_count)]
async def get_fan_psu(self) -> Optional[int]: async def _get_fan_psu(self) -> Optional[int]:
return None return None
async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
if not api_tunerstatus: if not api_tunerstatus:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
@@ -576,7 +578,7 @@ class BOSMiner(BaseMiner):
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: async def _get_fault_light(self) -> bool:
if self.light: if self.light:
return self.light return self.light
try: try:
@@ -590,7 +592,7 @@ class BOSMiner(BaseMiner):
except (TypeError, AttributeError): except (TypeError, AttributeError):
return self.light return self.light
async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if not api_devs:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
@@ -616,7 +618,7 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if not api_devdetails: if not api_devdetails:
try: try:
api_devdetails = await self.api.send_command( api_devdetails = await self.api.send_command(
@@ -631,7 +633,7 @@ class BOSMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
@@ -648,7 +650,7 @@ class BOSMiner(BaseMiner):
BOSER_DATA_LOC = DataLocations( BOSER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"get_mac", "_get_mac",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [GRPCCommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
@@ -720,7 +722,6 @@ class BOSer(BaseMiner):
# static data # static data
self.api_type = "BOSMiner" self.api_type = "BOSMiner"
self.fw_str = "BOS"
# data gathering locations # data gathering locations
self.data_locations = BOSER_DATA_LOC self.data_locations = BOSER_DATA_LOC
# autotuning/shutdown support # autotuning/shutdown support
@@ -770,41 +771,27 @@ class BOSer(BaseMiner):
return False return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
logging.debug(f"{self}: Getting config.") grpc_conf = await self.web.grpc.get_miner_configuration()
try: return MinerConfig.from_boser(grpc_conf)
conn = await self._get_ssh_connection()
except ConnectionError:
conn = None
if conn:
async with conn:
# 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_bosminer(toml_data)
self.config = cfg
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
raise NotImplementedError
logging.debug(f"{self}: Sending config.") logging.debug(f"{self}: Sending config.")
self.config = config self.config = config
raise NotImplementedError
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
cfg = await self.get_config() result = await self.web.grpc.set_power_target(wattage)
if cfg is None: except APIError:
return False return False
cfg.mining_mode = MiningModePowerTune(wattage)
await self.send_config(cfg) try:
except Exception as e: if result["powerTarget"]["watt"] == wattage:
logging.warning(f"{self} set_power_limit: {e}")
return False
else:
return True return True
except KeyError:
pass
return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
@@ -823,20 +810,11 @@ class BOSer(BaseMiner):
except (LookupError, TypeError): except (LookupError, TypeError):
pass pass
async def get_model(self) -> Optional[str]: async def _get_model(self) -> Optional[str]:
if self.raw_model is not None: if self.model is not None:
return self.raw_model + " (BOS)" return self.model + " (BOS)"
return "? (BOS)" return "? (BOS)"
async def get_version(
self, api_version: dict = None, graphql_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
api_ver = await self.get_api_ver(api_version)
fw_ver = await self.get_fw_ver(graphql_version)
return miner_version(api_ver, fw_ver)
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if not api_version:
try: try:
@@ -902,10 +880,10 @@ class BOSer(BaseMiner):
if api_summary: if api_summary:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (LookupError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def get_expected_hashrate( async def _get_expected_hashrate(
self, grpc_miner_details: dict = None self, grpc_miner_details: dict = None
) -> Optional[float]: ) -> Optional[float]:
if not grpc_miner_details: if not grpc_miner_details:
@@ -920,7 +898,7 @@ class BOSer(BaseMiner):
except LookupError: except LookupError:
pass pass
async def get_hashboards(self, grpc_hashboards: dict = None): async def _get_hashboards(self, grpc_hashboards: dict = None):
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
@@ -991,7 +969,7 @@ class BOSer(BaseMiner):
except KeyError: except KeyError:
pass pass
async def get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None: if grpc_cooling_state is None:
try: try:
grpc_cooling_state = self.web.grpc.get_cooling_state() grpc_cooling_state = self.web.grpc.get_cooling_state()
@@ -1000,13 +978,13 @@ class BOSer(BaseMiner):
if grpc_cooling_state: if grpc_cooling_state:
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.fan_count):
try: try:
fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"]))
except LookupError: except (IndexError, KeyError):
pass pass
return fans return fans
return [Fan() for _ in range(self.expected_fans)] return [Fan() for _ in range(self.fan_count)]
async def _get_fan_psu(self) -> Optional[int]: async def _get_fan_psu(self) -> Optional[int]:
return None return None

View File

@@ -28,42 +28,41 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC
AVALON_DATA_LOC = DataLocations( AVALON_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"get_mac", [RPCAPICommand("api_version", "version")] "_get_mac", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")] "_get_fw_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"), str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_devs", "devs")] "_get_hashrate", [RPCAPICommand("api_devs", "devs")]
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")] "_get_hashboards", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"get_env_temp", [RPCAPICommand("api_stats", "stats")] "_get_env_temp", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.WATTAGE): DataFunction("get_wattage"), str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", [RPCAPICommand("api_stats", "stats")] "_get_wattage_limit", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")] "_get_fans", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"), str(DataOptions.ERRORS): DataFunction("_get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", [RPCAPICommand("api_stats", "stats")] "_get_fault_light", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.IS_MINING): DataFunction("is_mining"), str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"), str(DataOptions.UPTIME): DataFunction("_get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"), str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
@@ -174,7 +173,7 @@ class CGMinerAvalon(CGMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac(self, api_version: dict = None) -> Optional[str]: async def _get_mac(self, api_version: dict = None) -> Optional[str]:
if not api_version: if not api_version:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
@@ -192,7 +191,7 @@ class CGMinerAvalon(CGMiner):
except (KeyError, ValueError): except (KeyError, ValueError):
pass pass
async def get_hostname(self) -> Optional[str]: async def _get_hostname(self) -> Optional[str]:
return None return None
# if not mac: # if not mac:
# mac = await self.get_mac() # mac = await self.get_mac()
@@ -200,7 +199,7 @@ class CGMinerAvalon(CGMiner):
# if mac: # if mac:
# return f"Avalon{mac.replace(':', '')[-6:]}" # return f"Avalon{mac.replace(':', '')[-6:]}"
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]: async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if not api_devs:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
@@ -213,7 +212,7 @@ class CGMinerAvalon(CGMiner):
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
@@ -261,7 +260,7 @@ class CGMinerAvalon(CGMiner):
return hashboards return hashboards
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
@@ -276,7 +275,7 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
@@ -291,10 +290,10 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def get_wattage(self) -> Optional[int]: async def _get_wattage(self) -> Optional[int]:
return None return None
async def get_wattage_limit(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
@@ -309,7 +308,7 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
@@ -331,10 +330,10 @@ class CGMinerAvalon(CGMiner):
pass pass
return fans_data return fans_data
async def get_errors(self) -> List[MinerErrorData]: async def _get_errors(self) -> List[MinerErrorData]:
return [] return []
async def get_fault_light(self, api_stats: dict = None) -> bool: # noqa async def _get_fault_light(self, api_stats: dict = None) -> bool: # noqa
if self.light: if self.light:
return self.light return self.light
if not api_stats: if not api_stats:
@@ -363,8 +362,8 @@ class CGMinerAvalon(CGMiner):
pass pass
return False return False
async def is_mining(self, *args, **kwargs) -> Optional[bool]: async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None return None
async def get_uptime(self) -> Optional[int]: async def _get_uptime(self) -> Optional[int]:
return None return None

View File

@@ -23,39 +23,38 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC
HIVEON_DATA_LOC = DataLocations( HIVEON_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction("get_mac"), str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")] "_get_fw_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"), str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate", [RPCAPICommand("api_summary", "summary")]
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", [RPCAPICommand("api_stats", "stats")] "_get_hashboards", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"get_env_temp", [RPCAPICommand("api_stats", "stats")] "_get_env_temp", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"get_wattage", [RPCAPICommand("api_stats", "stats")] "_get_wattage", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")] "_get_fans", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"), str(DataOptions.ERRORS): DataFunction("_get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"), str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.CONFIG): DataFunction("get_config"), str(DataOptions.CONFIG): DataFunction("get_config"),
} }
@@ -71,16 +70,11 @@ class Hiveon(BMMiner):
# data gathering locations # data gathering locations
self.data_locations = HIVEON_DATA_LOC self.data_locations = HIVEON_DATA_LOC
async def get_model(self) -> Optional[str]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
if self.model is not None:
return self.model + " (Hiveon)"
return "? (Hiveon)"
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
pass pass
async def get_wattage(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
pass pass
async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
pass pass

View File

@@ -33,68 +33,67 @@ from pyasic.web.innosilicon import InnosiliconWebAPI
INNOSILICON_DATA_LOC = DataLocations( INNOSILICON_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"get_mac", "_get_mac",
[ [
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
WebAPICommand("web_overview", "overview"), WebAPICommand("web_overview", "overview"),
], ],
), ),
str(DataOptions.MODEL): DataFunction("get_model"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", [RPCAPICommand("api_version", "version")] "_get_fw_ver", [RPCAPICommand("api_version", "version")]
), ),
str(DataOptions.HOSTNAME): DataFunction("get_hostname"), str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", "_get_hashrate",
[ [
RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_summary", "summary"),
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", "_get_expected_hashrate",
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", "_get_hashboards",
[ [
RPCAPICommand("api_stats", "stats"), RPCAPICommand("api_stats", "stats"),
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"get_wattage", "_get_wattage",
[ [
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
RPCAPICommand("api_stats", "stats"), RPCAPICommand("api_stats", "stats"),
], ],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", "_get_wattage_limit",
[ [
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"get_fans", "_get_fans",
[ [
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"get_errors", "_get_errors",
[ [
WebAPICommand("web_get_error_detail", "getErrorDetail"), WebAPICommand("web_get_error_detail", "getErrorDetail"),
], ],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"), str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.CONFIG): DataFunction("get_config"), str(DataOptions.CONFIG): DataFunction("get_config"),
} }
@@ -176,7 +175,7 @@ class Innosilicon(CGMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac( async def _get_mac(
self, web_get_all: dict = None, web_overview: dict = None self, web_get_all: dict = None, web_overview: dict = None
) -> Optional[str]: ) -> Optional[str]:
if web_get_all: if web_get_all:
@@ -202,7 +201,7 @@ class Innosilicon(CGMiner):
except KeyError: except KeyError:
pass pass
async def get_hashrate( async def _get_hashrate(
self, api_summary: dict = None, web_get_all: dict = None self, api_summary: dict = None, web_get_all: dict = None
) -> Optional[float]: ) -> Optional[float]:
if web_get_all: if web_get_all:
@@ -234,7 +233,7 @@ class Innosilicon(CGMiner):
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
async def get_hashboards( async def _get_hashboards(
self, api_stats: dict = None, web_get_all: dict = None self, api_stats: dict = None, web_get_all: dict = None
) -> List[HashBoard]: ) -> List[HashBoard]:
if web_get_all: if web_get_all:
@@ -292,7 +291,7 @@ class Innosilicon(CGMiner):
return hashboards return hashboards
async def get_wattage( async def _get_wattage(
self, web_get_all: dict = None, api_stats: dict = None self, web_get_all: dict = None, api_stats: dict = None
) -> Optional[int]: ) -> Optional[int]:
if web_get_all: if web_get_all:
@@ -329,7 +328,7 @@ class Innosilicon(CGMiner):
wattage = int(wattage) wattage = int(wattage)
return wattage return wattage
async def get_fans(self, web_get_all: dict = None) -> List[Fan]: async def _get_fans(self, web_get_all: dict = None) -> List[Fan]:
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
@@ -354,7 +353,7 @@ class Innosilicon(CGMiner):
return fans return fans
async def get_errors( async def _get_errors(
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:
errors = [] errors = []
@@ -377,7 +376,7 @@ class Innosilicon(CGMiner):
errors.append(InnosiliconError(error_code=err)) errors.append(InnosiliconError(error_code=err))
return errors return errors
async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: async def _get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
@@ -400,5 +399,5 @@ class Innosilicon(CGMiner):
limit = 1250 + (250 * level) limit = 1250 + (250 * level)
return limit return limit
async def get_expected_hashrate(self) -> Optional[float]: async def _get_expected_hashrate(self) -> Optional[float]:
pass pass

View File

@@ -142,7 +142,7 @@ class BaseMiner(ABC):
@property @property
def model(self): def model(self):
model_data = [self.raw_model] model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
if self.fw_str is not None: if self.fw_str is not None:
model_data.append(f"({self.fw_str})") model_data.append(f"({self.fw_str})")
return " ".join(model_data) return " ".join(model_data)

View File

@@ -26,6 +26,7 @@ class BaseWebAPI(ABC):
self.ip = ip # ipaddress.ip_address(ip) self.ip = ip # ipaddress.ip_address(ip)
self.username = "root" self.username = "root"
self.pwd = "root" self.pwd = "root"
self.port = 80
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is BaseWebAPI: if cls is BaseWebAPI:

View File

@@ -35,10 +35,12 @@ class AntminerModernWebAPI(BaseWebAPI):
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], **parameters: Union[str, int, bool],
) -> dict: ) -> dict:
url = f"http://{self.ip}/cgi-bin/{command}.cgi" url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd) auth = httpx.DigestAuth(self.username, self.pwd)
try: try:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(
transport=settings.transport(),
) as client:
if parameters: if parameters:
data = await client.post( data = await client.post(
url, url,
@@ -149,7 +151,7 @@ class AntminerOldWebAPI(BaseWebAPI):
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], **parameters: Union[str, int, bool],
) -> dict: ) -> dict:
url = f"http://{self.ip}/cgi-bin/{command}.cgi" url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd) auth = httpx.DigestAuth(self.username, self.pwd)
try: try:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:

View File

@@ -31,6 +31,7 @@ class BOSMinerWebAPI(BaseWebAPI):
ip, settings.get("default_bosminer_password", "root") ip, settings.get("default_bosminer_password", "root")
) )
self._pwd = settings.get("default_bosminer_password", "root") self._pwd = settings.get("default_bosminer_password", "root")
self._port = 80
super().__init__(ip) super().__init__(ip)
@property @property
@@ -42,6 +43,15 @@ class BOSMinerWebAPI(BaseWebAPI):
self._pwd = other self._pwd = other
self.luci.pwd = other self.luci.pwd = other
@property
def port(self):
return self._port
@port.setter
def port(self, other: str):
self._port = other
self.luci.port = other
async def send_command( async def send_command(
self, self,
command: Union[str, dict], command: Union[str, dict],
@@ -63,6 +73,7 @@ class BOSerWebAPI(BOSMinerWebAPI):
ip, settings.get("default_bosminer_password", "root") ip, settings.get("default_bosminer_password", "root")
) )
self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root")) self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root"))
self._port = 80
super().__init__(ip) super().__init__(ip)
@property @property
@@ -76,6 +87,16 @@ class BOSerWebAPI(BOSMinerWebAPI):
self.gql.pwd = other self.gql.pwd = other
self.grpc.pwd = other self.grpc.pwd = other
@property
def port(self):
return self._port
@port.setter
def port(self, other: str):
self._port = other
self.luci.port = other
self.gql.port = other
async def send_command( async def send_command(
self, self,
command: Union[str, dict], command: Union[str, dict],
@@ -84,14 +105,14 @@ class BOSerWebAPI(BOSMinerWebAPI):
**parameters: Union[str, int, bool], **parameters: Union[str, int, bool],
) -> dict: ) -> dict:
command_type = self.select_command_type(command) command_type = self.select_command_type(command)
if command_type is "gql": if command_type == "gql":
return await self.gql.send_command(command) return await self.gql.send_command(command)
elif command_type is "grpc": elif command_type == "grpc":
try: try:
return await (getattr(self.grpc, command.replace("grpc_", "")))() return await (getattr(self.grpc, command.replace("grpc_", "")))()
except AttributeError: except AttributeError:
raise APIError(f"No gRPC command found for command: {command}") raise APIError(f"No gRPC command found for command: {command}")
elif command_type is "luci": elif command_type == "luci":
return await self.luci.send_command(command) return await self.luci.send_command(command)
@staticmethod @staticmethod

View File

@@ -26,6 +26,7 @@ class BOSerGraphQLAPI:
self.ip = ip self.ip = ip
self.username = "root" self.username = "root"
self.pwd = pwd self.pwd = pwd
self.port = 80
async def multicommand(self, *commands: dict) -> dict: async def multicommand(self, *commands: dict) -> dict:
def merge(*d: dict): def merge(*d: dict):
@@ -60,7 +61,7 @@ class BOSerGraphQLAPI:
self, self,
command: dict, command: dict,
) -> dict: ) -> dict:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}:{self.port}/graphql"
query = command query = command
if command is None: if command is None:
return {} return {}
@@ -93,7 +94,7 @@ class BOSerGraphQLAPI:
return "{" + ",".join(data) + "}" return "{" + ",".join(data) + "}"
async def auth(self, client: httpx.AsyncClient) -> None: async def auth(self, client: httpx.AsyncClient) -> None:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}:{self.port}/graphql"
await client.post( await client.post(
url, url,
json={ json={

View File

@@ -14,9 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio import asyncio
import logging
from datetime import timedelta from datetime import timedelta
from betterproto import Message from betterproto import Message
from grpclib import GRPCError, Status
from grpclib.client import Channel from grpclib.client import Channel
from pyasic.errors import APIError from pyasic.errors import APIError
@@ -44,6 +46,7 @@ class BOSerGRPCAPI:
self.ip = ip self.ip = ip
self.username = "root" self.username = "root"
self.pwd = pwd self.pwd = pwd
self.port = 50051
self._auth = None self._auth = None
self._auth_time = datetime.now() self._auth_time = datetime.now()
@@ -90,13 +93,23 @@ class BOSerGRPCAPI:
metadata = [] metadata = []
if auth: if auth:
metadata.append(("authorization", await self.auth())) metadata.append(("authorization", await self.auth()))
async with Channel(self.ip, 50051) as c: try:
async with Channel(self.ip, self.port) as c:
endpoint = getattr(BOSMinerGRPCStub(c), command) endpoint = getattr(BOSMinerGRPCStub(c), command)
if endpoint is None: if endpoint is None:
if not ignore_errors: if not ignore_errors:
raise APIError(f"Command not found - {endpoint}") raise APIError(f"Command not found - {endpoint}")
return {} return {}
try:
return (await endpoint(message, metadata=metadata)).to_pydict() return (await endpoint(message, metadata=metadata)).to_pydict()
except GRPCError as e:
if e.status == Status.UNAUTHENTICATED:
await self._get_auth()
metadata = [("authorization", await self.auth())]
return (await endpoint(message, metadata=metadata)).to_pydict()
raise e
except GRPCError as e:
raise APIError(f"gRPC command failed - {endpoint}") from e
async def auth(self): async def auth(self):
if self._auth is not None and self._auth_time - datetime.now() < timedelta( if self._auth is not None and self._auth_time - datetime.now() < timedelta(
@@ -107,7 +120,7 @@ class BOSerGRPCAPI:
return self._auth return self._auth
async def _get_auth(self): async def _get_auth(self):
async with Channel(self.ip, 50051) as c: async with Channel(self.ip, self.port) as c:
req = LoginRequest(username=self.username, password=self.pwd) req = LoginRequest(username=self.username, password=self.pwd)
async with c.request( async with c.request(
"/braiins.bos.v1.AuthenticationService/Login", "/braiins.bos.v1.AuthenticationService/Login",
@@ -152,7 +165,9 @@ class BOSerGRPCAPI:
) )
async def get_locate_device_status(self): async def get_locate_device_status(self):
return await self.send_command("get_locate_device_status") return await self.send_command(
"get_locate_device_status", GetLocateDeviceStatusRequest()
)
async def set_password(self, password: str = None): async def set_password(self, password: str = None):
return await self.send_command( return await self.send_command(
@@ -281,14 +296,70 @@ class BOSerGRPCAPI:
async def set_dps( async def set_dps(
self, self,
enable: bool,
power_step: int,
min_power_target: int,
enable_shutdown: bool = None,
shutdown_duration: int = None,
): ):
raise NotImplementedError
return await self.send_command("braiins.bos.v1.PerformanceService/SetDPS")
async def set_performance_mode(self):
raise NotImplementedError
return await self.send_command( return await self.send_command(
"braiins.bos.v1.PerformanceService/SetPerformanceMode" "set_dps",
message=SetDpsRequest(
enable=enable,
enable_shutdown=enable_shutdown,
shutdown_duration=shutdown_duration,
target=DpsTarget(
power_target=DpsPowerTarget(
power_step=Power(power_step),
min_power_target=Power(min_power_target),
)
),
),
)
async def set_performance_mode(
self,
wattage_target: int = None,
hashrate_target: int = None,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
):
if wattage_target is not None and hashrate_target is not None:
logging.error(
"Cannot use both wattage_target and hashrate_target, using wattage_target."
)
elif wattage_target is None and hashrate_target is None:
raise APIError(
"No target supplied, please supply either wattage_target or hashrate_target."
)
if wattage_target is not None:
return await self.send_command(
"set_performance_mode",
message=SetPerformanceModeRequest(
save_action=save_action,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
power_target=PowerTargetMode(
power_target=Power(watt=wattage_target)
)
)
),
),
)
if hashrate_target is not None:
return await self.send_command(
"set_performance_mode",
message=SetPerformanceModeRequest(
save_action=save_action,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
hashrate_target=HashrateTargetMode(
hashrate_target=TeraHashrate(
terahash_per_second=hashrate_target
)
)
)
),
),
) )
async def get_active_performance_mode(self): async def get_active_performance_mode(self):

View File

@@ -26,6 +26,7 @@ class BOSMinerLuCIAPI:
self.ip = ip self.ip = ip
self.username = "root" self.username = "root"
self.pwd = pwd self.pwd = pwd
self.port = 80
async def multicommand(self, *commands: str) -> dict: async def multicommand(self, *commands: str) -> dict:
data = {} data = {}
@@ -38,7 +39,7 @@ class BOSMinerLuCIAPI:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
await self.auth(client) await self.auth(client)
data = await client.get( data = await client.get(
f"http://{self.ip}/cgi-bin/luci/{path}", f"http://{self.ip}:{self.port}/cgi-bin/luci/{path}",
headers={"User-Agent": "BTC Tools v0.1"}, headers={"User-Agent": "BTC Tools v0.1"},
) )
if data.status_code == 200: if data.status_code == 200:
@@ -55,7 +56,7 @@ class BOSMinerLuCIAPI:
async def auth(self, session: httpx.AsyncClient): async def auth(self, session: httpx.AsyncClient):
login = {"luci_username": self.username, "luci_password": self.pwd} login = {"luci_username": self.username, "luci_password": self.pwd}
url = f"http://{self.ip}/cgi-bin/luci" url = f"http://{self.ip}:{self.port}/cgi-bin/luci"
headers = { headers = {
"User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set "User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",

View File

@@ -29,6 +29,7 @@ class ePICWebAPI(BaseWebAPI):
self.username = "root" self.username = "root"
self.pwd = settings.get("default_epic_password", "letmein") self.pwd = settings.get("default_epic_password", "letmein")
self.token = None self.token = None
self.port = 4028
async def send_command( async def send_command(
self, self,
@@ -50,13 +51,13 @@ class ePICWebAPI(BaseWebAPI):
"password": self.pwd, "password": self.pwd,
} }
response = await client.post( response = await client.post(
f"http://{self.ip}:4028/{command}", f"http://{self.ip}:{self.port}/{command}",
timeout=5, timeout=5,
json=epic_param, json=epic_param,
) )
else: else:
response = await client.get( response = await client.get(
f"http://{self.ip}:4028/{command}", f"http://{self.ip}:{self.port}/{command}",
timeout=5, timeout=5,
) )
if not response.status_code == 200: if not response.status_code == 200:

View File

@@ -33,10 +33,10 @@ class GoldshellWebAPI(BaseWebAPI):
async def auth(self): async def auth(self):
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
await client.get(f"http://{self.ip}/user/logout") await client.get(f"http://{self.ip}:{self.port}/user/logout")
auth = ( auth = (
await client.get( await client.get(
f"http://{self.ip}/user/login?username={self.username}&password={self.pwd}&cipher=false" f"http://{self.ip}:{self.port}/user/login?username={self.username}&password={self.pwd}&cipher=false"
) )
).json() ).json()
except httpx.HTTPError: except httpx.HTTPError:
@@ -46,7 +46,7 @@ class GoldshellWebAPI(BaseWebAPI):
try: try:
auth = ( auth = (
await client.get( await client.get(
f"http://{self.ip}/user/login?username=admin&password=bbad7537f4c8b6ea31eea0b3d760e257&cipher=true" f"http://{self.ip}:{self.port}/user/login?username=admin&password=bbad7537f4c8b6ea31eea0b3d760e257&cipher=true"
) )
).json() ).json()
except (httpx.HTTPError, json.JSONDecodeError): except (httpx.HTTPError, json.JSONDecodeError):
@@ -76,14 +76,14 @@ class GoldshellWebAPI(BaseWebAPI):
try: try:
if parameters: if parameters:
response = await client.put( response = await client.put(
f"http://{self.ip}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.jwt},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
) )
else: else:
response = await client.get( response = await client.get(
f"http://{self.ip}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.jwt},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )
@@ -106,7 +106,7 @@ class GoldshellWebAPI(BaseWebAPI):
for command in commands: for command in commands:
try: try:
response = await client.get( response = await client.get(
f"http://{self.ip}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.jwt},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )

View File

@@ -35,7 +35,7 @@ class InnosiliconWebAPI(BaseWebAPI):
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
auth = await client.post( auth = await client.post(
f"http://{self.ip}/api/auth", f"http://{self.ip}:{self.port}/api/auth",
data={"username": self.username, "password": self.pwd}, data={"username": self.username, "password": self.pwd},
) )
except httpx.HTTPError: except httpx.HTTPError:
@@ -58,7 +58,7 @@ class InnosiliconWebAPI(BaseWebAPI):
for i in range(settings.get("get_data_retries", 1)): for i in range(settings.get("get_data_retries", 1)):
try: try:
response = await client.post( response = await client.post(
f"http://{self.ip}/api/{command}", f"http://{self.ip}:{self.port}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.jwt},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
@@ -94,7 +94,7 @@ class InnosiliconWebAPI(BaseWebAPI):
for command in commands: for command in commands:
try: try:
response = await client.post( response = await client.post(
f"http://{self.ip}/api/{command}", f"http://{self.ip}:{self.port}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.jwt},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )

View File

@@ -34,7 +34,7 @@ class VNishWebAPI(BaseWebAPI):
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
auth = await client.post( auth = await client.post(
f"http://{self.ip}/api/v1/unlock", f"http://{self.ip}:{self.port}/api/v1/unlock",
json={"pw": self.pwd}, json={"pw": self.pwd},
) )
except httpx.HTTPError: except httpx.HTTPError:
@@ -68,21 +68,21 @@ class VNishWebAPI(BaseWebAPI):
if parameters.get("post"): if parameters.get("post"):
parameters.pop("post") parameters.pop("post")
response = await client.post( response = await client.post(
f"http://{self.ip}/api/v1/{command}", f"http://{self.ip}:{self.port}/api/v1/{command}",
headers={"Authorization": auth}, headers={"Authorization": auth},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
) )
elif not parameters == {}: elif not parameters == {}:
response = await client.post( response = await client.post(
f"http://{self.ip}/api/v1/{command}", f"http://{self.ip}:{self.port}/api/v1/{command}",
headers={"Authorization": auth}, headers={"Authorization": auth},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
) )
else: else:
response = await client.get( response = await client.get(
f"http://{self.ip}/api/v1/{command}", f"http://{self.ip}:{self.port}/api/v1/{command}",
headers={"Authorization": auth}, headers={"Authorization": auth},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )

View File

@@ -13,31 +13,27 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio
import inspect import inspect
import sys
import unittest import unittest
import warnings import warnings
from dataclasses import asdict from dataclasses import asdict
from pyasic.miners.backends import CGMiner # noqa from pyasic.miners.miner_factory import MINER_CLASSES
from pyasic.miners.base import BaseMiner
from pyasic.miners.miner_factory import MINER_CLASSES, MinerFactory
class MinersTest(unittest.TestCase): class MinersTest(unittest.TestCase):
def test_miner_model_creation(self): def test_miner_type_creation(self):
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
for miner_model in MINER_CLASSES.keys(): for miner_type in MINER_CLASSES.keys():
for miner_api in MINER_CLASSES[miner_model].keys(): for miner_model in MINER_CLASSES[miner_type].keys():
with self.subTest( with self.subTest(
msg=f"Creation of miner using model={miner_model}, api={miner_api}", msg=f"Test creation of miner",
miner_type=miner_type,
miner_model=miner_model, miner_model=miner_model,
miner_api=miner_api,
): ):
miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1")
self.assertTrue( self.assertTrue(
isinstance(miner, MINER_CLASSES[miner_model][miner_api]) isinstance(miner, MINER_CLASSES[miner_type][miner_model])
) )
def test_miner_data_map_keys(self): def test_miner_data_map_keys(self):
@@ -56,7 +52,6 @@ class MinersTest(unittest.TestCase):
"hostname", "hostname",
"is_mining", "is_mining",
"mac", "mac",
"model",
"expected_hashrate", "expected_hashrate",
"uptime", "uptime",
"wattage", "wattage",
@@ -64,14 +59,14 @@ class MinersTest(unittest.TestCase):
] ]
) )
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
for miner_model in MINER_CLASSES.keys(): for miner_type in MINER_CLASSES.keys():
for miner_api in MINER_CLASSES[miner_model].keys(): for miner_model in MINER_CLASSES[miner_type].keys():
with self.subTest( with self.subTest(
msg=f"Data map key check of miner using model={miner_model}, api={miner_api}", msg=f"Data map key check",
miner_type=miner_type,
miner_model=miner_model, miner_model=miner_model,
miner_api=miner_api,
): ):
miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1")
miner_keys = sorted( miner_keys = sorted(
[str(k) for k in asdict(miner.data_locations).keys()] [str(k) for k in asdict(miner.data_locations).keys()]
) )
@@ -79,14 +74,14 @@ class MinersTest(unittest.TestCase):
def test_data_locations_match_signatures_command(self): def test_data_locations_match_signatures_command(self):
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
for miner_model in MINER_CLASSES.keys(): for miner_type in MINER_CLASSES.keys():
for miner_api in MINER_CLASSES[miner_model].keys(): for miner_model in MINER_CLASSES[miner_type].keys():
miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1")
for data_point in asdict(miner.data_locations).values(): for data_point in asdict(miner.data_locations).values():
with self.subTest( with self.subTest(
msg=f"Test {data_point['cmd']} signature matches with model={miner_model}, api={miner_api}", msg=f"Test {data_point['cmd']} signature matches",
miner_type=miner_type,
miner_model=miner_model, miner_model=miner_model,
miner_api=miner_api,
): ):
func = getattr(miner, data_point["cmd"]) func = getattr(miner, data_point["cmd"])
signature = inspect.signature(func) signature = inspect.signature(func)