Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fd2199435 | ||
|
|
3226d47846 | ||
|
|
6c1931fe7e | ||
|
|
1dd87ac102 | ||
|
|
95d1e40b4f | ||
|
|
31682b7fae | ||
|
|
e6523fc7d5 | ||
|
|
91de12467b | ||
|
|
d81e3e9f88 | ||
|
|
49fc0f3c54 | ||
|
|
4b36044e56 | ||
|
|
90fb67f586 | ||
|
|
edf31ae7df | ||
|
|
af354fd8e2 | ||
|
|
6a2a3e836d | ||
|
|
41709e4706 | ||
|
|
b60c7a55d4 | ||
|
|
eed1973345 | ||
|
|
64774d2017 | ||
|
|
e9751d6cd1 | ||
|
|
e2b0a76e67 | ||
|
|
1c5c39fa97 | ||
|
|
27c48764a8 |
@@ -113,10 +113,17 @@ class MinerConfig:
|
||||
**self.fan_mode.as_epic(),
|
||||
**self.temperature.as_epic(),
|
||||
**self.mining_mode.as_epic(),
|
||||
**self.pools.as_epic(user_suffix=user_suffix),
|
||||
**self.pools.as_epic(),
|
||||
**self.power_scaling.as_epic(),
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_auradine(),
|
||||
**self.mining_mode.as_auradine(),
|
||||
**self.pools.as_auradine(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
@@ -189,6 +196,14 @@ class MinerConfig:
|
||||
mining_mode=MiningModeConfig.from_vnish(web_settings),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_api(web_conf["pools"]),
|
||||
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
|
||||
mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
|
||||
)
|
||||
|
||||
|
||||
def merge(a: dict, b: dict) -> dict:
|
||||
result = deepcopy(a)
|
||||
|
||||
@@ -53,6 +53,9 @@ class MinerConfigOption(Enum):
|
||||
def as_vnish(self) -> dict:
|
||||
return self.value.as_vnish()
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return self.value.as_auradine()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@@ -99,3 +102,6 @@ class MinerConfigValue:
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {}
|
||||
|
||||
@@ -92,6 +92,9 @@ class FanModeManual(MinerConfigValue):
|
||||
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": self.speed}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
@@ -107,6 +110,9 @@ class FanModeImmersion(MinerConfigValue):
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "disabled"}}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": 0}}
|
||||
|
||||
|
||||
class FanModeConfig(MinerConfigOption):
|
||||
normal = FanModeNormal
|
||||
@@ -202,3 +208,13 @@ class FanModeConfig(MinerConfigOption):
|
||||
if "minimumRequiredFans" in keys:
|
||||
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
|
||||
return cls.manual(**conf)
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_fan: dict):
|
||||
try:
|
||||
fan_data = web_fan["Fan"][0]
|
||||
fan_1_max = fan_data["Max"]
|
||||
fan_1_target = fan_data["Target"]
|
||||
return cls.manual(speed=round((fan_1_target / fan_1_max) * 100))
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
@@ -43,6 +43,9 @@ class MiningModeNormal(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": self.mode}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
@@ -58,6 +61,9 @@ class MiningModeSleep(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"sleep": "on"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
@@ -73,6 +79,9 @@ class MiningModeLPM(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "eco"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHPM(MinerConfigValue):
|
||||
@@ -88,6 +97,9 @@ class MiningModeHPM(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "turbo"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
@@ -123,6 +135,9 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
),
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
@@ -152,6 +167,9 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
)
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
@@ -330,3 +348,22 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.hashrate_tuning(
|
||||
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_mode: dict):
|
||||
try:
|
||||
mode_data = web_mode["Mode"][0]
|
||||
if mode_data.get("Sleep") == "on":
|
||||
return cls.sleep()
|
||||
if mode_data.get("Mode") == "normal":
|
||||
return cls.normal()
|
||||
if mode_data.get("Mode") == "eco":
|
||||
return cls.low()
|
||||
if mode_data.get("Mode") == "turbo":
|
||||
return cls.high()
|
||||
if mode_data.get("Ths") is not None:
|
||||
return cls.hashrate_tuning(mode_data["Ths"])
|
||||
if mode_data.get("Power") is not None:
|
||||
return cls.power_tuning(mode_data["Power"])
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
@@ -27,7 +27,7 @@ class Pool(MinerConfigValue):
|
||||
user: str
|
||||
password: str
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None):
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
@@ -36,7 +36,7 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
def as_wm(self, idx: int = 1, user_suffix: str = None):
|
||||
def as_wm(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
@@ -49,7 +49,7 @@ class Pool(MinerConfigValue):
|
||||
f"passwd_{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_am_old(self, idx: int = 1, user_suffix: str = None):
|
||||
def as_am_old(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"_ant_pool{idx}url": self.url,
|
||||
@@ -62,7 +62,7 @@ class Pool(MinerConfigValue):
|
||||
f"_ant_pool{idx}pw": self.password,
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None):
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
@@ -71,12 +71,12 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None):
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
if user_suffix is not None:
|
||||
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
|
||||
return ",".join([self.url, self.user, self.password])
|
||||
|
||||
def as_inno(self, idx: int = 1, user_suffix: str = None):
|
||||
def as_inno(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"Pool{idx}": self.url,
|
||||
@@ -89,7 +89,7 @@ class Pool(MinerConfigValue):
|
||||
f"Password{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None):
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
@@ -98,6 +98,15 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "password": self.password}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix}",
|
||||
"pass": self.password,
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool":
|
||||
return cls(
|
||||
@@ -241,6 +250,9 @@ class PoolGroup(MinerConfigValue):
|
||||
return conf
|
||||
return {"name": "Group", "pool": []}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> list:
|
||||
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
@@ -296,7 +308,7 @@ class PoolGroup(MinerConfigValue):
|
||||
return cls([Pool.from_vnish(p) for p in web_settings_pools])
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_pool_group: dict):
|
||||
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
|
||||
try:
|
||||
return cls(
|
||||
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
||||
@@ -373,6 +385,15 @@ class PoolConfig(MinerConfigValue):
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
return {}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {
|
||||
"updatepools": {
|
||||
"pools": self.groups[0].as_auradine(user_suffix=user_suffix)
|
||||
}
|
||||
}
|
||||
return {"updatepools": {"pools": PoolGroup().as_auradine()}}
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
@@ -417,7 +438,7 @@ class PoolConfig(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict):
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig":
|
||||
try:
|
||||
return cls(
|
||||
groups=[
|
||||
|
||||
1
pyasic/miners/auradine/__init__.py
Normal file
1
pyasic/miners/auradine/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .flux import *
|
||||
6
pyasic/miners/auradine/flux/AD/AT1.py
Normal file
6
pyasic/miners/auradine/flux/AD/AT1.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAT1500
|
||||
|
||||
|
||||
class AuradineFluxAT1500(AuradineAT1500, Auradine):
|
||||
pass
|
||||
10
pyasic/miners/auradine/flux/AD/AT2.py
Normal file
10
pyasic/miners/auradine/flux/AD/AT2.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAT2860, AuradineAT2880
|
||||
|
||||
|
||||
class AuradineFluxAT2860(AuradineAT2860, Auradine):
|
||||
pass
|
||||
|
||||
|
||||
class AuradineFluxAT2880(AuradineAT2880, Auradine):
|
||||
pass
|
||||
2
pyasic/miners/auradine/flux/AD/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AD/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AT1 import AuradineFluxAT1500
|
||||
from .AT2 import AuradineFluxAT2860, AuradineFluxAT2880
|
||||
6
pyasic/miners/auradine/flux/AI/AI2.py
Normal file
6
pyasic/miners/auradine/flux/AI/AI2.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAI2500
|
||||
|
||||
|
||||
class AuradineFluxAI2500(AuradineAI2500, Auradine):
|
||||
pass
|
||||
6
pyasic/miners/auradine/flux/AI/AI3.py
Normal file
6
pyasic/miners/auradine/flux/AI/AI3.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAI3680
|
||||
|
||||
|
||||
class AuradineFluxAI3680(AuradineAI3680, Auradine):
|
||||
pass
|
||||
2
pyasic/miners/auradine/flux/AI/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AI/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AI2 import AuradineFluxAI2500
|
||||
from .AI3 import AuradineFluxAI3680
|
||||
6
pyasic/miners/auradine/flux/AT/AD2.py
Normal file
6
pyasic/miners/auradine/flux/AT/AD2.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAD2500
|
||||
|
||||
|
||||
class AuradineFluxAD2500(AuradineAD2500, Auradine):
|
||||
pass
|
||||
6
pyasic/miners/auradine/flux/AT/AD3.py
Normal file
6
pyasic/miners/auradine/flux/AT/AD3.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends import Auradine
|
||||
from pyasic.miners.types import AuradineAD3500
|
||||
|
||||
|
||||
class AuradineFluxAD3500(AuradineAD3500, Auradine):
|
||||
pass
|
||||
2
pyasic/miners/auradine/flux/AT/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AT/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AD2 import AuradineFluxAD2500
|
||||
from .AD3 import AuradineFluxAD3500
|
||||
3
pyasic/miners/auradine/flux/__init__.py
Normal file
3
pyasic/miners/auradine/flux/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .AD import *
|
||||
from .AI import *
|
||||
from .AT import *
|
||||
@@ -14,6 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .antminer import AntminerModern, AntminerOld
|
||||
from .auradine import Auradine
|
||||
from .avalonminer import AvalonMiner
|
||||
from .bfgminer import BFGMiner
|
||||
from .bmminer import BMMiner
|
||||
@@ -23,6 +24,7 @@ from .cgminer import CGMiner
|
||||
from .epic import ePIC
|
||||
from .goldshell import GoldshellMiner
|
||||
from .hiveon import Hiveon
|
||||
from .innosilicon import Innosilicon
|
||||
from .luxminer import LUXMiner
|
||||
from .vnish import VNish
|
||||
from .whatsminer import M2X, M3X, M5X, M6X
|
||||
|
||||
378
pyasic/miners/backends/auradine.py
Normal file
378
pyasic/miners/backends/auradine.py
Normal file
@@ -0,0 +1,378 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 logging
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc.gcminer import GCMinerRPCAPI
|
||||
from pyasic.web.auradine import FluxWebAPI
|
||||
|
||||
AURADINE_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_ipreport", "ipreport")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_ipreport", "ipreport")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_ipreport", "ipreport")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("api_devs", "devs"),
|
||||
WebAPICommand("web_ipreport", "ipreport"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_psu", "psu")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[WebAPICommand("web_mode", "mode"), WebAPICommand("web_psu", "psu")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_fan", "fan")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_led", "led")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_mode", "mode")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AuradineLEDColors(Enum):
|
||||
OFF = 0
|
||||
GREEN = 1
|
||||
RED = 2
|
||||
YELLOW = 3
|
||||
GREEN_FLASHING = 4
|
||||
RED_FLASHING = 5
|
||||
YELLOW_FLASHING = 6
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class AuradineLEDCodes(Enum):
|
||||
NO_POWER = 1
|
||||
NORMAL = 2
|
||||
LOCATE_MINER = 3
|
||||
TEMPERATURE = 4
|
||||
POOL_CONFIG = 5
|
||||
NETWORK = 6
|
||||
CONTROL_BOARD = 7
|
||||
HASH_RATE_LOW = 8
|
||||
CUSTOM1 = 101
|
||||
CUSTOM2 = 102
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class Auradine(BaseMiner):
|
||||
"""Base handler for Auradine miners"""
|
||||
|
||||
_api_cls = GCMinerRPCAPI
|
||||
api: GCMinerRPCAPI
|
||||
_web_cls = FluxWebAPI
|
||||
web: FluxWebAPI
|
||||
|
||||
data_locations = AURADINE_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
supports_autotuning = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
return await self.web.set_led(code=int(AuradineLEDCodes.LOCATE_MINER))
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
return await self.web.set_led(code=int(AuradineLEDCodes.NORMAL))
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
try:
|
||||
await self.web.reboot()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
try:
|
||||
await self.web.restart_gcminer()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
try:
|
||||
await self.web.set_mode(sleep="on")
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
try:
|
||||
await self.web.set_mode(sleep="off")
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
await self.web.set_mode(mode="custom", tune="power", power=wattage)
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
try:
|
||||
web_conf = await self.web.multicommand("pools", "mode", "fan")
|
||||
return MinerConfig.from_auradine(web_conf=web_conf)
|
||||
except APIError as e:
|
||||
logging.warning(e)
|
||||
except LookupError:
|
||||
pass
|
||||
return MinerConfig()
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
|
||||
conf = config.as_auradine(user_suffix=user_suffix)
|
||||
for key in conf.keys():
|
||||
await self.web.send_command(command=key, **conf[key])
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, web_ipreport: dict = None) -> Optional[str]:
|
||||
if web_ipreport is None:
|
||||
try:
|
||||
web_ipreport = await self.web.ipreport()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_ipreport is not None:
|
||||
try:
|
||||
return web_ipreport["IPReport"][0]["mac"].upper()
|
||||
except (LookupError, AttributeError):
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_ipreport: dict = None) -> Optional[str]:
|
||||
if web_ipreport is None:
|
||||
try:
|
||||
web_ipreport = await self.web.ipreport()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_ipreport is not None:
|
||||
try:
|
||||
return web_ipreport["IPReport"][0]["version"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hostname(self, web_ipreport: dict = None) -> Optional[str]:
|
||||
if web_ipreport is None:
|
||||
try:
|
||||
web_ipreport = await self.web.ipreport()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_ipreport is not None:
|
||||
try:
|
||||
return web_ipreport["IPReport"][0]["hostname"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(api_summary["SUMMARY"][0]["MHS 5s"]) / 1000000), 2
|
||||
)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(
|
||||
self, api_devs: dict = None, web_ipreport: dict = None
|
||||
) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
if web_ipreport is None:
|
||||
try:
|
||||
web_ipreport = await self.web.ipreport()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs is not None:
|
||||
try:
|
||||
for board in api_devs["DEVS"]:
|
||||
b_id = board["ID"] - 1
|
||||
hashboards[b_id].hashrate = round(
|
||||
float(float(board["MHS 5s"]) / 1000000), 2
|
||||
)
|
||||
hashboards[b_id].temp = round(float(float(board["Temperature"])), 2)
|
||||
hashboards[b_id].missing = False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
if web_ipreport is not None:
|
||||
try:
|
||||
for board, sn in enumerate(web_ipreport["IPReport"][0]["HBSerialNo"]):
|
||||
hashboards[board].serial_number = sn
|
||||
hashboards[board].missing = False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_wattage(self, web_psu: dict = None) -> Optional[int]:
|
||||
if web_psu is None:
|
||||
try:
|
||||
web_psu = await self.web.get_psu()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_psu is not None:
|
||||
try:
|
||||
return int(float(web_psu["PSU"][0]["PowerIn"].replace("W", "")))
|
||||
except (LookupError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(
|
||||
self, web_mode: dict = None, web_psu: dict = None
|
||||
) -> Optional[int]:
|
||||
if web_mode is None:
|
||||
try:
|
||||
web_mode = await self.web.get_mode()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_mode is not None:
|
||||
try:
|
||||
return web_mode["Mode"][0]["Power"]
|
||||
except (LookupError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
if web_psu is None:
|
||||
try:
|
||||
web_psu = await self.web.get_psu()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_psu is not None:
|
||||
try:
|
||||
return int(float(web_psu["PSU"][0]["PoutMax"].replace("W", "")))
|
||||
except (LookupError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_fan: dict = None) -> List[Fan]:
|
||||
if web_fan is None:
|
||||
try:
|
||||
web_fan = await self.web.get_fan()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = []
|
||||
if web_fan is not None:
|
||||
try:
|
||||
for fan in web_fan["Fan"]:
|
||||
fans.append(Fan(round(fan["Speed"])))
|
||||
except LookupError:
|
||||
pass
|
||||
return fans
|
||||
|
||||
async def _get_fault_light(self, web_led: dict = None) -> Optional[bool]:
|
||||
if web_led is None:
|
||||
try:
|
||||
web_led = await self.web.get_led()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_led is not None:
|
||||
try:
|
||||
return web_led["LED"][0]["Code"] == int(AuradineLEDCodes.LOCATE_MINER)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self, web_mode: dict = None) -> Optional[bool]:
|
||||
if web_mode is None:
|
||||
try:
|
||||
web_mode = await self.web.get_mode()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_mode is not None:
|
||||
try:
|
||||
return web_mode["Mode"][0]["Sleep"] == "off"
|
||||
except (LookupError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Elapsed"]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -334,6 +334,8 @@ class BOSMiner(BaseMiner):
|
||||
async def _get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
hostname = (await self.ssh.get_hostname()).strip()
|
||||
except AttributeError:
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"{self} - Getting hostname failed: {e}")
|
||||
return None
|
||||
|
||||
@@ -35,3 +35,7 @@ class InnosiliconMake(BaseMiner):
|
||||
|
||||
class GoldshellMake(BaseMiner):
|
||||
make = "Goldshell"
|
||||
|
||||
|
||||
class AuradineMake(BaseMiner):
|
||||
make = "Auradine"
|
||||
|
||||
@@ -26,19 +26,21 @@ import httpx
|
||||
from pyasic import settings
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.antminer import *
|
||||
from pyasic.miners.auradine import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
from pyasic.miners.backends import (
|
||||
Auradine,
|
||||
AvalonMiner,
|
||||
BMMiner,
|
||||
BOSMiner,
|
||||
BTMiner,
|
||||
GoldshellMiner,
|
||||
Hiveon,
|
||||
Innosilicon,
|
||||
LUXMiner,
|
||||
VNish,
|
||||
ePIC,
|
||||
)
|
||||
from pyasic.miners.backends.innosilicon import Innosilicon
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
@@ -57,6 +59,7 @@ class MinerTypes(enum.Enum):
|
||||
HIVEON = 7
|
||||
LUX_OS = 8
|
||||
EPIC = 9
|
||||
AURADINE = 10
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -392,6 +395,16 @@ MINER_CLASSES = {
|
||||
None: LUXMiner,
|
||||
"ANTMINER S9": LUXMinerS9,
|
||||
},
|
||||
MinerTypes.AURADINE: {
|
||||
None: Auradine,
|
||||
"AT1500": AuradineFluxAT1500,
|
||||
"AT2860": AuradineFluxAT2860,
|
||||
"AT2880": AuradineFluxAT2880,
|
||||
"AI2500": AuradineFluxAI2500,
|
||||
"AI3680": AuradineFluxAI3680,
|
||||
"AD2500": AuradineFluxAD2500,
|
||||
"AD3500": AuradineFluxAD3500,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -411,12 +424,6 @@ async def concurrent_get_first_result(tasks: list, verification_func: Callable):
|
||||
|
||||
|
||||
class MinerFactory:
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
|
||||
def clear_cached_miners(self):
|
||||
self.cache = {}
|
||||
|
||||
async def get_multiple_miners(
|
||||
self, ips: List[str], limit: int = 200
|
||||
) -> List[AnyMiner]:
|
||||
@@ -442,8 +449,6 @@ class MinerFactory:
|
||||
|
||||
async def get_miner(self, ip: str):
|
||||
ip = str(ip)
|
||||
if ip in self.cache:
|
||||
return self.cache[ip]
|
||||
|
||||
miner_type = None
|
||||
|
||||
@@ -472,6 +477,7 @@ class MinerFactory:
|
||||
MinerTypes.EPIC: self.get_miner_model_epic,
|
||||
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||
MinerTypes.AURADINE: self.get_miner_model_auradine,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
@@ -491,8 +497,6 @@ class MinerFactory:
|
||||
miner_model=miner_model,
|
||||
)
|
||||
|
||||
if miner is not None and not isinstance(miner, UnknownMiner):
|
||||
self.cache[ip] = miner
|
||||
return miner
|
||||
|
||||
async def _get_miner_type(self, ip: str):
|
||||
@@ -571,6 +575,8 @@ class MinerFactory:
|
||||
return MinerTypes.AVALONMINER
|
||||
if "DragonMint" in web_text:
|
||||
return MinerTypes.INNOSILICON
|
||||
if "Miner UI" in web_text:
|
||||
return MinerTypes.AURADINE
|
||||
|
||||
async def _get_miner_socket(self, ip: str):
|
||||
tasks = []
|
||||
@@ -660,6 +666,8 @@ class MinerFactory:
|
||||
return MinerTypes.GOLDSHELL
|
||||
if "AVALON" in upper_data:
|
||||
return MinerTypes.AVALONMINER
|
||||
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
|
||||
return MinerTypes.AURADINE
|
||||
|
||||
async def send_web_command(
|
||||
self,
|
||||
@@ -948,5 +956,12 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_auradine(self, ip: str):
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
return sock_json_data["DEVDETAILS"][0]["Model"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .antminer import *
|
||||
from .auradine import *
|
||||
from .avalonminer import *
|
||||
from .goldshell import *
|
||||
from .innosilicon import *
|
||||
|
||||
6
pyasic/miners/types/auradine/AD/AD2.py
Normal file
6
pyasic/miners/types/auradine/AD/AD2.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAD2500(AuradineMake):
|
||||
raw_model = "AD2500"
|
||||
expected_fans = 0
|
||||
6
pyasic/miners/types/auradine/AD/AD3.py
Normal file
6
pyasic/miners/types/auradine/AD/AD3.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAD3500(AuradineMake):
|
||||
raw_model = "AD3500"
|
||||
expected_fans = 0
|
||||
2
pyasic/miners/types/auradine/AD/__init__.py
Normal file
2
pyasic/miners/types/auradine/AD/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AD2 import AuradineAD2500
|
||||
from .AD3 import AuradineAD3500
|
||||
6
pyasic/miners/types/auradine/AI/AI2.py
Normal file
6
pyasic/miners/types/auradine/AI/AI2.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAI2500(AuradineMake):
|
||||
raw_model = "AI2500"
|
||||
expected_fans = 0
|
||||
6
pyasic/miners/types/auradine/AI/AI3.py
Normal file
6
pyasic/miners/types/auradine/AI/AI3.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAI3680(AuradineMake):
|
||||
raw_model = "AI3680"
|
||||
expected_fans = 0
|
||||
2
pyasic/miners/types/auradine/AI/__init__.py
Normal file
2
pyasic/miners/types/auradine/AI/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AI2 import AuradineAI2500
|
||||
from .AI3 import AuradineAI3680
|
||||
7
pyasic/miners/types/auradine/AT/AT1.py
Normal file
7
pyasic/miners/types/auradine/AT/AT1.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAT1500(AuradineMake):
|
||||
raw_model = "AT1500"
|
||||
expected_chips = 132
|
||||
expected_fans = 4
|
||||
11
pyasic/miners/types/auradine/AT/AT2.py
Normal file
11
pyasic/miners/types/auradine/AT/AT2.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from pyasic.miners.makes import AuradineMake
|
||||
|
||||
|
||||
class AuradineAT2860(AuradineMake):
|
||||
raw_model = "AT2860"
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class AuradineAT2880(AuradineMake):
|
||||
raw_model = "AT2880"
|
||||
expected_fans = 4
|
||||
2
pyasic/miners/types/auradine/AT/__init__.py
Normal file
2
pyasic/miners/types/auradine/AT/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .AT1 import AuradineAT1500
|
||||
from .AT2 import AuradineAT2860, AuradineAT2880
|
||||
3
pyasic/miners/types/auradine/__init__.py
Normal file
3
pyasic/miners/types/auradine/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .AD import *
|
||||
from .AI import *
|
||||
from .AT import *
|
||||
@@ -19,6 +19,7 @@ from pyasic.miners.makes import WhatsMinerMake
|
||||
|
||||
class M50SPlusPlusVK10(WhatsMinerMake):
|
||||
raw_model = "M50S++ VK10"
|
||||
expected_chips = 117
|
||||
expected_fans = 2
|
||||
|
||||
|
||||
|
||||
@@ -120,9 +120,6 @@ class MinerNetwork:
|
||||
async def scan_network_for_miners(self) -> List[AnyMiner]:
|
||||
logging.debug(f"{self} - (Scan Network For Miners) - Scanning")
|
||||
|
||||
# clear cached miners
|
||||
miner_factory.clear_cached_miners()
|
||||
|
||||
miners = await asyncio.gather(
|
||||
*[self.ping_and_get_miner(host) for host in self.hosts]
|
||||
)
|
||||
|
||||
@@ -187,7 +187,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, port, api_ver)
|
||||
self.pwd = settings.get("default_whatsminer_password", "admin")
|
||||
self.pwd = settings.get("default_whatsminer_rpc_password", "admin")
|
||||
self.current_token = None
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
|
||||
182
pyasic/rpc/gcminer.py
Normal file
182
pyasic/rpc/gcminer.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import Literal
|
||||
|
||||
from pyasic.rpc import BaseMinerRPCAPI
|
||||
|
||||
|
||||
class GCMinerRPCAPI(BaseMinerRPCAPI):
|
||||
"""An abstraction of the GCMiner API.
|
||||
|
||||
Each method corresponds to an API command in GCMiner.
|
||||
|
||||
No documentation for this API is currently publicly available.
|
||||
|
||||
This class abstracts use of the GCMiner API, as well as the
|
||||
methods for sending commands to it. The `self.send_command()`
|
||||
function handles sending a command to the miner asynchronously, and
|
||||
as such is the base for many of the functions in this class, which
|
||||
rely on it to send the command for them.
|
||||
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
"""
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
"""Get data for ASC device n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device to get data for.
|
||||
|
||||
Returns:
|
||||
The data for ASC device n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all ASC devices.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("asccount")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command `command` exists in LUXMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
command: The command to check.
|
||||
|
||||
Returns:
|
||||
## Information about a command:
|
||||
* Exists (Y/N) <- the command exists in this version
|
||||
* Access (Y/N) <- you have access to use the command
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Information about the current coin being mined:
|
||||
* Hash Method <- the hashing algorithm
|
||||
* Current Block Time <- blocktime as a float, 0 means none
|
||||
* Current Block Hash <- the hash of the current block, blank means none
|
||||
* LP <- whether LP is in use on at least 1 pool
|
||||
* Network Difficulty: the current network difficulty
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner configuration information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def edevs(self) -> dict:
|
||||
"""Alias for devs"""
|
||||
return await self.send_command("edevs")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner pool information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Stats of each device/pool with more than 1 getwork.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def estats(self) -> dict:
|
||||
"""Alias for stats"""
|
||||
return await self.send_command("estats")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner version information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
@@ -34,6 +34,8 @@ _settings = { # defaults
|
||||
"default_bosminer_web_password": "root",
|
||||
"default_vnish_web_password": "admin",
|
||||
"default_goldshell_web_password": "123456789",
|
||||
"default_auradine_web_password": "admin",
|
||||
"default_epic_web_password": "letmein",
|
||||
"default_hive_web_password": "admin",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
|
||||
@@ -26,7 +26,7 @@ from pyasic.web import BaseWebAPI
|
||||
class AntminerModernWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_antminer_password", "root")
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
@@ -142,7 +142,7 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
class AntminerOldWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_antminer_password", "root")
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
|
||||
262
pyasic/web/auradine.py
Normal file
262
pyasic/web/auradine.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 asyncio
|
||||
import json
|
||||
import warnings
|
||||
from typing import Any, List, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.web import BaseWebAPI
|
||||
|
||||
|
||||
class FluxWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_auradine_web_password", "admin")
|
||||
self.port = 8080
|
||||
self.jwt = None
|
||||
|
||||
async def auth(self):
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
try:
|
||||
auth = await client.post(
|
||||
f"http://{self.ip}:{self.port}/token",
|
||||
json={
|
||||
"command": "token",
|
||||
"user": self.username,
|
||||
"password": self.pwd,
|
||||
},
|
||||
)
|
||||
except httpx.HTTPError:
|
||||
warnings.warn(f"Could not authenticate web token with miner: {self}")
|
||||
else:
|
||||
json_auth = auth.json()
|
||||
try:
|
||||
self.jwt = json_auth["Token"][0]["Token"]
|
||||
except LookupError:
|
||||
return None
|
||||
return self.jwt
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: Union[str, bytes],
|
||||
post: bool = False,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
if self.jwt is None:
|
||||
await self.auth()
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for i in range(settings.get("get_data_retries", 1)):
|
||||
try:
|
||||
if post:
|
||||
response = await client.post(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
headers={"Token": self.jwt},
|
||||
timeout=settings.get("api_function_timeout", 5),
|
||||
)
|
||||
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:
|
||||
response = await client.get(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
headers={"Token": self.jwt},
|
||||
timeout=settings.get("api_function_timeout", 5),
|
||||
)
|
||||
json_data = response.json()
|
||||
validation = self._validate_command_output(json_data)
|
||||
if not validation[0]:
|
||||
if i == settings.get("get_data_retries", 1):
|
||||
raise APIError(validation[1])
|
||||
# refresh the token, retry
|
||||
await self.auth()
|
||||
continue
|
||||
return json_data
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
tasks = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks[cmd] = asyncio.create_task(
|
||||
self.send_command(cmd, allow_warning=allow_warning)
|
||||
)
|
||||
|
||||
await asyncio.gather(*[tasks[cmd] for cmd in tasks], return_exceptions=True)
|
||||
|
||||
data = {"multicommand": True}
|
||||
for cmd in tasks:
|
||||
try:
|
||||
result = tasks[cmd].result()
|
||||
if result is None or result == {}:
|
||||
result = {}
|
||||
data[cmd] = result
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _validate_command_output(data: dict) -> tuple:
|
||||
# check if the data returned is correct or an error
|
||||
# if status isn't a key, it is a multicommand
|
||||
if "STATUS" not in data.keys():
|
||||
for key in data.keys():
|
||||
# make sure not to try to turn id into a dict
|
||||
if not key == "id":
|
||||
# make sure they succeeded
|
||||
if "STATUS" in data[key][0].keys():
|
||||
if data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]:
|
||||
# this is an error
|
||||
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
||||
elif "id" not in data.keys():
|
||||
if isinstance(data["STATUS"], list):
|
||||
if data["STATUS"][0].get("STATUS", None) in ["S", "I"]:
|
||||
return True, None
|
||||
else:
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
|
||||
elif isinstance(data["STATUS"], dict):
|
||||
# new style X19 command
|
||||
if data["STATUS"]["STATUS"] not in ["S", "I"]:
|
||||
return False, data["STATUS"]["Msg"]
|
||||
return True, None
|
||||
|
||||
if data["STATUS"] not in ["S", "I"]:
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if isinstance(data["STATUS"], str):
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
return True, None
|
||||
elif isinstance(data["STATUS"], dict):
|
||||
if data["STATUS"].get("STATUS") in ["S", "I"]:
|
||||
return True, None
|
||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
return True, None
|
||||
|
||||
async def factory_reset(self):
|
||||
return await self.send_command("factory-reset", post=True)
|
||||
|
||||
async def get_fan(self):
|
||||
return await self.send_command("fan")
|
||||
|
||||
async def set_fan(self, fan: int, speed_pct: int):
|
||||
return await self.send_command("fan", index=fan, percentage=speed_pct)
|
||||
|
||||
async def firmware_upgrade(self, url: str = None, version: str = "latest"):
|
||||
if url is not None:
|
||||
return await self.send_command("firmware-upgrade", url=url)
|
||||
return await self.send_command("firmware-upgrade", version=version)
|
||||
|
||||
async def get_frequency(self):
|
||||
return await self.send_command("frequency")
|
||||
|
||||
async def set_frequency(self, board: int, frequency: float):
|
||||
return await self.send_command("frequency", board=board, frequency=frequency)
|
||||
|
||||
async def ipreport(self):
|
||||
return await self.send_command("ipreport")
|
||||
|
||||
async def get_led(self):
|
||||
return await self.send_command("led")
|
||||
|
||||
async def set_led(self, code: int):
|
||||
return await self.send_command("led", code=code)
|
||||
|
||||
async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str):
|
||||
return await self.send_command(
|
||||
"led", code=code, led1=led_1, led2=led_2, msg=msg
|
||||
)
|
||||
|
||||
async def get_mode(self):
|
||||
return await self.send_command("mode")
|
||||
|
||||
async def set_mode(self, **kwargs):
|
||||
return await self.send_command("mode", **kwargs)
|
||||
|
||||
async def get_network(self):
|
||||
return await self.send_command("network")
|
||||
|
||||
async def set_network(self, **kwargs):
|
||||
return await self.send_command("network", **kwargs)
|
||||
|
||||
async def password(self, password: str):
|
||||
res = await self.send_command(
|
||||
"password", user=self.username, old=self.pwd, new=password
|
||||
)
|
||||
self.pwd = password
|
||||
return res
|
||||
|
||||
async def get_psu(self):
|
||||
return await self.send_command("psu")
|
||||
|
||||
async def set_psu(self, voltage: float):
|
||||
return await self.send_command("psu", voltage=voltage)
|
||||
|
||||
async def get_register(self):
|
||||
return await self.send_command("register")
|
||||
|
||||
async def set_register(self, company: str):
|
||||
return await self.send_command("register", parameter=company)
|
||||
|
||||
async def reboot(self):
|
||||
return await self.send_command("restart", post=True)
|
||||
|
||||
async def restart_gcminer(self):
|
||||
return await self.send_command("restart", parameter="gcminer")
|
||||
|
||||
async def restart_api_server(self):
|
||||
return await self.send_command("restart", parameter="api-server")
|
||||
|
||||
async def temperature(self):
|
||||
return await self.send_command("temperature")
|
||||
|
||||
async def timedate(self, ntp: str, timezone: str):
|
||||
return await self.send_command("timedate", ntp=ntp, timezone=timezone)
|
||||
|
||||
async def token(self):
|
||||
return await self.send_command("token", user=self.username, password=self.pwd)
|
||||
|
||||
async def update_pools(self, pools: List[dict]):
|
||||
return await self.send_command("updatepools", pools=pools)
|
||||
|
||||
async def voltage(self):
|
||||
return await self.send_command("voltage")
|
||||
|
||||
async def get_ztp(self):
|
||||
return await self.send_command("ztp")
|
||||
|
||||
async def set_ztp(self, enable: bool):
|
||||
return await self.send_command("ztp", parameter="on" if enable else "off")
|
||||
@@ -109,7 +109,7 @@ class BOSerWebAPI(BOSMinerWebAPI):
|
||||
return await self.gql.send_command(command)
|
||||
elif command_type == "grpc":
|
||||
try:
|
||||
return await (getattr(self.grpc, command.replace("grpc_", "")))()
|
||||
return await getattr(self.grpc, command.replace("grpc_", ""))()
|
||||
except AttributeError:
|
||||
raise APIError(f"No gRPC command found for command: {command}")
|
||||
elif command_type == "luci":
|
||||
|
||||
@@ -27,7 +27,7 @@ class ePICWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.username = "root"
|
||||
self.pwd = settings.get("default_epic_password", "letmein")
|
||||
self.pwd = settings.get("default_epic_web_password", "letmein")
|
||||
self.token = None
|
||||
self.port = 4028
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class GoldshellWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_goldshell_password", "123456789")
|
||||
self.pwd = settings.get("default_goldshell_web_password", "123456789")
|
||||
self.jwt = None
|
||||
|
||||
async def auth(self):
|
||||
@@ -69,7 +69,7 @@ class GoldshellWebAPI(BaseWebAPI):
|
||||
if parameters.get("pool_pwd"):
|
||||
parameters["pass"] = parameters["pool_pwd"]
|
||||
parameters.pop("pool_pwd")
|
||||
if not self.jwt:
|
||||
if self.jwt is None:
|
||||
await self.auth()
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for _ in range(settings.get("get_data_retries", 1)):
|
||||
|
||||
@@ -28,7 +28,7 @@ class InnosiliconWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_innosilicon_password", "admin")
|
||||
self.pwd = settings.get("default_innosilicon_web_password", "admin")
|
||||
self.jwt = None
|
||||
|
||||
async def auth(self):
|
||||
@@ -52,7 +52,7 @@ class InnosiliconWebAPI(BaseWebAPI):
|
||||
allow_warning: bool = True,
|
||||
**parameters: Union[str, int, bool],
|
||||
) -> dict:
|
||||
if not self.jwt:
|
||||
if self.jwt is None:
|
||||
await self.auth()
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for _ in range(settings.get("get_data_retries", 1)):
|
||||
|
||||
@@ -27,7 +27,7 @@ class VNishWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_vnish_password", "admin")
|
||||
self.pwd = settings.get("default_vnish_web_password", "admin")
|
||||
self.token = None
|
||||
|
||||
async def auth(self):
|
||||
@@ -56,7 +56,7 @@ class VNishWebAPI(BaseWebAPI):
|
||||
allow_warning: bool = True,
|
||||
**parameters: Union[str, int, bool],
|
||||
) -> dict:
|
||||
if not self.token:
|
||||
if self.token is None:
|
||||
await self.auth()
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for _ in range(settings.get("get_data_retries", 1)):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.48.3"
|
||||
version = "0.49.0"
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
@@ -11,7 +11,6 @@ readme = "README.md"
|
||||
python = "^3.8"
|
||||
httpx = "^0.26.0"
|
||||
asyncssh = "^2.14.2"
|
||||
grpc-requests = "^0.1.13"
|
||||
passlib = "^1.7.4"
|
||||
pyaml = "^23.12.0"
|
||||
toml = "^0.10.2"
|
||||
|
||||
Reference in New Issue
Block a user