refactor: use protocol for BaseMiner and update attributes to be part of the class rather than a __init__ method.

This commit is contained in:
UpstreamData
2024-01-16 14:47:43 -07:00
parent 10293ae24a
commit 307926afbb
135 changed files with 1469 additions and 3262 deletions

View File

@@ -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: