feature: finish get_data functions for bosminer

This commit is contained in:
UpstreamData
2024-01-12 13:29:46 -07:00
parent 53a018f526
commit 361d6e07cc
3 changed files with 141 additions and 526 deletions

View File

@@ -33,6 +33,7 @@ from pyasic.miners.base import (
DataLocations, DataLocations,
DataOptions, DataOptions,
GraphQLCommand, GraphQLCommand,
GRPCCommand,
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
@@ -665,128 +666,57 @@ BOSER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"get_mac", "get_mac",
[WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], [GRPCCommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.MODEL): DataFunction("get_model"), 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", [GRPCCommand("api_version", "get_api_version")]
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"get_fw_ver", "get_fw_ver",
[ [GRPCCommand("grpc_miner_details", "get_miner_details")],
GraphQLCommand(
"graphql_version", {"bos": {"info": {"version": {"full": None}}}}
)
],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"get_hostname", "get_hostname",
[GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})], [GRPCCommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"get_hashrate", "get_hashrate",
[ [RPCAPICommand("api_summary", "summary")],
RPCAPICommand("api_summary", "summary"),
GraphQLCommand(
"graphql_hashrate",
{
"bosminer": {
"info": {"workSolver": {"realHashrate": {"mhs1M": None}}}
}
},
),
],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] "get_expected_hashrate",
[GRPCCommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"get_hashboards", "get_hashboards",
[ [GRPCCommand("grpc_hashboards", "get_hashboards")],
RPCAPICommand("api_temps", "temps"),
RPCAPICommand("api_devdetails", "devdetails"),
RPCAPICommand("api_devs", "devs"),
GraphQLCommand(
"graphql_boards",
{
"bosminer": {
"info": {
"workSolver": {
"childSolvers": {
"name": None,
"realHashrate": {"mhs1M": None},
"hwDetails": {"chips": None},
"temperatures": {"degreesC": None},
}
}
}
}
},
),
],
), ),
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",
[ [GRPCCommand("grpc_miner_stats", "get_miner_stats")],
RPCAPICommand("api_tunerstatus", "tunerstatus"),
GraphQLCommand(
"graphql_wattage",
{
"bosminer": {
"info": {
"workSolver": {"power": {"approxConsumptionW": None}}
}
}
},
),
],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", "get_wattage_limit",
[ [
RPCAPICommand("api_tunerstatus", "tunerstatus"), GRPCCommand(
GraphQLCommand( "grpc_active_performance_mode", "get_active_performance_mode"
"graphql_wattage_limit", )
{"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}},
),
], ],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"get_fans", "get_fans",
[ [GRPCCommand("grpc_cooling_state", "get_cooling_state")],
RPCAPICommand("api_fans", "fans"),
GraphQLCommand(
"graphql_fans",
{"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}},
),
],
), ),
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"),
GraphQLCommand(
"graphql_errors",
{
"bosminer": {
"info": {
"workSolver": {
"childSolvers": {
"name": None,
"tuner": {"statusMessages": None},
}
}
}
}
},
),
],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", "get_fault_light",
[GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})], [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"is_mining", [RPCAPICommand("api_devdetails", "devdetails")] "is_mining", [RPCAPICommand("api_devdetails", "devdetails")]
@@ -830,41 +760,29 @@ class BOSer(BaseMiner):
return False return False
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
return await self.restart_bosminer() return await self.restart_boser()
async def restart_bosminer(self) -> bool: async def restart_boser(self) -> bool:
logging.debug(f"{self}: Sending bosminer restart command.") ret = await self.web.grpc.restart()
ret = await self.send_ssh_command("/etc/init.d/bosminer restart") return True
logging.debug(f"{self}: bosminer restart command completed.")
if isinstance(ret, str):
return True
return False
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
data = await self.api.pause() await self.web.grpc.pause_mining()
except APIError: except APIError:
return False return False
if data.get("PAUSE"): return True
if data["PAUSE"][0]:
return True
return False
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
data = await self.api.resume() await self.web.grpc.resume_mining()
except APIError: except APIError:
return False return False
if data.get("RESUME"): return True
if data["RESUME"][0]:
return True
return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.") ret = await self.web.grpc.reboot()
ret = await self.send_ssh_command("/sbin/reboot") if ret == {}:
logging.debug(f"{self}: Reboot command completed.")
if isinstance(ret, str):
return True return True
return False return False
@@ -895,28 +813,18 @@ class BOSer(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, grpc_miner_details: dict = None) -> Optional[str]:
if not web_net_conf: if not grpc_miner_details:
try: try:
web_net_conf = await self.web.send_command( grpc_miner_details = await self.web.grpc.get_miner_details()
"admin/network/iface_status/lan"
)
except APIError: except APIError:
pass pass
if isinstance(web_net_conf, dict): if grpc_miner_details:
if "admin/network/iface_status/lan" in web_net_conf.keys():
web_net_conf = web_net_conf["admin/network/iface_status/lan"]
if web_net_conf:
try: try:
return web_net_conf[0]["macaddr"] return grpc_miner_details["macAddress"].upper()
except LookupError: except (LookupError, TypeError):
pass pass
# could use ssh, but its slow and buggy
# result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
# if result:
# return result.upper().strip()
async def get_model(self) -> Optional[str]: async def get_model(self) -> Optional[str]:
if self.model is not None: if self.model is not None:
@@ -950,27 +858,21 @@ class BOSer(BaseMiner):
return self.api_ver return self.api_ver
async def get_fw_ver(self, graphql_version: dict = None) -> Optional[str]: async def get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
if not graphql_version: if not grpc_miner_details:
try: try:
graphql_version = await self.web.send_command( grpc_miner_details = await self.web.grpc.get_miner_details()
{"bos": {"info": {"version": {"full"}}}}
)
except APIError: except APIError:
pass pass
fw_ver = None fw_ver = None
if graphql_version: if grpc_miner_details:
try: try:
fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"] fw_ver = grpc_miner_details["bosVersion"]["current"]
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
if not fw_ver:
# try version data file
fw_ver = await self.send_ssh_command("cat /etc/bos_version")
# if we get the version data, parse it # if we get the version data, parse it
if fw_ver is not None: if fw_ver is not None:
ver = fw_ver.split("-")[5] ver = fw_ver.split("-")[5]
@@ -980,62 +882,20 @@ class BOSer(BaseMiner):
return self.fw_ver return self.fw_ver
async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]: async def get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]:
hostname = None if not grpc_miner_details:
if not graphql_hostname:
try: try:
graphql_hostname = await self.web.send_command({"bos": {"hostname"}}) grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError: except APIError:
pass pass
if graphql_hostname: if grpc_miner_details:
try: try:
hostname = graphql_hostname["data"]["bos"]["hostname"] return grpc_miner_details["hostname"]
return hostname except LookupError:
except (TypeError, KeyError):
pass pass
try: async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
async with await self._get_ssh_connection() as conn:
if conn is not None:
data = await conn.run("cat /proc/sys/kernel/hostname")
host = data.stdout.strip()
logging.debug(f"Found hostname for {self.ip}: {host}")
hostname = host
else:
logging.warning(f"Failed to get hostname for miner: {self}")
except Exception as e:
logging.warning(f"Failed to get hostname for miner: {self}, {e}")
return hostname
async def get_hashrate(
self, api_summary: dict = None, graphql_hashrate: dict = None
) -> Optional[float]:
# get hr from graphql
if not graphql_hashrate:
try:
graphql_hashrate = await self.web.send_command(
{"bosminer": {"info": {"workSolver": {"realHashrate": {"mhs1M"}}}}}
)
except APIError:
pass
if graphql_hashrate:
try:
return round(
float(
graphql_hashrate["data"]["bosminer"]["info"]["workSolver"][
"realHashrate"
]["mhs1M"]
/ 1000000
),
2,
)
except (LookupError, ValueError, TypeError):
pass
# get hr from API
if not api_summary: if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
@@ -1048,243 +908,104 @@ class BOSer(BaseMiner):
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def get_hashboards( async def get_expected_hashrate(
self, self, grpc_miner_details: dict = None
api_temps: dict = None, ) -> Optional[float]:
api_devdetails: dict = None, if not grpc_miner_details:
api_devs: dict = None, try:
graphql_boards: dict = None, grpc_miner_details = await self.web.grpc.get_miner_details()
): except APIError:
pass
if grpc_miner_details:
try:
return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000
except LookupError:
pass
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)
] ]
if not graphql_boards and not (api_devs or api_temps or api_devdetails): if grpc_hashboards is None:
try: try:
graphql_boards = await self.web.send_command( grpc_hashboards = await self.web.grpc.get_hashboards()
{
"bosminer": {
"info": {
"workSolver": {
"childSolvers": {
"name": None,
"realHashrate": {"mhs1M"},
"hwDetails": {"chips"},
"temperatures": {"degreesC"},
}
}
}
}
},
)
except APIError: except APIError:
pass pass
if graphql_boards: if grpc_hashboards is not None:
try: for board in grpc_hashboards["hashboards"]:
boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][ idx = int(board["id"]) - 1
"childSolvers" if board.get("chipsCount") is not None:
] hashboards[idx].chips = board["chipsCount"]
except (TypeError, LookupError): if board.get("boardTemp") is not None:
boards = None hashboards[idx].temp = board["boardTemp"]["degreeC"]
if board.get("highestChipTemp") is not None:
if boards: hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][
b_names = [int(b["name"]) for b in boards] "degreeC"
offset = 0 ]
if 3 in b_names: if board.get("stats") is not None:
offset = 1 if not board["stats"]["realHashrate"]["last5S"] == {}:
elif 6 in b_names or 7 in b_names or 8 in b_names: hashboards[idx].hashrate = round(
offset = 6 board["stats"]["realHashrate"]["last5S"][
for hb in boards: "gigahashPerSecond"
_id = int(hb["name"]) - offset ]
board = hashboards[_id] / 1000,
2,
board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2) )
temps = hb["temperatures"] hashboards[idx].missing = False
try:
if len(temps) > 0:
board.temp = round(hb["temperatures"][0]["degreesC"])
if len(temps) > 1:
board.chip_temp = round(hb["temperatures"][1]["degreesC"])
except (TypeError, KeyError, ValueError, IndexError):
pass
details = hb.get("hwDetails")
if details:
if chips := details["chips"]:
board.chips = chips
board.missing = False
return hashboards
cmds = []
if not api_temps:
cmds.append("temps")
if not api_devdetails:
cmds.append("devdetails")
if not api_devs:
cmds.append("devs")
if len(cmds) > 0:
try:
d = await self.api.multicommand(*cmds)
except APIError:
d = {}
try:
api_temps = d["temps"][0]
except (KeyError, IndexError):
api_temps = None
try:
api_devdetails = d["devdetails"][0]
except LookupError:
api_devdetails = None
try:
api_devs = d["devs"][0]
except (KeyError, IndexError):
api_devs = None
if api_temps:
try:
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
for board in api_temps["TEMPS"]:
_id = board["ID"] - offset
chip_temp = round(board["Chip"])
board_temp = round(board["Board"])
hashboards[_id].chip_temp = chip_temp
hashboards[_id].temp = board_temp
except (IndexError, KeyError, ValueError, TypeError):
pass
if api_devdetails:
try:
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devdetails["DEVDETAILS"]:
_id = board["ID"] - offset
chips = board["Chips"]
hashboards[_id].chips = chips
hashboards[_id].missing = False
except (IndexError, KeyError):
pass
if api_devs:
try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
for board in api_devs["DEVS"]:
_id = board["ID"] - offset
hashrate = round(float(board["MHS 1m"] / 1000000), 2)
hashboards[_id].hashrate = hashrate
except (IndexError, KeyError):
pass
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( async def get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
self, api_tunerstatus: dict = None, graphql_wattage: dict = None if grpc_miner_stats is None:
) -> Optional[int]:
if not graphql_wattage and not api_tunerstatus:
try: try:
graphql_wattage = await self.web.send_command( grpc_miner_stats = self.web.grpc.get_miner_stats()
{
"bosminer": {
"info": {"workSolver": {"power": {"approxConsumptionW"}}}
}
}
)
except APIError:
pass
if graphql_wattage is not None:
try:
return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
"power"
]["approxConsumptionW"]
except (LookupError, TypeError):
pass
if not api_tunerstatus:
try:
api_tunerstatus = await self.api.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus: if grpc_miner_stats:
try: try:
return api_tunerstatus["TUNERSTATUS"][0][ return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
"ApproximateMinerPowerConsumption" except KeyError:
]
except LookupError:
pass pass
async def get_wattage_limit( async def get_wattage_limit(
self, api_tunerstatus: dict = None, graphql_wattage_limit: dict = None self, grpc_active_performance_mode: dict = None
) -> Optional[int]: ) -> Optional[int]:
if not graphql_wattage_limit and not api_tunerstatus: if grpc_active_performance_mode is None:
try: try:
graphql_wattage_limit = await self.web.send_command( grpc_active_performance_mode = (
{"bosminer": {"info": {"workSolver": {"power": {"limitW"}}}}} self.web.grpc.get_active_performance_mode()
) )
except APIError: except APIError:
pass pass
if graphql_wattage_limit: if grpc_active_performance_mode:
try: try:
return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][ return grpc_active_performance_mode["tunerMode"]["powerTarget"][
"power" "powerTarget"
]["limitW"] ]["watt"]
except (LookupError, TypeError): except KeyError:
pass pass
if not api_tunerstatus: async def get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() grpc_cooling_state = self.web.grpc.get_cooling_state()
except APIError: except APIError:
pass pass
if api_tunerstatus: if grpc_cooling_state:
try:
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
except (KeyError, IndexError):
pass
async def get_fans(
self, api_fans: dict = None, graphql_fans: dict = None
) -> List[Fan]:
if not graphql_fans and not api_fans:
try:
graphql_fans = await self.web.send_command(
{"bosminer": {"info": {"fans": {"name", "rpm"}}}}
)
except APIError:
pass
if graphql_fans.get("data"):
fans = [] fans = []
for n in range(self.fan_count): for n in range(self.fan_count):
try: try:
fans.append( fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"]))
Fan(
speed=graphql_fans["data"]["bosminer"]["info"]["fans"][n][
"rpm"
]
)
)
except (LookupError, TypeError):
pass
return fans
if not api_fans:
try:
api_fans = await self.api.fans()
except APIError:
pass
if api_fans:
fans = []
for n in range(self.fan_count):
try:
fans.append(Fan(api_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
@@ -1293,56 +1014,7 @@ class BOSer(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
async def get_errors( async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
self, api_tunerstatus: dict = None, graphql_errors: dict = None
) -> List[MinerErrorData]:
if not graphql_errors and not api_tunerstatus:
try:
graphql_errors = await self.web.send_command(
{
"bosminer": {
"info": {
"workSolver": {
"childSolvers": {
"name": None,
"tuner": {"statusMessages"},
}
}
}
}
}
)
except APIError:
pass
if graphql_errors:
errors = []
try:
boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
"childSolvers"
]
except (LookupError, TypeError):
boards = None
if boards:
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else 0
for hb in boards:
_id = int(hb["name"]) - offset
tuner = hb["tuner"]
if tuner:
if msg := tuner.get("statusMessages"):
if len(msg) > 0:
if hb["tuner"]["statusMessages"][0] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips",
]:
errors.append(
BraiinsOSError(
f"Slot {_id} {hb['tuner']['statusMessages'][0]}"
)
)
if not api_tunerstatus: if not api_tunerstatus:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
@@ -1372,87 +1044,24 @@ class BOSer(BaseMiner):
except LookupError: except LookupError:
pass pass
async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: async def get_fault_light(self, grpc_locate_device_status: dict = None) -> bool:
if self.light: if self.light is not None:
return self.light return self.light
if not graphql_fault_light: if not grpc_locate_device_status:
if self.fw_ver:
# fw version has to be greater than 21.09 and not 21.09
if (
int(self.fw_ver.split(".")[0]) == 21
and int(self.fw_ver.split(".")[1]) > 9
) or int(self.fw_ver.split(".")[0]) > 21:
try:
graphql_fault_light = await self.web.send_command(
{"bos": {"faultLight"}}
)
except APIError:
pass
else:
logging.info(
f"FW version {self.fw_ver} is too low for fault light info in graphql."
)
else:
# worth trying
try:
graphql_fault_light = await self.web.send_command(
{"bos": {"faultLight"}}
)
except APIError:
logging.debug(
"GraphQL fault light failed, likely due to version being too low (<=21.0.9)"
)
if not graphql_fault_light:
# also a failure
logging.debug(
"GraphQL fault light failed, likely due to version being too low (<=21.0.9)"
)
# get light through GraphQL
if graphql_fault_light:
try: try:
self.light = graphql_fault_light["data"]["bos"]["faultLight"] grpc_locate_device_status = (
return self.light await self.web.grpc.get_locate_device_status()
except (TypeError, ValueError, LookupError): )
pass
# get light via ssh if that fails (10x slower)
try:
data = (
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
).strip()
self.light = False
if data == "50":
self.light = True
return self.light
except (TypeError, AttributeError):
return self.light
async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs:
try:
api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
if api_devs: if grpc_locate_device_status:
if grpc_locate_device_status == {}:
return False
try: try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0 return grpc_locate_device_status["enabled"]
hr_list = [] except LookupError:
for board in api_devs["DEVS"]:
_id = board["ID"] - offset
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
if expected_hashrate:
hr_list.append(expected_hashrate)
if len(hr_list) == 0:
return 0
else:
return round(
(sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2
)
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]:

View File

@@ -98,17 +98,15 @@ class BOSerWebAPI(BOSMinerWebAPI):
def select_command_type(command: Union[str, dict]) -> str: def select_command_type(command: Union[str, dict]) -> str:
if isinstance(command, dict): if isinstance(command, dict):
return "gql" return "gql"
elif command.startswith("grpc_"):
return "grpc"
else: else:
return "luci" return "grpc"
async def multicommand( async def multicommand(
self, *commands: Union[dict, str], allow_warning: bool = True self, *commands: Union[dict, str], allow_warning: bool = True
) -> dict: ) -> dict:
cmd_types = {"grpc": [], "gql": [], "luci": []} cmd_types = {"grpc": [], "gql": []}
for cmd in commands: for cmd in commands:
cmd_types[self.select_command_type(cmd)] = cmd cmd_types[self.select_command_type(cmd)].append(cmd)
async def no_op(): async def no_op():
return {} return {}
@@ -118,21 +116,13 @@ class BOSerWebAPI(BOSMinerWebAPI):
self.grpc.multicommand(*cmd_types["grpc"]) self.grpc.multicommand(*cmd_types["grpc"])
) )
else: else:
grpc_data_t = no_op() grpc_data_t = asyncio.create_task(no_op())
if len(cmd_types["gql"]) > 0: if len(cmd_types["gql"]) > 0:
gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"]))
else: else:
gql_data_t = no_op() gql_data_t = asyncio.create_task(no_op())
if len(cmd_types["luci"]) > 0:
luci_data_t = asyncio.create_task(
self.luci.multicommand(*cmd_types["luci"])
)
else:
luci_data_t = no_op()
await asyncio.gather(grpc_data_t, gql_data_t, luci_data_t) await asyncio.gather(grpc_data_t, gql_data_t)
data = dict( data = dict(**grpc_data_t.result(), **gql_data_t.result())
**luci_data_t.result(), **gql_data_t.result(), **luci_data_t.result()
)
return data return data

View File

@@ -13,6 +13,7 @@
# 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
from datetime import timedelta from datetime import timedelta
from betterproto import Message from betterproto import Message
@@ -65,7 +66,20 @@ class BOSerGRPCAPI:
] ]
async def multicommand(self, *commands: str) -> dict: async def multicommand(self, *commands: str) -> dict:
pass result = {"multicommand": True}
tasks = {}
for command in commands:
try:
tasks[command] = asyncio.create_task(getattr(self, command)())
except AttributeError:
result["command"] = {}
await asyncio.gather(*list(tasks.values()))
for cmd in tasks:
result[cmd] = tasks[cmd].result()
return result
async def send_command( async def send_command(
self, self,
@@ -164,10 +178,12 @@ class BOSerGRPCAPI:
) )
async def get_tuner_state(self): async def get_tuner_state(self):
return await self.send_command("get_tuner_state") return await self.send_command("get_tuner_state", GetTunerStateRequest())
async def list_target_profiles(self): async def list_target_profiles(self):
return await self.send_command("list_target_profiles") return await self.send_command(
"list_target_profiles", ListTargetProfilesRequest()
)
async def set_default_power_target( async def set_default_power_target(
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY