Update data locations to be typed with dataclasses and enums. (#82)

* feature: swap AntminerModern to new data location style.

* bug: fix a bunch of missed instances of `nominal_` naming.

* feature: add support for S19 Pro Hydro.

* version: bump version number.

* dependencies: bump httpx version

* version: bump version number.

* feature: implement data locations for all remaining miners.

* refactor: remove some unused docstrings.

* feature: swap AntminerModern to new data location style.

* feature: implement data locations for all remaining miners.

* refactor: remove some unused docstrings.

* bug: fix misnamed data locations, and update base miner get_data to use new data locations.

* bug: fix include/exclude implementation on get_data.

* bug: swap ePIC to BaseMiner subclass.

* feature: add DataOptions to __all__

* tests: update data tests with new data locations method.

* bug: remove bad command from bosminer commands.

* dependencies: update dependencies.

* bug: fix some typing issues with python 3.8, and remove useless semaphore and scan threads.

* bug: fix KeyError when pools rpc command returns broken data.
This commit is contained in:
UpstreamData
2024-01-04 13:03:45 -07:00
committed by GitHub
parent 936474ed3b
commit 6e7442f90d
24 changed files with 861 additions and 984 deletions

View File

@@ -48,10 +48,10 @@ PrePowerOnMessage = Union[
def _crypt(word: str, salt: str) -> str: def _crypt(word: str, salt: str) -> str:
"""Encrypts a word with a salt, using a standard salt format. r"""Encrypts a word with a salt, using a standard salt format.
Encrypts a word using a salt with the format Encrypts a word using a salt with the format
'\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a \s*\$(\d+)\$([\w\./]*)\$. If this format is not used, a
ValueError is raised. ValueError is raised.
Parameters: Parameters:
@@ -62,7 +62,7 @@ def _crypt(word: str, salt: str) -> str:
An MD5 hash of the word with the salt. An MD5 hash of the word with the salt.
""" """
# compile a standard format for the salt # compile a standard format for the salt
standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$") standard_salt = re.compile(r"\s*\$(\d+)\$([\w\./]*)\$")
# check if the salt matches # check if the salt matches
match = standard_salt.match(salt) match = standard_salt.match(salt)
# if the matching fails, the salt is incorrect # if the matching fails, the salt is incorrect

View File

@@ -29,7 +29,7 @@ from pyasic.data import (
) )
from pyasic.errors import APIError, APIWarning from pyasic.errors import APIError, APIWarning
from pyasic.miners import get_miner from pyasic.miners import get_miner
from pyasic.miners.base import AnyMiner from pyasic.miners.base import AnyMiner, DataOptions
from pyasic.miners.miner_factory import MinerFactory, miner_factory from pyasic.miners.miner_factory import MinerFactory, miner_factory
from pyasic.miners.miner_listener import MinerListener from pyasic.miners.miner_listener import MinerListener
from pyasic.network import MinerNetwork from pyasic.network import MinerNetwork
@@ -50,6 +50,7 @@ __all__ = [
"APIWarning", "APIWarning",
"get_miner", "get_miner",
"AnyMiner", "AnyMiner",
"DataOptions",
"MinerFactory", "MinerFactory",
"miner_factory", "miner_factory",
"MinerListener", "MinerListener",

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union from typing import Dict, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@@ -132,7 +132,7 @@ class MiningModeManual(MinerConfigValue):
global_freq: float global_freq: float
global_volt: float global_volt: float
boards: dict[int, ManualBoardSettings] = field(default_factory=dict) boards: Dict[int, ManualBoardSettings] = field(default_factory=dict)
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual": def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual":

View File

@@ -16,7 +16,7 @@
import random import random
import string import string
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union from typing import Dict, List, Union
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@@ -144,7 +144,7 @@ class Pool(MinerConfigValue):
@dataclass @dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
pools: list[Pool] = field(default_factory=list) pools: List[Pool] = field(default_factory=list)
quota: int = 1 quota: int = 1
name: str = None name: str = None
@@ -278,7 +278,7 @@ class PoolGroup(MinerConfigValue):
@dataclass @dataclass
class PoolConfig(MinerConfigValue): class PoolConfig(MinerConfigValue):
groups: list[PoolGroup] = field(default_factory=list) groups: List[PoolGroup] = field(default_factory=list)
@classmethod @classmethod
def default(cls) -> "PoolConfig": def default(cls) -> "PoolConfig":
@@ -292,7 +292,7 @@ class PoolConfig(MinerConfigValue):
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]]) return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
@classmethod @classmethod
def simple(cls, pools: list[Union[Pool, dict[str, str]]]) -> "PoolConfig": def simple(cls, pools: List[Union[Pool, Dict[str, str]]]) -> "PoolConfig":
group_pools = [] group_pools = []
for pool in pools: for pool in pools:
if isinstance(pool, dict): if isinstance(pool, dict):
@@ -342,7 +342,10 @@ class PoolConfig(MinerConfigValue):
@classmethod @classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig": def from_api(cls, api_pools: dict) -> "PoolConfig":
pool_data = api_pools["POOLS"] try:
pool_data = api_pools["POOLS"]
except KeyError:
return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
return cls([PoolGroup.from_api(pool_data)]) return cls([PoolGroup.from_api(pool_data)])

View File

@@ -14,7 +14,6 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio
from typing import List, Optional, Union from typing import List, Optional, Union
from pyasic.API import APIError from pyasic.API import APIError
@@ -23,50 +22,62 @@ from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
ANTMINER_MODERN_DATA_LOC = { ANTMINER_MODERN_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, "get_mac", [WebAPICommand("web_get_system_info", "get_system_info")]
}, ),
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hostname": { ),
"cmd": "get_hostname", str(DataOptions.FW_VERSION): DataFunction(
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
}, ),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.HOSTNAME): DataFunction(
"expected_hashrate": { "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.HASHRATE): DataFunction(
}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHBOARDS): DataFunction(
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"fault_light": { str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"cmd": "get_fault_light", str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
}, str(DataOptions.FANS): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "get_fans", [RPCAPICommand("api_stats", "stats")]
"is_mining": { ),
"cmd": "is_mining", str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, str(DataOptions.ERRORS): DataFunction(
}, "get_errors", [WebAPICommand("web_summary", "summary")]
"uptime": { ),
"cmd": "get_uptime", str(DataOptions.FAULT_LIGHT): DataFunction(
"kwargs": {"api_stats": {"api": "stats"}}, "get_fault_light",
}, [WebAPICommand("web_get_blink_status", "get_blink_status")],
"config": { ),
"cmd": "get_config", str(DataOptions.IS_MINING): DataFunction(
"kwargs": {}, "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
}, ),
} str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class AntminerModern(BMMiner): class AntminerModern(BMMiner):
@@ -298,39 +309,49 @@ class AntminerModern(BMMiner):
pass pass
ANTMINER_OLD_DATA_LOC = { ANTMINER_OLD_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_hostname", ),
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.HOSTNAME): DataFunction(
"cmd": "get_expected_hashrate", "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HASHRATE): DataFunction(
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"errors": {"cmd": "get_errors", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"fault_light": { ),
"cmd": "get_fault_light", str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, str(DataOptions.FANS): DataFunction(
"is_mining": { "get_fans", [RPCAPICommand("api_stats", "stats")]
"cmd": "is_mining", ),
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
}, str(DataOptions.ERRORS): DataFunction("get_errors"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.FAULT_LIGHT): DataFunction(
"config": {"cmd": "get_config", "kwargs": {}}, "get_fault_light",
} [WebAPICommand("web_get_blink_status", "get_blink_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class AntminerOld(CGMiner): class AntminerOld(CGMiner):

View File

@@ -22,32 +22,48 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
BFGMINER_DATA_LOC = { BFGMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
} str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BFGMiner(BaseMiner): class BFGMiner(BaseMiner):
@@ -268,32 +284,6 @@ class BFGMiner(BaseMiner):
return fans return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -20,41 +20,55 @@ from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.goldshell import GoldshellWebAPI from pyasic.web.goldshell import GoldshellWebAPI
GOLDSHELL_DATA_LOC = { GOLDSHELL_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_setting": {"web": "setting"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_setting", "setting")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [WebAPICommand("web_status", "status")]
"hashboards": { ),
"cmd": "get_hashboards", str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"kwargs": { str(DataOptions.HASHRATE): DataFunction(
"api_devs": {"api": "devs"}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"api_devdetails": {"api": "devdetails"}, ),
}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashboards",
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, [
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, RPCAPICommand("api_devs", "devs"),
"errors": {"cmd": "get_errors", "kwargs": {}}, RPCAPICommand("api_devdetails", "devdetails"),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, ],
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": { str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"cmd": "get_config", str(DataOptions.FANS): DataFunction(
"kwargs": {}, "get_fans", [RPCAPICommand("api_stats", "stats")]
}, ),
} str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BFGMinerGoldshell(BFGMiner): class BFGMinerGoldshell(BFGMiner):

View File

@@ -23,32 +23,50 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
BMMINER_DATA_LOC = { BMMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
} str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BMMiner(BaseMiner): class BMMiner(BaseMiner):
@@ -315,32 +333,6 @@ class BMMiner(BaseMiner):
return fans return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -27,157 +27,157 @@ from pyasic.config.mining import MiningModePowerTune
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
GraphQLCommand,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.bosminer import BOSMinerWebAPI from pyasic.web.bosminer import BOSMinerWebAPI
BOSMINER_DATA_LOC = { BOSMINER_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": { "get_mac",
"web_net_conf": {"web": "/cgi-bin/luci/admin/network/iface_status/lan"} [
}, WebAPICommand(
}, "web_net_conf", "/cgi-bin/luci/admin/network/iface_status/lan"
"model": {"cmd": "get_model", "kwargs": {}}, )
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, ],
"fw_ver": { ),
"cmd": "get_fw_ver", str(DataOptions.MODEL): DataFunction("get_model"),
"kwargs": { str(DataOptions.API_VERSION): DataFunction(
"graphql_version": {"web": {"bos": {"info": {"version": {"full": None}}}}} "get_api_ver", [RPCAPICommand("api_version", "version")]
}, ),
}, str(DataOptions.FW_VERSION): DataFunction(
"hostname": { "get_fw_ver",
"cmd": "get_hostname", [
"kwargs": {"graphql_hostname": {"web": {"bos": {"hostname": None}}}}, GraphQLCommand(
}, "graphql_version", {"bos": {"info": {"version": {"full": None}}}}
"hashrate": { )
"cmd": "get_hashrate", ],
"kwargs": { ),
"api_summary": {"api": "summary"}, str(DataOptions.HOSTNAME): DataFunction(
"graphql_hashrate": { "get_hostname",
"web": { [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})],
"bosminer": { ),
"info": {"workSolver": {"realHashrate": {"mhs1M": None}}} str(DataOptions.HASHRATE): DataFunction(
} "get_hashrate",
}, [
}, RPCAPICommand("api_summary", "summary"),
}, GraphQLCommand(
}, "graphql_hashrate",
"expected_hashrate": { {
"cmd": "get_expected_hashrate", "bosminer": {
"kwargs": {"api_devs": {"api": "devs"}}, "info": {"workSolver": {"realHashrate": {"mhs1M": None}}}
}, }
"hashboards": { },
"cmd": "get_hashboards", ),
"kwargs": { ],
"api_temps": {"api": "temps"}, ),
"api_devdetails": {"api": "devdetails"}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"api_devs": {"api": "devs"}, "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")]
"graphql_boards": { ),
"web": { str(DataOptions.HASHBOARDS): DataFunction(
"bosminer": { "get_hashboards",
"info": { [
"workSolver": { RPCAPICommand("api_temps", "temps"),
"childSolvers": { RPCAPICommand("api_devdetails", "devdetails"),
"name": None, RPCAPICommand("api_devs", "devs"),
"realHashrate": {"mhs1M": None}, GraphQLCommand(
"hwDetails": {"chips": None}, "graphql_boards",
"temperatures": {"degreesC": None}, {
"bosminer": {
"info": {
"workSolver": {
"childSolvers": {
"name": None,
"realHashrate": {"mhs1M": None},
"hwDetails": {"chips": None},
"temperatures": {"degreesC": None},
}
} }
} }
} }
} },
}, ),
}, ],
}, ),
}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"wattage": { str(DataOptions.WATTAGE): DataFunction(
"cmd": "get_wattage", "get_wattage",
"kwargs": { [
"api_tunerstatus": {"api": "tunerstatus"}, RPCAPICommand("api_tunerstatus", "tunerstatus"),
"graphql_wattage": { GraphQLCommand(
"web": { "graphql_wattage",
"bosminer": { {
"info": {"workSolver": {"power": {"approxConsumptionW": None}}} "bosminer": {
} "info": {
} "workSolver": {"power": {"approxConsumptionW": None}}
}, }
}, }
}, },
"wattage_limit": { ),
"cmd": "get_wattage_limit", ],
"kwargs": { ),
"api_tunerstatus": {"api": "tunerstatus"}, str(DataOptions.WATTAGE_LIMIT): DataFunction(
"graphql_wattage_limit": { "get_wattage_limit",
"web": { [
"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}} RPCAPICommand("api_tunerstatus", "tunerstatus"),
} GraphQLCommand(
}, "graphql_wattage_limit",
}, {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}},
}, ),
"fans": { ],
"cmd": "get_fans", ),
"kwargs": { str(DataOptions.FANS): DataFunction(
"api_fans": {"api": "fans"}, "get_fans",
"graphql_fans": { [
"web": {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}} RPCAPICommand("api_fans", "fans"),
}, GraphQLCommand(
}, "graphql_fans",
}, {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}},
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ],
"errors": { ),
"cmd": "get_errors", str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
"kwargs": { str(DataOptions.ERRORS): DataFunction(
"api_tunerstatus": {"api": "tunerstatus"}, "get_errors",
"graphql_errors": { [
"web": { RPCAPICommand("api_tunerstatus", "tunerstatus"),
"bosminer": { GraphQLCommand(
"info": { "graphql_errors",
"workSolver": { {
"childSolvers": { "bosminer": {
"name": None, "info": {
"tuner": {"statusMessages": None}, "workSolver": {
"childSolvers": {
"name": None,
"tuner": {"statusMessages": None},
}
} }
} }
} }
} },
} ),
}, ],
}, ),
}, str(DataOptions.FAULT_LIGHT): DataFunction(
"fault_light": { "get_fault_light",
"cmd": "get_fault_light", [GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})],
"kwargs": {"graphql_fault_light": {"web": {"bos": {"faultLight": None}}}}, ),
}, str(DataOptions.IS_MINING): DataFunction(
"pools": { "is_mining", [RPCAPICommand("api_devdetails", "devdetails")]
"cmd": "get_pools", ),
"kwargs": { str(DataOptions.UPTIME): DataFunction(
"api_pools": {"api": "pools"}, "get_uptime", [RPCAPICommand("api_summary", "summary")]
"graphql_pools": { ),
"web": { str(DataOptions.CONFIG): DataFunction("get_config"),
"bosminer": { }
"config": { )
"... on BosminerConfig": {
"groups": {
"pools": {"url": None, "user": None},
"strategy": {
"... on QuotaStrategy": {"quota": None}
},
}
}
}
}
}
},
},
},
"is_mining": {
"cmd": "is_mining",
"kwargs": {"api_devdetails": {"api": "devdetails"}},
},
"uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}},
"config": {"cmd": "get_config", "kwargs": {}},
}
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
@@ -231,7 +231,6 @@ class BOSMiner(BaseMiner):
return result return result
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner."""
logging.debug(f"{self}: Sending fault_light on command.") logging.debug(f"{self}: Sending fault_light on command.")
ret = await self.send_ssh_command("miner fault_light on") ret = await self.send_ssh_command("miner fault_light on")
logging.debug(f"{self}: fault_light on command completed.") logging.debug(f"{self}: fault_light on command completed.")
@@ -241,7 +240,6 @@ class BOSMiner(BaseMiner):
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
"""Sends command to turn off fault light on the miner."""
logging.debug(f"{self}: Sending fault_light off command.") logging.debug(f"{self}: Sending fault_light off command.")
self.light = False self.light = False
ret = await self.send_ssh_command("miner fault_light off") ret = await self.send_ssh_command("miner fault_light off")
@@ -252,11 +250,9 @@ class BOSMiner(BaseMiner):
return False return False
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners.backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
return await self.restart_bosminer() return await self.restart_bosminer()
async def restart_bosminer(self) -> bool: async def restart_bosminer(self) -> bool:
"""Restart bosminer hashing process."""
logging.debug(f"{self}: Sending bosminer restart command.") logging.debug(f"{self}: Sending bosminer restart command.")
ret = await self.send_ssh_command("/etc/init.d/bosminer restart") ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
logging.debug(f"{self}: bosminer restart command completed.") logging.debug(f"{self}: bosminer restart command completed.")
@@ -285,7 +281,6 @@ class BOSMiner(BaseMiner):
return False return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("/sbin/reboot") ret = await self.send_ssh_command("/sbin/reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
@@ -833,95 +828,6 @@ class BOSMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
async def get_pools(
self, api_pools: dict = None, graphql_pools: dict = None
) -> List[dict]:
if not graphql_pools and not api_pools:
try:
graphql_pools = await self.web.send_command(
{
"bosminer": {
"config": {
"... on BosminerConfig": {
"groups": {
"pools": {"urluser"},
"strategy": {"... on QuotaStrategy": {"quota"}},
}
}
}
}
}
)
except APIError:
pass
if graphql_pools:
groups = []
try:
g = graphql_pools["data"]["bosminer"]["config"]["groups"]
for group in g:
pools = {"quota": group["strategy"]["quota"]}
for i, pool in enumerate(group["pools"]):
pools[f"pool_{i + 1}_url"] = (
pool["url"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["user"]
groups.append(pools)
return groups
except (KeyError, TypeError):
pass
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
seen = []
groups = [{"quota": "0"}]
if api_pools.get("POOLS"):
for i, pool in enumerate(api_pools["POOLS"]):
if len(seen) == 0:
seen.append(pool["User"])
if not pool["User"] in seen:
# need to use get_config, as this will never read perfectly as there are some bad edge cases
groups = []
cfg = await self.get_config()
if cfg:
for group in cfg.pool_groups:
pools = {"quota": group.quota}
for _i, _pool in enumerate(group.pools):
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
"stratum+tcp://", ""
).replace("stratum2+tcp://", "")
pools[f"pool_{_i + 1}_user"] = _pool.username
groups.append(pools)
return groups
else:
groups[0][f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
groups[0][f"pool_{i + 1}_user"] = pool["User"]
else:
groups = []
cfg = await self.get_config()
if cfg:
for group in cfg.pool_groups:
pools = {"quota": group.quota}
for _i, _pool in enumerate(group.pools):
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
"stratum+tcp://", ""
).replace("stratum2+tcp://", "")
pools[f"pool_{_i + 1}_user"] = _pool.username
groups.append(pools)
return groups
return groups
async def get_errors( async def get_errors(
self, api_tunerstatus: dict = None, graphql_errors: dict = None self, api_tunerstatus: dict = None, graphql_errors: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:

View File

@@ -136,9 +136,6 @@ class BOSMinerOld(BOSMiner):
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]: async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
return None return None
async def get_pools(self, *args, **kwargs) -> List[dict]:
return []
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
return [] return []

View File

@@ -23,74 +23,86 @@ from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
BTMINER_DATA_LOC = { BTMINER_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": { "get_mac",
"api_summary": {"api": "summary"}, [
"api_get_miner_info": {"api": "get_miner_info"}, RPCAPICommand("api_summary", "summary"),
}, RPCAPICommand("api_get_miner_info", "get_miner_info"),
}, ],
"model": {"cmd": "get_model", "kwargs": {}}, ),
"api_ver": { str(DataOptions.MODEL): DataFunction("get_model"),
"cmd": "get_api_ver", str(DataOptions.API_VERSION): DataFunction(
"kwargs": {"api_get_version": {"api": "get_version"}}, "get_api_ver", [RPCAPICommand("api_get_version", "get_version")]
}, ),
"fw_ver": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_fw_ver", "get_fw_ver",
"kwargs": { [
"api_get_version": {"api": "get_version"}, RPCAPICommand("api_get_version", "get_version"),
"api_summary": {"api": "summary"}, RPCAPICommand("api_summary", "summary"),
}, ],
}, ),
"hostname": { str(DataOptions.HOSTNAME): DataFunction(
"cmd": "get_hostname", "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")]
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, ),
}, str(DataOptions.HASHRATE): DataFunction(
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"expected_hashrate": { ),
"cmd": "get_expected_hashrate", str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"kwargs": {"api_summary": {"api": "summary"}}, "get_expected_hashrate", [RPCAPICommand("api_summary", "summary")]
}, ),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}}, str(DataOptions.HASHBOARDS): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {"api_summary": {"api": "summary"}}}, "get_hashboards", [RPCAPICommand("api_devs", "devs")]
"wattage": {"cmd": "get_wattage", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"wattage_limit": { str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"cmd": "get_wattage_limit", "get_env_temp", [RPCAPICommand("api_summary", "summary")]
"kwargs": {"api_summary": {"api": "summary"}}, ),
}, str(DataOptions.WATTAGE): DataFunction(
"fans": { "get_wattage", [RPCAPICommand("api_summary", "summary")]
"cmd": "get_fans", ),
"kwargs": { str(DataOptions.WATTAGE_LIMIT): DataFunction(
"api_summary": {"api": "summary"}, "get_wattage_limit", [RPCAPICommand("api_summary", "summary")]
"api_get_psu": {"api": "get_psu"}, ),
}, str(DataOptions.FANS): DataFunction(
}, "get_fans",
"fan_psu": { [
"cmd": "get_fan_psu", RPCAPICommand("api_summary", "summary"),
"kwargs": { RPCAPICommand("api_get_psu", "get_psu"),
"api_summary": {"api": "summary"}, ],
"api_get_psu": {"api": "get_psu"}, ),
}, str(DataOptions.FAN_PSU): DataFunction(
}, "get_fan_psu",
"errors": { [
"cmd": "get_errors", RPCAPICommand("api_summary", "summary"),
"kwargs": { RPCAPICommand("api_get_psu", "get_psu"),
"api_summary": {"api": "summary"}, ],
"api_get_error_code": {"api": "get_error_code"}, ),
}, str(DataOptions.ERRORS): DataFunction(
}, "get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")]
"fault_light": { ),
"cmd": "get_fault_light", str(DataOptions.FAULT_LIGHT): DataFunction(
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, "get_fault_light",
}, [RPCAPICommand("api_get_miner_info", "get_miner_info")],
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}}, str(DataOptions.IS_MINING): DataFunction(
"uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}}, "is_mining", [RPCAPICommand("api_status", "status")]
"config": {"cmd": "get_config", "kwargs": {}}, ),
} str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_summary", "summary")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
@@ -529,32 +541,6 @@ class BTMiner(BaseMiner):
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors( async def get_errors(
self, api_summary: dict = None, api_get_error_code: dict = None self, api_summary: dict = None, api_get_error_code: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:

View File

@@ -23,32 +23,50 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
CGMINER_DATA_LOC = { CGMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
} str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class CGMiner(BaseMiner): class CGMiner(BaseMiner):
@@ -94,11 +112,9 @@ class CGMiner(BaseMiner):
return result return result
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners.backends.cgminer.CGMiner.restart_cgminer] to standardize."""
return await self.restart_cgminer() return await self.restart_cgminer()
async def restart_cgminer(self) -> bool: async def restart_cgminer(self) -> bool:
"""Restart cgminer hashing process."""
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"] commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands) commands = ";".join(commands)
ret = await self.send_ssh_command(commands) ret = await self.send_ssh_command(commands)
@@ -107,7 +123,6 @@ class CGMiner(BaseMiner):
return True return True
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot") ret = await self.send_ssh_command("reboot")
if ret is None: if ret is None:
@@ -328,32 +343,6 @@ class CGMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -23,37 +23,52 @@ from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner from pyasic.miners.backends import CGMiner
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
AVALON_DATA_LOC = { AVALON_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"api_version": {"api": "version"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [RPCAPICommand("api_version", "version")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HOSTNAME): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {}}, "get_hostname", [RPCAPICommand("api_version", "version")]
"wattage_limit": { ),
"cmd": "get_wattage_limit", str(DataOptions.HASHRATE): DataFunction(
"kwargs": {"api_stats": {"api": "stats"}}, "get_hashrate", [RPCAPICommand("api_devs", "devs")]
}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": { str(DataOptions.HASHBOARDS): DataFunction(
"cmd": "get_fault_light", "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "get_env_temp", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE_LIMIT): DataFunction(
} "get_wattage_limit", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class CGMinerAvalon(CGMiner): class CGMinerAvalon(CGMiner):
@@ -116,7 +131,7 @@ class CGMinerAvalon(CGMiner):
@staticmethod @staticmethod
def parse_stats(stats): def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats) _stats_items = re.findall(".+?\\[*?]", stats)
stats_items = [] stats_items = []
stats_dict = {} stats_dict = {}
for item in _stats_items: for item in _stats_items:
@@ -318,32 +333,6 @@ class CGMinerAvalon(CGMiner):
pass pass
return fans_data return fans_data
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -14,52 +14,74 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple
from pyasic import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
WebAPICommand,
)
from pyasic.web.epic import ePICWebAPI from pyasic.web.epic import ePICWebAPI
EPIC_DATA_LOC = { EPIC_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "network"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_network", "network")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [WebAPICommand("web_summary", "summary")]
"kwargs": {"web_summary": {"web": "summary"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction(
"hashboards": { "get_hostname", [WebAPICommand("web_summary", "summary")]
"cmd": "get_hashboards", ),
"kwargs": { str(DataOptions.HASHRATE): DataFunction(
"web_summary": {"web": "summary"}, "get_hashrate", [WebAPICommand("web_summary", "summary")]
"web_hashrate": {"web": "hashrate"}, ),
}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
}, "get_expected_hashrate", [WebAPICommand("web_summary", "summary")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.HASHBOARDS): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashboards",
"fans": {"cmd": "get_fans", "kwargs": {"web_summary": {"web": "summary"}}}, [
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, WebAPICommand("web_summary", "summary"),
"fault_light": { WebAPICommand("web_hashrate", "hashrate"),
"cmd": "get_fault_light", ],
"kwargs": {"web_summary": {"web": "summary"}}, ),
}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"pools": {"cmd": "get_pools", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.WATTAGE): DataFunction(
"is_mining": {"cmd": "is_mining", "kwargs": {}}, "get_wattage", [WebAPICommand("web_summary", "summary")]
"uptime": {"cmd": "get_uptime", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.FANS): DataFunction(
} "get_fans", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction(
"get_errors", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"get_fault_light", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class ePIC(BMMiner): class ePIC(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
# interfaces # interfaces
@@ -128,13 +150,13 @@ class ePIC(BMMiner):
pass pass
return False return False
async def get_mac(self, web_summary: dict = None) -> str: async def get_mac(self, web_network: dict = None) -> str:
if not web_summary: if not web_network:
web_summary = await self.web.network() web_network = await self.web.network()
if web_summary: if web_network:
try: try:
for network in web_summary: for network in web_network:
mac = web_summary[network]["mac_address"] mac = web_network[network]["mac_address"]
return mac return mac
except KeyError: except KeyError:
pass pass
@@ -267,32 +289,6 @@ class ePIC(BMMiner):
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_pools(self, web_summary: dict = None) -> List[dict]:
groups = []
if not web_summary:
try:
web_summary = await self.api.summary()
except APIError:
pass
if web_summary:
try:
pools = {}
for i, pool in enumerate(web_summary["StratumConfigs"]):
pools[f"pool_{i + 1}_url"] = (
pool["pool"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["login"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_uptime(self, web_summary: dict = None) -> Optional[int]: async def get_uptime(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if not web_summary:
web_summary = await self.web.summary() web_summary = await self.web.summary()
@@ -328,3 +324,33 @@ class ePIC(BMMiner):
except KeyError: except KeyError:
pass pass
return errors return errors
def fault_light_off(self) -> bool:
return False
def fault_light_on(self) -> bool:
return False
def get_api_ver(self, *args, **kwargs) -> Optional[str]:
pass
def get_config(self) -> MinerConfig:
return self.config
def get_env_temp(self, *args, **kwargs) -> Optional[float]:
pass
def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
pass
def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
pass
def get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
pass
def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -26,30 +26,52 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.bosminer import BOSMinerWebAPI from pyasic.web.bosminer import BOSMinerWebAPI
LUXMINER_DATA_LOC = { LUXMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"api_config": {"api": "config"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {}}, "get_mac", [RPCAPICommand("api_config", "config")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {}}, str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
"expected_hashrate": {"cmd": "get_expected_hashrate", "kwargs": {}}, str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {}}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.HASHRATE): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"fans": {"cmd": "get_fans", "kwargs": {}}, ),
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction(
} "get_wattage", [RPCAPICommand("api_power", "power")]
),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"get_fans", [RPCAPICommand("api_fans", "fans")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class LUXMiner(BaseMiner): class LUXMiner(BaseMiner):
@@ -85,7 +107,6 @@ class LUXMiner(BaseMiner):
return return
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -96,7 +117,6 @@ class LUXMiner(BaseMiner):
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
"""Sends command to turn off fault light on the miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -107,11 +127,9 @@ class LUXMiner(BaseMiner):
return False return False
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
"""Restart luxminer hashing process. Wraps [`restart_luxminer`][pyasic.miners.backends.luxminer.LUXMiner.restart_luxminer] to standardize."""
return await self.restart_luxminer() return await self.restart_luxminer()
async def restart_luxminer(self) -> bool: async def restart_luxminer(self) -> bool:
"""Restart luxminer hashing process."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -141,7 +159,6 @@ class LUXMiner(BaseMiner):
pass pass
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -303,56 +320,6 @@ class LUXMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
seen = []
groups = [{"quota": "0"}]
if api_pools.get("POOLS"):
for i, pool in enumerate(api_pools["POOLS"]):
if len(seen) == 0:
seen.append(pool["User"])
if not pool["User"] in seen:
# need to use get_config, as this will never read perfectly as there are some bad edge cases
groups = []
cfg = await self.get_config()
if cfg:
for group in cfg.pool_groups:
pools = {"quota": group.quota}
for _i, _pool in enumerate(group.pools):
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
"stratum+tcp://", ""
).replace("stratum2+tcp://", "")
pools[f"pool_{_i + 1}_user"] = _pool.username
groups.append(pools)
return groups
else:
groups[0][f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
groups[0][f"pool_{i + 1}_user"] = pool["User"]
else:
groups = []
cfg = await self.get_config()
if cfg:
for group in cfg.pool_groups:
pools = {"quota": group.quota}
for _i, _pool in enumerate(group.pools):
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
"stratum+tcp://", ""
).replace("stratum2+tcp://", "")
pools[f"pool_{_i + 1}_user"] = _pool.username
groups.append(pools)
return groups
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
pass pass

View File

@@ -19,35 +19,57 @@ from typing import Optional
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.vnish import VNishWebAPI from pyasic.web.vnish import VNishWebAPI
VNISH_DATA_LOC = { VNISH_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "summary"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_summary", "summary")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [WebAPICommand("web_summary", "summary")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, str(DataOptions.HOSTNAME): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, "get_hostname", [WebAPICommand("web_summary", "summary")]
"wattage_limit": { ),
"cmd": "get_wattage_limit", str(DataOptions.HASHRATE): DataFunction(
"kwargs": {"web_settings": {"web": "settings"}}, "get_hashrate", [WebAPICommand("web_summary", "summary")]
}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"config": {"cmd": "get_config", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction(
} "get_wattage", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"get_wattage_limit", [WebAPICommand("web_settings", "settings")]
),
str(DataOptions.FANS): DataFunction(
"get_fans", [WebAPICommand("web_summary", "summary")]
),
str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("is_mining"),
str(DataOptions.UPTIME): DataFunction("get_uptime"),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class VNish(BMMiner): class VNish(BMMiner):

View File

@@ -17,7 +17,9 @@ import asyncio
import ipaddress import ipaddress
import logging import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Optional, Tuple, TypeVar from dataclasses import dataclass, field, make_dataclass
from enum import Enum
from typing import List, Optional, Tuple, TypeVar, Union
import asyncssh import asyncssh
@@ -27,6 +29,70 @@ from pyasic.data.error_codes import MinerErrorData
from pyasic.logger import logger from pyasic.logger import logger
class DataOptions(Enum):
MAC = "mac"
MODEL = "model"
API_VERSION = "api_ver"
FW_VERSION = "fw_ver"
HOSTNAME = "hostname"
HASHRATE = "hashrate"
EXPECTED_HASHRATE = "expected_hashrate"
HASHBOARDS = "hashboards"
ENVIRONMENT_TEMP = "env_temp"
WATTAGE = "wattage"
WATTAGE_LIMIT = "wattage_limit"
FANS = "fans"
FAN_PSU = "fan_psu"
ERRORS = "errors"
FAULT_LIGHT = "fault_light"
IS_MINING = "is_mining"
UPTIME = "uptime"
CONFIG = "config"
def __str__(self):
return self.value
@dataclass
class RPCAPICommand:
name: str
cmd: str
@dataclass
class WebAPICommand:
name: str
cmd: str
@dataclass
class GRPCCommand(WebAPICommand):
name: str
cmd: str
@dataclass
class GraphQLCommand(WebAPICommand):
name: str
cmd: dict
@dataclass
class DataFunction:
cmd: str
kwargs: list[
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
] = field(default_factory=list)
DataLocations = make_dataclass(
"DataLocations",
[(enum_value.value, str) for enum_value in DataOptions],
)
# add default value with
# [(enum_value.value, str, , DataFunction(enum_value.value)) for enum_value in DataOptions],
class BaseMiner(ABC): class BaseMiner(ABC):
def __init__(self, ip: str, *args, **kwargs) -> None: def __init__(self, ip: str, *args, **kwargs) -> None:
# interfaces # interfaces
@@ -46,7 +112,7 @@ class BaseMiner(ABC):
self.expected_chips = 0 self.expected_chips = 0
self.fan_count = 2 self.fan_count = 2
# data gathering locations # data gathering locations
self.data_locations = None self.data_locations: DataLocations = None
# autotuning/shutdown support # autotuning/shutdown support
self.supports_autotuning = False self.supports_autotuning = False
self.supports_shutdown = False self.supports_shutdown = False
@@ -359,15 +425,6 @@ class BaseMiner(ABC):
""" """
pass pass
@abstractmethod
async def get_pools(self, *args, **kwargs) -> List[dict]:
"""Get pool information from the miner.
Returns:
Pool groups and quotas in a list of dicts.
"""
pass
@abstractmethod @abstractmethod
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
"""Get a list of the errors the miner is experiencing. """Get a list of the errors the miner is experiencing.
@@ -414,28 +471,33 @@ class BaseMiner(ABC):
pass pass
async def _get_data( async def _get_data(
self, allow_warning: bool, include: list = None, exclude: list = None self,
allow_warning: bool,
include: List[Union[str, DataOptions]] = None,
exclude: List[Union[str, DataOptions]] = None,
) -> dict: ) -> dict:
if include is None: if include is not None:
include = [str(i) for i in include]
else:
# everything # everything
include = list(self.data_locations.keys()) include = [str(enum_value.value) for enum_value in DataOptions]
if exclude is not None: if exclude is not None:
for item in exclude: for item in exclude:
if item in include: if str(item) in include:
include.remove(item) include.remove(str(item))
api_multicommand = set() api_multicommand = set()
web_multicommand = [] web_multicommand = []
for data_name in include: for data_name in include:
try: try:
fn_args = self.data_locations[data_name]["kwargs"] fn_args = getattr(self.data_locations, data_name).kwargs
for arg_name in fn_args: for arg in fn_args:
if fn_args[arg_name].get("api"): if isinstance(arg, RPCAPICommand):
api_multicommand.add(fn_args[arg_name]["api"]) api_multicommand.add(arg.cmd)
if fn_args[arg_name].get("web"): if isinstance(arg, WebAPICommand):
if not fn_args[arg_name]["web"] in web_multicommand: if arg.cmd not in web_multicommand:
web_multicommand.append(fn_args[arg_name]["web"]) web_multicommand.append(arg.cmd)
except KeyError as e: except KeyError as e:
logger.error(e, data_name) logger.error(e, data_name)
continue continue
@@ -465,37 +527,36 @@ class BaseMiner(ABC):
for data_name in include: for data_name in include:
try: try:
fn_args = self.data_locations[data_name]["kwargs"] fn_args = getattr(self.data_locations, data_name).kwargs
args_to_send = {k: None for k in fn_args} args_to_send = {k.name: None for k in fn_args}
for arg_name in fn_args: for arg in fn_args:
try: try:
if fn_args[arg_name].get("api"): if isinstance(arg, RPCAPICommand):
if api_command_data.get("multicommand"): if api_command_data.get("multicommand"):
args_to_send[arg_name] = api_command_data[ args_to_send[arg.name] = api_command_data[arg.cmd][0]
fn_args[arg_name]["api"]
][0]
else: else:
args_to_send[arg_name] = api_command_data args_to_send[arg.name] = api_command_data
if fn_args[arg_name].get("web"): if isinstance(arg, WebAPICommand):
if web_command_data is not None: if web_command_data is not None:
if web_command_data.get("multicommand"): if web_command_data.get("multicommand"):
args_to_send[arg_name] = web_command_data[ args_to_send[arg.name] = web_command_data[arg.cmd]
fn_args[arg_name]["web"]
]
else: else:
if not web_command_data == {"multicommand": False}: if not web_command_data == {"multicommand": False}:
args_to_send[arg_name] = web_command_data args_to_send[arg.name] = web_command_data
except LookupError: except LookupError:
args_to_send[arg_name] = None args_to_send[arg.name] = None
except LookupError: except LookupError:
continue continue
function = getattr(self, self.data_locations[data_name]["cmd"]) function = getattr(self, getattr(self.data_locations, data_name).cmd)
miner_data[data_name] = await function(**args_to_send) miner_data[data_name] = await function(**args_to_send)
return miner_data return miner_data
async def get_data( async def get_data(
self, allow_warning: bool = False, include: list = None, exclude: list = None self,
allow_warning: bool = False,
include: List[Union[str, DataOptions]] = None,
exclude: List[Union[str, DataOptions]] = None,
) -> MinerData: ) -> MinerData:
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData]. """Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].

View File

@@ -267,32 +267,6 @@ class CGMinerA10X(CGMiner, A10X):
return fans return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors( async def get_errors(
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality

View File

@@ -246,32 +246,6 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
return fans return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors( async def get_errors(
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality

View File

@@ -113,32 +113,6 @@ class UnknownMiner(BaseMiner):
async def get_fw_ver(self) -> Optional[str]: async def get_fw_ver(self) -> Optional[str]:
return None return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
if not api_pools:
try:
api_pools = await self.api.pools()
except APIError:
pass
if api_pools:
try:
pools = {}
for i, pool in enumerate(api_pools["POOLS"]):
pools[f"pool_{i + 1}_url"] = (
pool["URL"]
.replace("stratum+tcp://", "")
.replace("stratum2+tcp://", "")
)
pools[f"pool_{i + 1}_user"] = pool["User"]
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
groups.append(pools)
except KeyError:
pass
return groups
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
@@ -155,6 +129,6 @@ class UnknownMiner(BaseMiner):
return None return None
async def get_data( async def get_data(
self, allow_warning: bool = False, data_to_get: list = None self, allow_warning: bool = False, data_to_get: list = None, **kwargs
) -> MinerData: ) -> MinerData:
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))

View File

@@ -123,9 +123,8 @@ class MinerNetwork:
# clear cached miners # clear cached miners
miner_factory.clear_cached_miners() miner_factory.clear_cached_miners()
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
miners = await asyncio.gather( miners = await asyncio.gather(
*[self.ping_and_get_miner(host, limit) for host in self.hosts] *[self.ping_and_get_miner(host) for host in self.hosts]
) )
# remove all None from the miner list # remove all None from the miner list
@@ -148,12 +147,8 @@ class MinerNetwork:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
# create a list of scan tasks # create a list of scan tasks
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
miners = asyncio.as_completed( miners = asyncio.as_completed(
[ [loop.create_task(self.ping_and_get_miner(host)) for host in self.hosts]
loop.create_task(self.ping_and_get_miner(host, limit))
for host in self.hosts
]
) )
for miner in miners: for miner in miners:
try: try:
@@ -162,21 +157,16 @@ class MinerNetwork:
yield None yield None
@staticmethod @staticmethod
async def ping_and_get_miner( async def ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
ip: ipaddress.ip_address, semaphore: asyncio.Semaphore try:
) -> Union[None, AnyMiner]: return await ping_and_get_miner(ip)
async with semaphore: except ConnectionRefusedError:
try: tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]]
return await ping_and_get_miner(ip) for miner in asyncio.as_completed(tasks):
except ConnectionRefusedError: try:
tasks = [ return await miner
ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889] except ConnectionRefusedError:
] pass
for miner in asyncio.as_completed(tasks):
try:
return await miner
except ConnectionRefusedError:
pass
async def ping_and_get_miner( async def ping_and_get_miner(

View File

@@ -24,7 +24,6 @@ from httpx import AsyncHTTPTransport
_settings = { # defaults _settings = { # defaults
"network_ping_retries": 1, "network_ping_retries": 1,
"network_ping_timeout": 3, "network_ping_timeout": 3,
"network_scan_threads": 300,
"factory_get_retries": 1, "factory_get_retries": 1,
"factory_get_timeout": 3, "factory_get_timeout": 3,
"get_data_retries": 1, "get_data_retries": 1,

View File

@@ -10,10 +10,10 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
httpx = "^0.26.0" httpx = "^0.26.0"
asyncssh = "^2.14.1" asyncssh = "^2.14.2"
grpc-requests = "^0.1.12" grpc-requests = "^0.1.13"
passlib = "^1.7.4" passlib = "^1.7.4"
pyaml = "^23.9.7" pyaml = "^23.12.0"
toml = "^0.10.2" toml = "^0.10.2"
betterproto = "2.0.0b6" betterproto = "2.0.0b6"

View File

@@ -18,6 +18,7 @@ import inspect
import sys import sys
import unittest import unittest
import warnings import warnings
from dataclasses import asdict
from pyasic.miners.backends import CGMiner # noqa from pyasic.miners.backends import CGMiner # noqa
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
@@ -57,7 +58,6 @@ class MinersTest(unittest.TestCase):
"mac", "mac",
"model", "model",
"expected_hashrate", "expected_hashrate",
"pools",
"uptime", "uptime",
"wattage", "wattage",
"wattage_limit", "wattage_limit",
@@ -72,7 +72,9 @@ class MinersTest(unittest.TestCase):
miner_api=miner_api, miner_api=miner_api,
): ):
miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1")
miner_keys = sorted(list(miner.data_locations.keys())) miner_keys = sorted(
[str(k) for k in asdict(miner.data_locations).keys()]
)
self.assertEqual(miner_keys, keys) self.assertEqual(miner_keys, keys)