refactor: make web handlers much more consistent across types, remove graphql, and make luci and grpc the dedicated web apis for BOSer and BOSMiner respectively.

This commit is contained in:
UpstreamData
2024-01-25 13:50:04 -07:00
parent b328a27f04
commit aa1d7c1b6f
13 changed files with 356 additions and 581 deletions

View File

@@ -29,7 +29,6 @@ from pyasic.miners.base import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
GRPCCommand,
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
@@ -275,7 +274,7 @@ class BOSMiner(BaseMiner):
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
if web_net_conf is None: if web_net_conf is None:
try: try:
web_net_conf = await self.web.luci.get_net_conf() web_net_conf = await self.web.get_net_conf()
except APIError: except APIError:
pass pass
@@ -314,7 +313,7 @@ class BOSMiner(BaseMiner):
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]: async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
if web_bos_info is None: if web_bos_info is None:
try: try:
web_bos_info = await self.web.luci.get_bos_info() web_bos_info = await self.web.get_bos_info()
except APIError: except APIError:
return None return None
@@ -571,19 +570,19 @@ BOSER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", "_get_mac",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[GRPCCommand("api_version", "get_api_version")], [RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", "_get_hostname",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
@@ -591,27 +590,27 @@ BOSER_DATA_LOC = DataLocations(
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[GRPCCommand("grpc_hashboards", "get_hashboards")], [WebAPICommand("grpc_hashboards", "get_hashboards")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[GRPCCommand("grpc_miner_stats", "get_miner_stats")], [WebAPICommand("grpc_miner_stats", "get_miner_stats")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", "_get_wattage_limit",
[ [
GRPCCommand( WebAPICommand(
"grpc_active_performance_mode", "get_active_performance_mode" "grpc_active_performance_mode", "get_active_performance_mode"
) )
], ],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[GRPCCommand("grpc_cooling_state", "get_cooling_state")], [WebAPICommand("grpc_cooling_state", "get_cooling_state")],
), ),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
@@ -619,7 +618,7 @@ BOSER_DATA_LOC = DataLocations(
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], [WebAPICommand("grpc_locate_device_status", "get_locate_device_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", "_is_mining",
@@ -647,13 +646,13 @@ class BOSer(BaseMiner):
supports_shutdown = True supports_shutdown = True
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(True) resp = await self.web.set_locate_device_status(True)
if resp.get("enabled", False): if resp.get("enabled", False):
return True return True
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(False) resp = await self.web.set_locate_device_status(False)
if resp == {}: if resp == {}:
return True return True
return False return False
@@ -662,37 +661,37 @@ class BOSer(BaseMiner):
return await self.restart_boser() return await self.restart_boser()
async def restart_boser(self) -> bool: async def restart_boser(self) -> bool:
await self.web.grpc.restart() await self.web.restart()
return True return True
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
await self.web.grpc.pause_mining() await self.web.pause_mining()
except APIError: except APIError:
return False return False
return True return True
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
await self.web.grpc.resume_mining() await self.web.resume_mining()
except APIError: except APIError:
return False return False
return True return True
async def reboot(self) -> bool: async def reboot(self) -> bool:
ret = await self.web.grpc.reboot() ret = await self.web.reboot()
if ret == {}: if ret == {}:
return True return True
return False return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
grpc_conf = await self.web.grpc.get_miner_configuration() grpc_conf = await self.web.get_miner_configuration()
return MinerConfig.from_boser(grpc_conf) return MinerConfig.from_boser(grpc_conf)
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
result = await self.web.grpc.set_power_target(wattage) result = await self.web.set_power_target(wattage)
except APIError: except APIError:
return False return False
@@ -710,7 +709,7 @@ class BOSer(BaseMiner):
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -740,7 +739,7 @@ class BOSer(BaseMiner):
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -763,7 +762,7 @@ class BOSer(BaseMiner):
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]: async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -791,7 +790,7 @@ class BOSer(BaseMiner):
) -> Optional[float]: ) -> Optional[float]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -809,7 +808,7 @@ class BOSer(BaseMiner):
if grpc_hashboards is None: if grpc_hashboards is None:
try: try:
grpc_hashboards = await self.web.grpc.get_hashboards() grpc_hashboards = await self.web.get_hashboards()
except APIError: except APIError:
pass pass
@@ -840,7 +839,7 @@ class BOSer(BaseMiner):
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
if grpc_miner_stats is None: if grpc_miner_stats is None:
try: try:
grpc_miner_stats = self.web.grpc.get_miner_stats() grpc_miner_stats = self.web.get_miner_stats()
except APIError: except APIError:
pass pass
@@ -855,9 +854,7 @@ class BOSer(BaseMiner):
) -> Optional[int]: ) -> Optional[int]:
if grpc_active_performance_mode is None: if grpc_active_performance_mode is None:
try: try:
grpc_active_performance_mode = ( grpc_active_performance_mode = self.web.get_active_performance_mode()
self.web.grpc.get_active_performance_mode()
)
except APIError: except APIError:
pass pass
@@ -872,7 +869,7 @@ class BOSer(BaseMiner):
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None: if grpc_cooling_state is None:
try: try:
grpc_cooling_state = self.web.grpc.get_cooling_state() grpc_cooling_state = self.web.get_cooling_state()
except APIError: except APIError:
pass pass
@@ -922,9 +919,7 @@ class BOSer(BaseMiner):
if grpc_locate_device_status is None: if grpc_locate_device_status is None:
try: try:
grpc_locate_device_status = ( grpc_locate_device_status = await self.web.get_locate_device_status()
await self.web.grpc.get_locate_device_status()
)
except APIError: except APIError:
pass pass

View File

@@ -76,18 +76,12 @@ class GRPCCommand(WebAPICommand):
cmd: str cmd: str
@dataclass
class GraphQLCommand(WebAPICommand):
name: str
cmd: dict
@dataclass @dataclass
class DataFunction: class DataFunction:
cmd: str cmd: str
kwargs: List[ kwargs: List[Union[RPCAPICommand, WebAPICommand, GRPCCommand]] = field(
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand] default_factory=list
] = field(default_factory=list) )
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self return self

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import warnings import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Union from typing import Any
from pyasic.errors import APIWarning from pyasic.errors import APIWarning
@@ -23,11 +25,13 @@ from pyasic.errors import APIWarning
class BaseWebAPI(ABC): class BaseWebAPI(ABC):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
# ip address of the miner # ip address of the miner
self.ip = ip # ipaddress.ip_address(ip) self.ip = ip
self.username = "root" self.username = None
self.pwd = None self.pwd = None
self.port = 80 self.port = 80
self.token = None
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is BaseWebAPI: if cls is BaseWebAPI:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated") raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
@@ -39,10 +43,11 @@ class BaseWebAPI(ABC):
@abstractmethod @abstractmethod
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
pass pass

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import json import json
from typing import Union from typing import Any
import httpx import httpx
@@ -26,14 +28,16 @@ from pyasic.web import BaseWebAPI
class AntminerModernWebAPI(BaseWebAPI): class AntminerModernWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.username = "root"
self.pwd = settings.get("default_antminer_web_password", "root") self.pwd = settings.get("default_antminer_web_password", "root")
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd) auth = httpx.DigestAuth(self.username, self.pwd)
@@ -44,9 +48,9 @@ class AntminerModernWebAPI(BaseWebAPI):
if parameters: if parameters:
data = await client.post( data = await client.post(
url, url,
data=json.dumps(parameters),
auth=auth, auth=auth,
timeout=settings.get("api_function_timeout", 3), # noqa timeout=settings.get("api_function_timeout", 3),
json=parameters,
) )
else: else:
data = await client.get(url, auth=auth) data = await client.get(url, auth=auth)
@@ -76,7 +80,9 @@ class AntminerModernWebAPI(BaseWebAPI):
data["multicommand"] = True data["multicommand"] = True
return data return data
async def _handle_multicommand(self, client: httpx.AsyncClient, command: str): async def _handle_multicommand(
self, client: httpx.AsyncClient, command: str
) -> dict:
auth = httpx.DigestAuth(self.username, self.pwd) auth = httpx.DigestAuth(self.username, self.pwd)
try: try:
@@ -142,14 +148,16 @@ class AntminerModernWebAPI(BaseWebAPI):
class AntminerOldWebAPI(BaseWebAPI): class AntminerOldWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.username = "root"
self.pwd = settings.get("default_antminer_web_password", "root") self.pwd = settings.get("default_antminer_web_password", "root")
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd) auth = httpx.DigestAuth(self.username, self.pwd)

View File

@@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import json import json
import warnings import warnings
from typing import Any, List, Union from typing import Any
import httpx import httpx
@@ -31,9 +33,9 @@ class FluxWebAPI(BaseWebAPI):
self.username = "admin" self.username = "admin"
self.pwd = settings.get("default_auradine_web_password", "admin") self.pwd = settings.get("default_auradine_web_password", "admin")
self.port = 8080 self.port = 8080
self.jwt = None self.token = None
async def auth(self): async def auth(self) -> str | None:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
auth = await client.post( auth = await client.post(
@@ -49,20 +51,24 @@ class FluxWebAPI(BaseWebAPI):
else: else:
json_auth = auth.json() json_auth = auth.json()
try: try:
self.jwt = json_auth["Token"][0]["Token"] self.token = json_auth["Token"][0]["Token"]
except LookupError: except LookupError:
return None return None
return self.jwt return self.token
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
post: bool = False,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
privileged: bool = False,
**parameters: Any, **parameters: Any,
) -> dict: ) -> dict:
if self.jwt is None: post = privileged or not parameters == {}
if not parameters == {}:
parameters["command"] = command
if self.token is None:
await self.auth() await self.auth()
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
for i in range(settings.get("get_data_retries", 1)): for i in range(settings.get("get_data_retries", 1)):
@@ -70,20 +76,14 @@ class FluxWebAPI(BaseWebAPI):
if post: if post:
response = await client.post( response = await client.post(
f"http://{self.ip}:{self.port}/{command}", f"http://{self.ip}:{self.port}/{command}",
headers={"Token": self.jwt}, headers={"Token": self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) json=parameters,
elif parameters:
response = await client.post(
f"http://{self.ip}:{self.port}/{command}",
headers={"Token": self.jwt},
timeout=settings.get("api_function_timeout", 5),
json={"command": command, **parameters},
) )
else: else:
response = await client.get( response = await client.get(
f"http://{self.ip}:{self.port}/{command}", f"http://{self.ip}:{self.port}/{command}",
headers={"Token": self.jwt}, headers={"Token": self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )
json_data = response.json() json_data = response.json()
@@ -95,9 +95,7 @@ class FluxWebAPI(BaseWebAPI):
await self.auth() await self.auth()
continue continue
return json_data return json_data
except httpx.HTTPError: except (httpx.HTTPError, json.JSONDecodeError):
pass
except json.JSONDecodeError:
pass pass
async def multicommand( async def multicommand(
@@ -166,97 +164,97 @@ class FluxWebAPI(BaseWebAPI):
return False, data["STATUS"][0]["Msg"] return False, data["STATUS"][0]["Msg"]
return True, None return True, None
async def factory_reset(self): async def factory_reset(self) -> dict:
return await self.send_command("factory-reset", post=True) return await self.send_command("factory-reset", privileged=True)
async def get_fan(self): async def get_fan(self) -> dict:
return await self.send_command("fan") return await self.send_command("fan")
async def set_fan(self, fan: int, speed_pct: int): async def set_fan(self, fan: int, speed_pct: int) -> dict:
return await self.send_command("fan", index=fan, percentage=speed_pct) return await self.send_command("fan", index=fan, percentage=speed_pct)
async def firmware_upgrade(self, url: str = None, version: str = "latest"): async def firmware_upgrade(self, url: str = None, version: str = "latest") -> dict:
if url is not None: if url is not None:
return await self.send_command("firmware-upgrade", url=url) return await self.send_command("firmware-upgrade", url=url)
return await self.send_command("firmware-upgrade", version=version) return await self.send_command("firmware-upgrade", version=version)
async def get_frequency(self): async def get_frequency(self) -> dict:
return await self.send_command("frequency") return await self.send_command("frequency")
async def set_frequency(self, board: int, frequency: float): async def set_frequency(self, board: int, frequency: float) -> dict:
return await self.send_command("frequency", board=board, frequency=frequency) return await self.send_command("frequency", board=board, frequency=frequency)
async def ipreport(self): async def ipreport(self) -> dict:
return await self.send_command("ipreport") return await self.send_command("ipreport")
async def get_led(self): async def get_led(self) -> dict:
return await self.send_command("led") return await self.send_command("led")
async def set_led(self, code: int): async def set_led(self, code: int) -> dict:
return await self.send_command("led", code=code) return await self.send_command("led", code=code)
async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str): async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str) -> dict:
return await self.send_command( return await self.send_command(
"led", code=code, led1=led_1, led2=led_2, msg=msg "led", code=code, led1=led_1, led2=led_2, msg=msg
) )
async def get_mode(self): async def get_mode(self) -> dict:
return await self.send_command("mode") return await self.send_command("mode")
async def set_mode(self, **kwargs): async def set_mode(self, **kwargs) -> dict:
return await self.send_command("mode", **kwargs) return await self.send_command("mode", **kwargs)
async def get_network(self): async def get_network(self) -> dict:
return await self.send_command("network") return await self.send_command("network")
async def set_network(self, **kwargs): async def set_network(self, **kwargs) -> dict:
return await self.send_command("network", **kwargs) return await self.send_command("network", **kwargs)
async def password(self, password: str): async def password(self, password: str) -> dict:
res = await self.send_command( res = await self.send_command(
"password", user=self.username, old=self.pwd, new=password "password", user=self.username, old=self.pwd, new=password
) )
self.pwd = password self.pwd = password
return res return res
async def get_psu(self): async def get_psu(self) -> dict:
return await self.send_command("psu") return await self.send_command("psu")
async def set_psu(self, voltage: float): async def set_psu(self, voltage: float) -> dict:
return await self.send_command("psu", voltage=voltage) return await self.send_command("psu", voltage=voltage)
async def get_register(self): async def get_register(self) -> dict:
return await self.send_command("register") return await self.send_command("register")
async def set_register(self, company: str): async def set_register(self, company: str) -> dict:
return await self.send_command("register", parameter=company) return await self.send_command("register", parameter=company)
async def reboot(self): async def reboot(self) -> dict:
return await self.send_command("restart", post=True) return await self.send_command("restart", privileged=True)
async def restart_gcminer(self): async def restart_gcminer(self) -> dict:
return await self.send_command("restart", parameter="gcminer") return await self.send_command("restart", parameter="gcminer")
async def restart_api_server(self): async def restart_api_server(self) -> dict:
return await self.send_command("restart", parameter="api-server") return await self.send_command("restart", parameter="api-server")
async def temperature(self): async def temperature(self) -> dict:
return await self.send_command("temperature") return await self.send_command("temperature")
async def timedate(self, ntp: str, timezone: str): async def timedate(self, ntp: str, timezone: str) -> dict:
return await self.send_command("timedate", ntp=ntp, timezone=timezone) return await self.send_command("timedate", ntp=ntp, timezone=timezone)
async def token(self): async def token(self) -> dict:
return await self.send_command("token", user=self.username, password=self.pwd) return await self.send_command("token", user=self.username, password=self.pwd)
async def update_pools(self, pools: List[dict]): async def update_pools(self, pools: list[dict]) -> dict:
return await self.send_command("updatepools", pools=pools) return await self.send_command("updatepools", pools=pools)
async def voltage(self): async def voltage(self) -> dict:
return await self.send_command("voltage") return await self.send_command("voltage")
async def get_ztp(self): async def get_ztp(self) -> dict:
return await self.send_command("ztp") return await self.send_command("ztp")
async def set_ztp(self, enable: bool): async def set_ztp(self, enable: bool) -> dict:
return await self.send_command("ztp", parameter="on" if enable else "off") return await self.send_command("ztp", parameter="on" if enable else "off")

View File

@@ -1,149 +1,2 @@
# ------------------------------------------------------------------------------ from .boser import BOSerWebAPI
# Copyright 2022 Upstream Data Inc - from .bosminer import BOSMinerWebAPI
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import asyncio
from typing import Union
from pyasic import settings
from pyasic.errors import APIError
from pyasic.web import BaseWebAPI
from .graphql import BOSerGraphQLAPI
from .grpc import BOSerGRPCAPI
from .luci import BOSMinerLuCIAPI
class BOSMinerWebAPI(BaseWebAPI):
def __init__(self, ip: str) -> None:
self.luci = BOSMinerLuCIAPI(
ip, settings.get("default_bosminer_password", "root")
)
self._pwd = settings.get("default_bosminer_password", "root")
self._port = 80
super().__init__(ip)
@property
def pwd(self):
return self._pwd
@pwd.setter
def pwd(self, other: str):
self._pwd = other
self.luci.pwd = other
@property
def port(self):
return self._port
@port.setter
def port(self, other: str):
self._port = other
self.luci.port = other
async def send_command(
self,
command: Union[str, dict],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
return await self.luci.send_command(command)
async def multicommand(
self, *commands: Union[dict, str], allow_warning: bool = True
) -> dict:
return await self.luci.multicommand(*commands)
class BOSerWebAPI(BOSMinerWebAPI):
def __init__(self, ip: str) -> None:
self.gql = BOSerGraphQLAPI(
ip, settings.get("default_bosminer_password", "root")
)
self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root"))
self._port = 80
super().__init__(ip)
@property
def pwd(self):
return self._pwd
@pwd.setter
def pwd(self, other: str):
self._pwd = other
self.luci.pwd = other
self.gql.pwd = other
self.grpc.pwd = other
@property
def port(self):
return self._port
@port.setter
def port(self, other: str):
self._port = other
self.luci.port = other
self.gql.port = other
async def send_command(
self,
command: Union[str, dict],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
command_type = self.select_command_type(command)
if command_type == "gql":
return await self.gql.send_command(command)
elif command_type == "grpc":
try:
return await getattr(self.grpc, command.replace("grpc_", ""))()
except AttributeError:
raise APIError(f"No gRPC command found for command: {command}")
elif command_type == "luci":
return await self.luci.send_command(command)
@staticmethod
def select_command_type(command: Union[str, dict]) -> str:
if isinstance(command, dict):
return "gql"
else:
return "grpc"
async def multicommand(
self, *commands: Union[dict, str], allow_warning: bool = True
) -> dict:
cmd_types = {"grpc": [], "gql": []}
for cmd in commands:
cmd_types[self.select_command_type(cmd)].append(cmd)
async def no_op():
return {}
if len(cmd_types["grpc"]) > 0:
grpc_data_t = asyncio.create_task(
self.grpc.multicommand(*cmd_types["grpc"])
)
else:
grpc_data_t = asyncio.create_task(no_op())
if len(cmd_types["gql"]) > 0:
gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"]))
else:
gql_data_t = asyncio.create_task(no_op())
await asyncio.gather(grpc_data_t, gql_data_t)
data = dict(**grpc_data_t.result(), **gql_data_t.result())
return data

View File

@@ -13,15 +13,19 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
from typing import Any
from betterproto import Message
from grpclib import GRPCError, Status from grpclib import GRPCError, Status
from grpclib.client import Channel from grpclib.client import Channel
from pyasic import settings
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.web import BaseWebAPI
from .proto.braiins.bos import * from .proto.braiins.bos import *
from .proto.braiins.bos.v1 import * from .proto.braiins.bos.v1 import *
@@ -41,14 +45,13 @@ class BOSMinerGRPCStub(
pass pass
class BOSerGRPCAPI: class BOSerWebAPI(BaseWebAPI):
def __init__(self, ip: str, pwd: str): def __init__(self, ip: str) -> None:
self.ip = ip super().__init__(ip)
self.username = "root" self.username = "root"
self.pwd = pwd self.pwd = settings.get("default_bosminer_password", "root")
self.port = 50051 self.port = 50051
self._auth = None self._auth_time = None
self._auth_time = datetime.now()
@property @property
def commands(self) -> list: def commands(self) -> list:
@@ -85,13 +88,15 @@ class BOSerGRPCAPI:
async def send_command( async def send_command(
self, self,
command: str, command: str | bytes,
message: Message = None,
ignore_errors: bool = False, ignore_errors: bool = False,
auth: bool = True, allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
message: betterproto.Message = parameters["message"]
metadata = [] metadata = []
if auth: if privileged:
metadata.append(("authorization", await self.auth())) metadata.append(("authorization", await self.auth()))
try: try:
async with Channel(self.ip, self.port) as c: async with Channel(self.ip, self.port) as c:
@@ -111,15 +116,15 @@ class BOSerGRPCAPI:
except GRPCError as e: except GRPCError as e:
raise APIError(f"gRPC command failed - {endpoint}") from e raise APIError(f"gRPC command failed - {endpoint}") from e
async def auth(self): async def auth(self) -> str | None:
if self._auth is not None and self._auth_time - datetime.now() < timedelta( if self.token is not None and self._auth_time - datetime.now() < timedelta(
seconds=3540 seconds=3540
): ):
return self._auth return self.token
await self._get_auth() await self._get_auth()
return self._auth return self.token
async def _get_auth(self): async def _get_auth(self) -> str:
async with Channel(self.ip, self.port) as c: async with Channel(self.ip, self.port) as c:
req = LoginRequest(username=self.username, password=self.pwd) req = LoginRequest(username=self.username, password=self.pwd)
async with c.request( async with c.request(
@@ -132,74 +137,79 @@ class BOSerGRPCAPI:
await stream.recv_initial_metadata() await stream.recv_initial_metadata()
auth = stream.initial_metadata.get("authorization") auth = stream.initial_metadata.get("authorization")
if auth is not None: if auth is not None:
self._auth = auth self.token = auth
self._auth_time = datetime.now() self._auth_time = datetime.now()
return self._auth return self.token
async def get_api_version(self): async def get_api_version(self) -> dict:
return await self.send_command( return await self.send_command(
"get_api_version", ApiVersionRequest(), auth=False "get_api_version", message=ApiVersionRequest(), privileged=False
) )
async def start(self): async def start(self) -> dict:
return await self.send_command("start", StartRequest()) return await self.send_command("start", message=StartRequest())
async def stop(self): async def stop(self) -> dict:
return await self.send_command("stop", StopRequest()) return await self.send_command("stop", message=StopRequest())
async def pause_mining(self): async def pause_mining(self) -> dict:
return await self.send_command("pause_mining", PauseMiningRequest()) return await self.send_command("pause_mining", message=PauseMiningRequest())
async def resume_mining(self): async def resume_mining(self) -> dict:
return await self.send_command("resume_mining", ResumeMiningRequest()) return await self.send_command("resume_mining", message=ResumeMiningRequest())
async def restart(self): async def restart(self) -> dict:
return await self.send_command("restart", RestartRequest()) return await self.send_command("restart", message=RestartRequest())
async def reboot(self): async def reboot(self) -> dict:
return await self.send_command("reboot", RebootRequest()) return await self.send_command("reboot", message=RebootRequest())
async def set_locate_device_status(self, enable: bool): async def set_locate_device_status(self, enable: bool) -> dict:
return await self.send_command( return await self.send_command(
"set_locate_device_status", SetLocateDeviceStatusRequest(enable=enable) "set_locate_device_status",
message=SetLocateDeviceStatusRequest(enable=enable),
) )
async def get_locate_device_status(self): async def get_locate_device_status(self) -> dict:
return await self.send_command( return await self.send_command(
"get_locate_device_status", GetLocateDeviceStatusRequest() "get_locate_device_status", message=GetLocateDeviceStatusRequest()
) )
async def set_password(self, password: str = None): async def set_password(self, password: str = None) -> dict:
return await self.send_command( return await self.send_command(
"set_password", SetPasswordRequest(password=password) "set_password", message=SetPasswordRequest(password=password)
) )
async def get_cooling_state(self): async def get_cooling_state(self) -> dict:
return await self.send_command("get_cooling_state", GetCoolingStateRequest()) return await self.send_command(
"get_cooling_state", message=GetCoolingStateRequest()
)
async def set_immersion_mode( async def set_immersion_mode(
self, self,
enable: bool, enable: bool,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_immersion_mode", "set_immersion_mode",
SetImmersionModeRequest( message=SetImmersionModeRequest(
enable_immersion_mode=enable, save_action=save_action enable_immersion_mode=enable, save_action=save_action
), ),
) )
async def get_tuner_state(self): async def get_tuner_state(self) -> dict:
return await self.send_command("get_tuner_state", GetTunerStateRequest())
async def list_target_profiles(self):
return await self.send_command( return await self.send_command(
"list_target_profiles", ListTargetProfilesRequest() "get_tuner_state", message=GetTunerStateRequest()
)
async def list_target_profiles(self) -> dict:
return await self.send_command(
"list_target_profiles", message=ListTargetProfilesRequest()
) )
async def set_default_power_target( async def set_default_power_target(
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_default_power_target", "set_default_power_target",
message=SetDefaultPowerTargetRequest(save_action=save_action), message=SetDefaultPowerTargetRequest(save_action=save_action),
@@ -209,10 +219,10 @@ class BOSerGRPCAPI:
self, self,
power_target: int, power_target: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_power_target", "set_power_target",
SetPowerTargetRequest( message=SetPowerTargetRequest(
power_target=Power(watt=power_target), save_action=save_action power_target=Power(watt=power_target), save_action=save_action
), ),
) )
@@ -221,7 +231,7 @@ class BOSerGRPCAPI:
self, self,
power_target_increment: int, power_target_increment: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"increment_power_target", "increment_power_target",
message=IncrementPowerTargetRequest( message=IncrementPowerTargetRequest(
@@ -234,7 +244,7 @@ class BOSerGRPCAPI:
self, self,
power_target_decrement: int, power_target_decrement: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"decrement_power_target", "decrement_power_target",
message=DecrementPowerTargetRequest( message=DecrementPowerTargetRequest(
@@ -245,7 +255,7 @@ class BOSerGRPCAPI:
async def set_default_hashrate_target( async def set_default_hashrate_target(
self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_default_hashrate_target", "set_default_hashrate_target",
message=SetDefaultHashrateTargetRequest(save_action=save_action), message=SetDefaultHashrateTargetRequest(save_action=save_action),
@@ -255,10 +265,10 @@ class BOSerGRPCAPI:
self, self,
hashrate_target: float, hashrate_target: float,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_hashrate_target", "set_hashrate_target",
SetHashrateTargetRequest( message=SetHashrateTargetRequest(
hashrate_target=TeraHashrate(terahash_per_second=hashrate_target), hashrate_target=TeraHashrate(terahash_per_second=hashrate_target),
save_action=save_action, save_action=save_action,
), ),
@@ -268,10 +278,10 @@ class BOSerGRPCAPI:
self, self,
hashrate_target_increment: int, hashrate_target_increment: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"increment_hashrate_target", "increment_hashrate_target",
IncrementHashrateTargetRequest( message=IncrementHashrateTargetRequest(
hashrate_target_increment=TeraHashrate( hashrate_target_increment=TeraHashrate(
terahash_per_second=hashrate_target_increment terahash_per_second=hashrate_target_increment
), ),
@@ -283,10 +293,10 @@ class BOSerGRPCAPI:
self, self,
hashrate_target_decrement: int, hashrate_target_decrement: int,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"decrement_hashrate_target", "decrement_hashrate_target",
DecrementHashrateTargetRequest( message=DecrementHashrateTargetRequest(
hashrate_target_decrement=TeraHashrate( hashrate_target_decrement=TeraHashrate(
terahash_per_second=hashrate_target_decrement terahash_per_second=hashrate_target_decrement
), ),
@@ -301,7 +311,7 @@ class BOSerGRPCAPI:
min_power_target: int, min_power_target: int,
enable_shutdown: bool = None, enable_shutdown: bool = None,
shutdown_duration: int = None, shutdown_duration: int = None,
): ) -> dict:
return await self.send_command( return await self.send_command(
"set_dps", "set_dps",
message=SetDpsRequest( message=SetDpsRequest(
@@ -322,7 +332,7 @@ class BOSerGRPCAPI:
wattage_target: int = None, wattage_target: int = None,
hashrate_target: int = None, hashrate_target: int = None,
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
if wattage_target is not None and hashrate_target is not None: if wattage_target is not None and hashrate_target is not None:
logging.error( logging.error(
"Cannot use both wattage_target and hashrate_target, using wattage_target." "Cannot use both wattage_target and hashrate_target, using wattage_target."
@@ -362,62 +372,71 @@ class BOSerGRPCAPI:
), ),
) )
async def get_active_performance_mode(self): async def get_active_performance_mode(self) -> dict:
return await self.send_command( return await self.send_command(
"get_active_performance_mode", GetPerformanceModeRequest() "get_active_performance_mode", message=GetPerformanceModeRequest()
) )
async def get_pool_groups(self): async def get_pool_groups(self) -> dict:
return await self.send_command("get_pool_groups", GetPoolGroupsRequest())
async def create_pool_group(self):
raise NotImplementedError
return await self.send_command("braiins.bos.v1.PoolService/CreatePoolGroup")
async def update_pool_group(self):
raise NotImplementedError
return await self.send_command("braiins.bos.v1.PoolService/UpdatePoolGroup")
async def remove_pool_group(self):
raise NotImplementedError
return await self.send_command("braiins.bos.v1.PoolService/RemovePoolGroup")
async def get_miner_configuration(self):
return await self.send_command( return await self.send_command(
"get_miner_configuration", GetMinerConfigurationRequest() "get_pool_groups", message=GetPoolGroupsRequest()
) )
async def get_constraints(self): async def create_pool_group(self) -> dict:
return await self.send_command("get_constraints", GetConstraintsRequest()) raise NotImplementedError
async def get_license_state(self): async def update_pool_group(self) -> dict:
return await self.send_command("get_license_state", GetLicenseStateRequest()) raise NotImplementedError
async def get_miner_status(self): async def remove_pool_group(self) -> dict:
return await self.send_command("get_miner_status", GetMinerStatusRequest()) raise NotImplementedError
async def get_miner_details(self): async def get_miner_configuration(self) -> dict:
return await self.send_command("get_miner_details", GetMinerDetailsRequest())
async def get_miner_stats(self):
return await self.send_command("get_miner_stats", GetMinerStatsRequest())
async def get_hashboards(self):
return await self.send_command("get_hashboards", GetHashboardsRequest())
async def get_support_archive(self):
return await self.send_command( return await self.send_command(
"get_support_archive", GetSupportArchiveRequest() "get_miner_configuration", message=GetMinerConfigurationRequest()
)
async def get_constraints(self) -> dict:
return await self.send_command(
"get_constraints", message=GetConstraintsRequest()
)
async def get_license_state(self) -> dict:
return await self.send_command(
"get_license_state", message=GetLicenseStateRequest()
)
async def get_miner_status(self) -> dict:
return await self.send_command(
"get_miner_status", message=GetMinerStatusRequest()
)
async def get_miner_details(self) -> dict:
return await self.send_command(
"get_miner_details", message=GetMinerDetailsRequest()
)
async def get_miner_stats(self) -> dict:
return await self.send_command(
"get_miner_stats", message=GetMinerStatsRequest()
)
async def get_hashboards(self) -> dict:
return await self.send_command("get_hashboards", message=GetHashboardsRequest())
async def get_support_archive(self) -> dict:
return await self.send_command(
"get_support_archive", message=GetSupportArchiveRequest()
) )
async def enable_hashboards( async def enable_hashboards(
self, self,
hashboard_ids: List[str], hashboard_ids: List[str],
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"enable_hashboards", "enable_hashboards",
EnableHashboardsRequest( message=EnableHashboardsRequest(
hashboard_ids=hashboard_ids, save_action=save_action hashboard_ids=hashboard_ids, save_action=save_action
), ),
) )
@@ -426,10 +445,10 @@ class BOSerGRPCAPI:
self, self,
hashboard_ids: List[str], hashboard_ids: List[str],
save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
): ) -> dict:
return await self.send_command( return await self.send_command(
"disable_hashboards", "disable_hashboards",
DisableHashboardsRequest( message=DisableHashboardsRequest(
hashboard_ids=hashboard_ids, save_action=save_action hashboard_ids=hashboard_ids, save_action=save_action
), ),
) )

View File

@@ -13,33 +13,38 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import json import json
from typing import Any
import httpx import httpx
from pyasic import settings from pyasic import settings
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.web import BaseWebAPI
class BOSMinerLuCIAPI: class BOSMinerWebAPI(BaseWebAPI):
def __init__(self, ip: str, pwd: str): def __init__(self, ip: str) -> None:
self.ip = ip super().__init__(ip)
self.username = "root" self.username = "root"
self.pwd = pwd self.pwd = settings.get("default_bosminer_password", "root")
self.port = 80 self.port = 80
async def multicommand(self, *commands: str) -> dict: async def send_command(
data = {} self,
for command in commands: command: str | bytes,
data[command] = await self.send_command(command, ignore_errors=True) ignore_errors: bool = False,
return data allow_warning: bool = True,
privileged: bool = False,
async def send_command(self, path: str, ignore_errors: bool = False) -> dict: **parameters: Any,
) -> dict:
try: try:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
await self.auth(client) await self.auth(client)
data = await client.get( data = await client.get(
f"http://{self.ip}:{self.port}/cgi-bin/luci/{path}", f"http://{self.ip}:{self.port}/cgi-bin/luci/{command}",
headers={"User-Agent": "BTC Tools v0.1"}, headers={"User-Agent": "BTC Tools v0.1"},
) )
if data.status_code == 200: if data.status_code == 200:
@@ -47,14 +52,20 @@ class BOSMinerLuCIAPI:
if ignore_errors: if ignore_errors:
return {} return {}
raise APIError( raise APIError(
f"LUCI web command failed: path={path}, code={data.status_code}" f"LUCI web command failed: command={command}, code={data.status_code}"
) )
except (httpx.HTTPError, json.JSONDecodeError): except (httpx.HTTPError, json.JSONDecodeError):
if ignore_errors: if ignore_errors:
return {} return {}
raise APIError(f"LUCI web command failed: path={path}") raise APIError(f"LUCI web command failed: command={command}")
async def auth(self, session: httpx.AsyncClient): async def multicommand(self, *commands: str) -> dict:
data = {}
for command in commands:
data[command] = await self.send_command(command, ignore_errors=True)
return data
async def auth(self, session: httpx.AsyncClient) -> None:
login = {"luci_username": self.username, "luci_password": self.pwd} login = {"luci_username": self.username, "luci_password": self.pwd}
url = f"http://{self.ip}:{self.port}/cgi-bin/luci" url = f"http://{self.ip}:{self.port}/cgi-bin/luci"
headers = { headers = {
@@ -63,22 +74,22 @@ class BOSMinerLuCIAPI:
} }
await session.post(url, headers=headers, data=login) await session.post(url, headers=headers, data=login)
async def get_net_conf(self): async def get_net_conf(self) -> dict:
return await self.send_command("admin/network/iface_status/lan") return await self.send_command("admin/network/iface_status/lan")
async def get_cfg_metadata(self): async def get_cfg_metadata(self) -> dict:
return await self.send_command("admin/miner/cfg_metadata") return await self.send_command("admin/miner/cfg_metadata")
async def get_cfg_data(self): async def get_cfg_data(self) -> dict:
return await self.send_command("admin/miner/cfg_data") return await self.send_command("admin/miner/cfg_data")
async def get_bos_info(self): async def get_bos_info(self) -> dict:
return await self.send_command("bos/info") return await self.send_command("bos/info")
async def get_overview(self): async def get_overview(self) -> dict:
return await self.send_command( return await self.send_command(
"admin/status/overview?status=1" "admin/status/overview?status=1"
) # needs status=1 or it fails ) # needs status=1 or it fails
async def get_api_status(self): async def get_api_status(self) -> dict:
return await self.send_command("admin/miner/api_status") return await self.send_command("admin/miner/api_status")

View File

@@ -1,105 +0,0 @@
# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import json
from typing import Union
import httpx
from pyasic import settings
class BOSerGraphQLAPI:
def __init__(self, ip: str, pwd: str):
self.ip = ip
self.username = "root"
self.pwd = pwd
self.port = 80
async def multicommand(self, *commands: dict) -> dict:
def merge(*d: dict):
ret = {}
for i in d:
if i:
for k in i:
if k not in ret:
ret[k] = i[k]
else:
ret[k] = merge(ret[k], i[k])
return None if ret == {} else ret
command = merge(*commands)
data = await self.send_command(command)
if data is not None:
if data.get("data") is None:
try:
commands = list(commands)
# noinspection PyTypeChecker
commands.remove({"bos": {"faultLight": None}})
command = merge(*commands)
data = await self.send_command(command)
except (LookupError, ValueError):
pass
if not data:
data = {}
data["multicommand"] = False
return data
async def send_command(
self,
command: dict,
) -> dict:
url = f"http://{self.ip}:{self.port}/graphql"
query = command
if command is None:
return {}
if command.get("query") is None:
query = {"query": self.parse_command(command)}
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
await self.auth(client)
data = await client.post(url, json=query)
except httpx.HTTPError:
pass
else:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
pass
def parse_command(self, graphql_command: Union[dict, set]) -> str:
if isinstance(graphql_command, dict):
data = []
for key in graphql_command:
if graphql_command[key] is not None:
parsed = self.parse_command(graphql_command[key])
data.append(key + parsed)
else:
data.append(key)
else:
data = graphql_command
return "{" + ",".join(data) + "}"
async def auth(self, client: httpx.AsyncClient) -> None:
url = f"http://{self.ip}:{self.port}/graphql"
await client.post(
url,
json={
"query": (
f'mutation{{auth{{login(username:"{self.username}", password:"{self.pwd}"){{__typename}}}}}}'
)
},
)

View File

@@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import json import json
from typing import Union from typing import Any
import httpx import httpx
@@ -28,32 +30,30 @@ class ePICWebAPI(BaseWebAPI):
super().__init__(ip) super().__init__(ip)
self.username = "root" self.username = "root"
self.pwd = settings.get("default_epic_web_password", "letmein") self.pwd = settings.get("default_epic_web_password", "letmein")
self.token = None
self.port = 4028 self.port = 4028
self.token = None
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
post: bool = False, privileged: bool = False,
**parameters: Union[str, int, bool], **parameters: Any,
) -> dict: ) -> dict:
if post or parameters != {}: post = privileged or not parameters == {}
post = True
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
for i in range(settings.get("get_data_retries", 1) + 1): for i in range(settings.get("get_data_retries", 1) + 1):
try: try:
if post: if post:
epic_param = {
"param": parameters.get("parameters"),
"password": self.pwd,
}
response = await client.post( response = await client.post(
f"http://{self.ip}:{self.port}/{command}", f"http://{self.ip}:{self.port}/{command}",
timeout=5, timeout=5,
json=epic_param, json={
**parameters,
"password": self.pwd,
},
) )
else: else:
response = await client.get( response = await client.get(
@@ -89,31 +89,31 @@ class ePICWebAPI(BaseWebAPI):
return data return data
async def restart_epic(self) -> dict: async def restart_epic(self) -> dict:
return await self.send_command("softreboot", post=True) return await self.send_command("softreboot", privileged=True)
async def reboot(self) -> dict: async def reboot(self) -> dict:
return await self.send_command("reboot", post=True) return await self.send_command("reboot", privileged=True)
async def pause_mining(self) -> dict: async def pause_mining(self) -> dict:
return await self.send_command("miner", post=True, parameters="Stop") return await self.send_command("miner", param="Stop")
async def resume_mining(self) -> dict: async def resume_mining(self) -> dict:
return await self.send_command("miner", post=True, parameters="Autostart") return await self.send_command("miner", param="Autostart")
async def stop_mining(self) -> dict: async def stop_mining(self) -> dict:
return await self.send_command("miner", post=True, parameters="Stop") return await self.send_command("miner", param="Stop")
async def start_mining(self) -> dict: async def start_mining(self) -> dict:
return await self.send_command("miner", post=True, parameters="Autostart") return await self.send_command("miner", param="Autostart")
async def summary(self): async def summary(self) -> dict:
return await self.send_command("summary") return await self.send_command("summary")
async def hashrate(self): async def hashrate(self) -> dict:
return await self.send_command("hashrate") return await self.send_command("hashrate")
async def network(self): async def network(self) -> dict:
return await self.send_command("network") return await self.send_command("network")
async def capabilities(self): async def capabilities(self) -> dict:
return await self.send_command("capabilities") return await self.send_command("capabilities")

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import json import json
import warnings import warnings
from typing import Union from typing import Any
import httpx import httpx
@@ -28,9 +30,9 @@ class GoldshellWebAPI(BaseWebAPI):
super().__init__(ip) super().__init__(ip)
self.username = "admin" self.username = "admin"
self.pwd = settings.get("default_goldshell_web_password", "123456789") self.pwd = settings.get("default_goldshell_web_password", "123456789")
self.jwt = None self.token = None
async def auth(self): async def auth(self) -> str | None:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
await client.get(f"http://{self.ip}:{self.port}/user/logout") await client.get(f"http://{self.ip}:{self.port}/user/logout")
@@ -54,47 +56,43 @@ class GoldshellWebAPI(BaseWebAPI):
f"Could not authenticate web token with miner: {self}" f"Could not authenticate web token with miner: {self}"
) )
else: else:
self.jwt = auth.get("JWT Token") self.token = auth.get("JWT Token")
else: else:
self.jwt = auth.get("JWT Token") self.token = auth.get("JWT Token")
return self.jwt return self.token
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
if parameters.get("pool_pwd"): if self.token is None:
parameters["pass"] = parameters["pool_pwd"]
parameters.pop("pool_pwd")
if self.jwt is None:
await self.auth() await self.auth()
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
for _ in range(settings.get("get_data_retries", 1)): for _ in range(settings.get("get_data_retries", 1)):
try: try:
if parameters: if not parameters == {}:
response = await client.put( response = await client.put(
f"http://{self.ip}:{self.port}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
) )
else: else:
response = await client.get( response = await client.get(
f"http://{self.ip}:{self.port}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )
json_data = response.json() json_data = response.json()
return json_data return json_data
except httpx.HTTPError:
pass
except json.JSONDecodeError:
pass
except TypeError: except TypeError:
await self.auth() await self.auth()
except (httpx.HTTPError, json.JSONDecodeError):
pass
async def multicommand( async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
@@ -107,7 +105,7 @@ class GoldshellWebAPI(BaseWebAPI):
try: try:
response = await client.get( response = await client.get(
f"http://{self.ip}:{self.port}/mcb/{command}", f"http://{self.ip}:{self.port}/mcb/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )
json_data = response.json() json_data = response.json()
@@ -120,19 +118,25 @@ class GoldshellWebAPI(BaseWebAPI):
await self.auth() await self.auth()
return data return data
async def pools(self): async def pools(self) -> dict:
return await self.send_command("pools") return await self.send_command("pools")
async def newpool(self, url: str, user: str, password: str): async def newpool(self, url: str, user: str, password: str) -> dict:
return await self.send_command("newpool", url=url, user=user, pool_pwd=password) # looks dumb, but cant pass `pass` since it is a built in type
async def delpool(self, url: str, user: str, password: str, dragid: int = 0):
return await self.send_command( return await self.send_command(
"delpool", url=url, user=user, pool_pwd=password, dragid=dragid "newpool", **{"url": url, "user": user, "pass": password}
) )
async def setting(self): async def delpool(
self, url: str, user: str, password: str, dragid: int = 0
) -> dict:
# looks dumb, but cant pass `pass` since it is a built in type
return await self.send_command(
"delpool", **{"url": url, "user": user, "pass": password, "dragid": dragid}
)
async def setting(self) -> dict:
return await self.send_command("setting") return await self.send_command("setting")
async def status(self): async def status(self) -> dict:
return await self.send_command("status") return await self.send_command("status")

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import json import json
import warnings import warnings
from typing import Union from typing import Any
import httpx import httpx
@@ -29,9 +31,9 @@ class InnosiliconWebAPI(BaseWebAPI):
super().__init__(ip) super().__init__(ip)
self.username = "admin" self.username = "admin"
self.pwd = settings.get("default_innosilicon_web_password", "admin") self.pwd = settings.get("default_innosilicon_web_password", "admin")
self.jwt = None self.token = None
async def auth(self): async def auth(self) -> str | None:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
auth = await client.post( auth = await client.post(
@@ -42,24 +44,25 @@ class InnosiliconWebAPI(BaseWebAPI):
warnings.warn(f"Could not authenticate web token with miner: {self}") warnings.warn(f"Could not authenticate web token with miner: {self}")
else: else:
json_auth = auth.json() json_auth = auth.json()
self.jwt = json_auth.get("jwt") self.token = json_auth.get("jwt")
return self.jwt return self.token
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
if self.jwt is None: if self.token is None:
await self.auth() await self.auth()
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
for _ in range(settings.get("get_data_retries", 1)): for _ in range(settings.get("get_data_retries", 1)):
try: try:
response = await client.post( response = await client.post(
f"http://{self.ip}:{self.port}/api/{command}", f"http://{self.ip}:{self.port}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
json=parameters, json=parameters,
) )
@@ -79,9 +82,7 @@ class InnosiliconWebAPI(BaseWebAPI):
raise APIError(json_data["message"]) raise APIError(json_data["message"])
raise APIError("Innosilicon web api command failed.") raise APIError("Innosilicon web api command failed.")
return json_data return json_data
except httpx.HTTPError: except (httpx.HTTPError, json.JSONDecodeError):
pass
except json.JSONDecodeError:
pass pass
async def multicommand( async def multicommand(
@@ -95,7 +96,7 @@ class InnosiliconWebAPI(BaseWebAPI):
try: try:
response = await client.post( response = await client.post(
f"http://{self.ip}:{self.port}/api/{command}", f"http://{self.ip}:{self.port}/api/{command}",
headers={"Authorization": "Bearer " + self.jwt}, headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5), timeout=settings.get("api_function_timeout", 5),
) )
json_data = response.json() json_data = response.json()
@@ -123,14 +124,14 @@ class InnosiliconWebAPI(BaseWebAPI):
async def type(self) -> dict: async def type(self) -> dict:
return await self.send_command("type") return await self.send_command("type")
async def get_all(self): async def get_all(self) -> dict:
return await self.send_command("getAll") return await self.send_command("getAll")
async def get_error_detail(self): async def get_error_detail(self) -> dict:
return await self.send_command("getErrorDetail") return await self.send_command("getErrorDetail")
async def pools(self): async def pools(self) -> dict:
return await self.send_command("pools") return await self.send_command("pools")
async def poweroff(self): async def poweroff(self) -> dict:
return await self.send_command("poweroff") return await self.send_command("poweroff")

View File

@@ -13,9 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import json import json
import warnings import warnings
from typing import Union from typing import Any
import httpx import httpx
@@ -30,7 +32,7 @@ class VNishWebAPI(BaseWebAPI):
self.pwd = settings.get("default_vnish_web_password", "admin") self.pwd = settings.get("default_vnish_web_password", "admin")
self.token = None self.token = None
async def auth(self): async def auth(self) -> str | None:
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
try: try:
auth = await client.post( auth = await client.post(
@@ -51,11 +53,13 @@ class VNishWebAPI(BaseWebAPI):
async def send_command( async def send_command(
self, self,
command: Union[str, bytes], command: str | bytes,
ignore_errors: bool = False, ignore_errors: bool = False,
allow_warning: bool = True, allow_warning: bool = True,
**parameters: Union[str, int, bool], privileged: bool = False,
**parameters: Any,
) -> dict: ) -> dict:
post = privileged or not parameters == {}
if self.token is None: if self.token is None:
await self.auth() await self.auth()
async with httpx.AsyncClient(transport=settings.transport()) as client: async with httpx.AsyncClient(transport=settings.transport()) as client:
@@ -65,15 +69,7 @@ class VNishWebAPI(BaseWebAPI):
if command.startswith("system"): if command.startswith("system"):
auth = "Bearer " + self.token auth = "Bearer " + self.token
if parameters.get("post"): if post:
parameters.pop("post")
response = await client.post(
f"http://{self.ip}:{self.port}/api/v1/{command}",
headers={"Authorization": auth},
timeout=settings.get("api_function_timeout", 5),
json=parameters,
)
elif not parameters == {}:
response = await client.post( response = await client.post(
f"http://{self.ip}:{self.port}/api/v1/{command}", f"http://{self.ip}:{self.port}/api/v1/{command}",
headers={"Authorization": auth}, headers={"Authorization": auth},
@@ -94,11 +90,7 @@ class VNishWebAPI(BaseWebAPI):
if json_data: if json_data:
return json_data return json_data
return {"success": True} return {"success": True}
except httpx.HTTPError: except (httpx.HTTPError, json.JSONDecodeError, AttributeError):
pass
except json.JSONDecodeError:
pass
except AttributeError:
pass pass
async def multicommand( async def multicommand(
@@ -111,40 +103,40 @@ class VNishWebAPI(BaseWebAPI):
return data return data
async def restart_vnish(self) -> dict: async def restart_vnish(self) -> dict:
return await self.send_command("mining/restart", post=True) return await self.send_command("mining/restart", privileged=True)
async def reboot(self) -> dict: async def reboot(self) -> dict:
return await self.send_command("system/reboot", post=True) return await self.send_command("system/reboot", privileged=True)
async def pause_mining(self) -> dict: async def pause_mining(self) -> dict:
return await self.send_command("mining/pause", post=True) return await self.send_command("mining/pause", privileged=True)
async def resume_mining(self) -> dict: async def resume_mining(self) -> dict:
return await self.send_command("mining/resume", post=True) return await self.send_command("mining/resume", privileged=True)
async def stop_mining(self) -> dict: async def stop_mining(self) -> dict:
return await self.send_command("mining/stop", post=True) return await self.send_command("mining/stop", privileged=True)
async def start_mining(self) -> dict: async def start_mining(self) -> dict:
return await self.send_command("mining/start", post=True) return await self.send_command("mining/start", privileged=True)
async def info(self): async def info(self) -> dict:
return await self.send_command("info") return await self.send_command("info")
async def summary(self): async def summary(self) -> dict:
return await self.send_command("summary") return await self.send_command("summary")
async def chips(self): async def chips(self) -> dict:
return await self.send_command("chips") return await self.send_command("chips")
async def layout(self): async def layout(self) -> dict:
return await self.send_command("layout") return await self.send_command("layout")
async def status(self): async def status(self) -> dict:
return await self.send_command("status") return await self.send_command("status")
async def settings(self): async def settings(self) -> dict:
return await self.send_command("settings") return await self.send_command("settings")
async def autotune_presets(self): async def autotune_presets(self) -> dict:
return await self.send_command("autotune/presets") return await self.send_command("autotune/presets")