refactor: use protocol for BaseMiner and update attributes to be part of the class rather than a __init__ method.
This commit is contained in:
@@ -13,7 +13,6 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import List, Optional, Union
|
||||
@@ -96,69 +95,29 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerRPCAPI(ip, api_ver)
|
||||
self.web = BOSMinerWebAPI(ip)
|
||||
"""Handler for old versions of BraiinsOS+ (pre-gRPC)"""
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BOSMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = True
|
||||
self.supports_shutdown = True
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
_web_cls = BOSMinerWebAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
firmware = "BOS+"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
data_locations = BOSMINER_DATA_LOC
|
||||
|
||||
try:
|
||||
conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10)
|
||||
except (ConnectionError, asyncio.TimeoutError):
|
||||
return None
|
||||
|
||||
# open an ssh connection
|
||||
async with conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
stderr = result.stderr
|
||||
result = result.stdout
|
||||
|
||||
if len(stderr) > len(result):
|
||||
result = stderr
|
||||
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return result
|
||||
supports_shutdown = True
|
||||
supports_autotuning = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
logging.debug(f"{self}: Sending fault_light on command.")
|
||||
ret = await self.send_ssh_command("miner fault_light on")
|
||||
logging.debug(f"{self}: fault_light on command completed.")
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = True
|
||||
return self.light
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
logging.debug(f"{self}: Sending fault_light off command.")
|
||||
self.light = False
|
||||
ret = await self.send_ssh_command("miner fault_light off")
|
||||
logging.debug(f"{self}: fault_light off command completed.")
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = False
|
||||
return True
|
||||
@@ -168,9 +127,8 @@ class BOSMiner(BaseMiner):
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
logging.debug(f"{self}: Sending bosminer restart command.")
|
||||
ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
||||
logging.debug(f"{self}: bosminer restart command completed.")
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
return False
|
||||
@@ -180,6 +138,7 @@ class BOSMiner(BaseMiner):
|
||||
data = await self.api.pause()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
if data.get("PAUSE"):
|
||||
if data["PAUSE"][0]:
|
||||
return True
|
||||
@@ -190,40 +149,32 @@ class BOSMiner(BaseMiner):
|
||||
data = await self.api.resume()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
if data.get("RESUME"):
|
||||
if data["RESUME"][0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
ret = await self.send_ssh_command("/sbin/reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
logging.debug(f"{self}: Getting config.")
|
||||
raw_data = await self.send_ssh_command("cat /etc/bosminer.toml")
|
||||
|
||||
try:
|
||||
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.")
|
||||
toml_data = toml.loads(raw_data)
|
||||
cfg = MinerConfig.from_bosminer(toml_data)
|
||||
self.config = cfg
|
||||
except toml.TomlDecodeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
self.config = config
|
||||
|
||||
toml_conf = toml.dumps(
|
||||
@@ -241,6 +192,7 @@ class BOSMiner(BaseMiner):
|
||||
conn = await self._get_ssh_connection()
|
||||
except ConnectionError as e:
|
||||
raise APIError("SSH connection failed when sending config.") from e
|
||||
|
||||
async with conn:
|
||||
# BBB check because bitmain suxx
|
||||
bbb_check = await conn.run(
|
||||
@@ -251,20 +203,13 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
if not bbb:
|
||||
await conn.run("/etc/init.d/bosminer stop")
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Opening config file.")
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
logging.debug(f"{self}: Restarting BOSMiner")
|
||||
await conn.run("/etc/init.d/bosminer start")
|
||||
|
||||
# I really hate BBB, please get rid of it if you have it
|
||||
else:
|
||||
await conn.run("/etc/init.d/S99bosminer stop")
|
||||
logging.debug(f"{self}: BBB sending config")
|
||||
await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml")
|
||||
logging.debug(f"{self}: BBB restarting bosminer.")
|
||||
await conn.run("/etc/init.d/S99bosminer start")
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
@@ -274,8 +219,10 @@ class BOSMiner(BaseMiner):
|
||||
return False
|
||||
cfg.mining_mode = MiningModePowerTune(wattage)
|
||||
await self.send_config(cfg)
|
||||
except APIError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} set_power_limit: {e}")
|
||||
logging.warning(f"{self} - Failed to set power limit: {e}")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -377,7 +324,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, web_bos_info: dict) -> Optional[str]:
|
||||
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
|
||||
if web_bos_info is None:
|
||||
try:
|
||||
web_bos_info = await self.web.luci.get_bos_info()
|
||||
@@ -392,7 +339,6 @@ class BOSMiner(BaseMiner):
|
||||
ver = web_bos_info["version"].split("-")[5]
|
||||
if "." in ver:
|
||||
self.fw_ver = ver
|
||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
||||
except (LookupError, AttributeError):
|
||||
return None
|
||||
|
||||
@@ -404,7 +350,7 @@ class BOSMiner(BaseMiner):
|
||||
await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||
).strip()
|
||||
except Exception as e:
|
||||
logging.error(f"BOSMiner get_hostname failed with error: {e}")
|
||||
logging.error(f"{self} - Getting hostname failed: {e}")
|
||||
return None
|
||||
return hostname
|
||||
|
||||
@@ -426,7 +372,7 @@ class BOSMiner(BaseMiner):
|
||||
api_temps: dict = None,
|
||||
api_devdetails: dict = None,
|
||||
api_devs: dict = None,
|
||||
):
|
||||
) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -494,9 +440,6 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
@@ -542,9 +485,6 @@ class BOSMiner(BaseMiner):
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
@@ -709,22 +649,15 @@ BOSER_DATA_LOC = DataLocations(
|
||||
|
||||
|
||||
class BOSer(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerRPCAPI(ip, api_ver)
|
||||
self.web = BOSerWebAPI(ip)
|
||||
"""Handler for new versions of BraiinsOS+ (post-gRPC)"""
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BOSER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = True
|
||||
self.supports_shutdown = True
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
_web_cls = BOSerWebAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
data_locations = BOSER_DATA_LOC
|
||||
|
||||
supports_autotuning = True
|
||||
supports_shutdown = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
resp = await self.web.grpc.set_locate_device_status(True)
|
||||
@@ -770,11 +703,6 @@ class BOSer(BaseMiner):
|
||||
|
||||
return MinerConfig.from_boser(grpc_conf)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
raise NotImplementedError
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
self.config = config
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
result = await self.web.grpc.set_power_target(wattage)
|
||||
@@ -812,7 +740,6 @@ class BOSer(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
# Now get the API version
|
||||
if api_version is not None:
|
||||
try:
|
||||
api_ver = api_version["VERSION"][0]["API"]
|
||||
@@ -843,11 +770,10 @@ class BOSer(BaseMiner):
|
||||
ver = fw_ver.split("-")[5]
|
||||
if "." in ver:
|
||||
self.fw_ver = ver
|
||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]:
|
||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
@@ -888,7 +814,7 @@ class BOSer(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, grpc_hashboards: dict = None):
|
||||
async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -924,9 +850,6 @@ class BOSer(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
||||
if grpc_miner_stats is None:
|
||||
try:
|
||||
@@ -976,9 +899,6 @@ class BOSer(BaseMiner):
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user