Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b068c57c5 | ||
|
|
203f199aec | ||
|
|
895f17aaf9 | ||
|
|
8a64ff3559 | ||
|
|
4c45d356c4 | ||
|
|
4dec329f11 | ||
|
|
b563ed118e | ||
|
|
75b2ec40b1 | ||
|
|
d9adaf6667 | ||
|
|
9343308f41 | ||
|
|
88769e40ae | ||
|
|
be45eb7400 | ||
|
|
2f719a03a4 | ||
|
|
64196f9754 | ||
|
|
49a77f1b79 | ||
|
|
3838c4f2f9 | ||
|
|
80d89c95b5 | ||
|
|
30cd8b5cfe | ||
|
|
c443170f78 | ||
|
|
a2c2aa2377 | ||
|
|
4f0eb49a02 | ||
|
|
a821357b4f | ||
|
|
3c7679a22d | ||
|
|
a52737e236 | ||
|
|
7c96bbe153 | ||
|
|
e8bbf22aa7 | ||
|
|
5ac8b27cb6 | ||
|
|
6c14902484 | ||
|
|
96aa346f00 | ||
|
|
c2b6cc7468 | ||
|
|
ac7f41be44 | ||
|
|
718b87fd12 | ||
|
|
5ad23c6cd0 | ||
|
|
66be443dc3 | ||
|
|
a9135e21d4 | ||
|
|
dd4c087749 | ||
|
|
aa1d7c1b6f | ||
|
|
b328a27f04 | ||
|
|
c5eed797ec | ||
|
|
4fd2199435 | ||
|
|
3226d47846 | ||
|
|
6c1931fe7e | ||
|
|
1dd87ac102 | ||
|
|
95d1e40b4f | ||
|
|
31682b7fae | ||
|
|
e6523fc7d5 | ||
|
|
91de12467b | ||
|
|
d81e3e9f88 | ||
|
|
49fc0f3c54 | ||
|
|
4b36044e56 | ||
|
|
90fb67f586 | ||
|
|
edf31ae7df | ||
|
|
af354fd8e2 | ||
|
|
6a2a3e836d | ||
|
|
41709e4706 | ||
|
|
b60c7a55d4 | ||
|
|
eed1973345 | ||
|
|
64774d2017 | ||
|
|
e9751d6cd1 | ||
|
|
e2b0a76e67 | ||
|
|
1c5c39fa97 | ||
|
|
27c48764a8 |
@@ -3,7 +3,7 @@ import importlib
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes
|
||||
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ if __name__ == "__main__":
|
||||
|
||||
---
|
||||
##### Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
||||
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
[`MinerFactory`][pyasic.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
|
||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||
|
||||
@@ -9,7 +9,7 @@ The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_
|
||||
|
||||
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||
|
||||
::: pyasic.miners.miner_factory.MinerFactory
|
||||
::: pyasic.miners.factory.MinerFactory
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -25,12 +25,12 @@ Finally, there is functionality to get multiple miners without using `asyncio.ga
|
||||
<br>
|
||||
|
||||
## AnyMiner
|
||||
::: pyasic.miners.miner_factory.AnyMiner
|
||||
::: pyasic.miners.base.AnyMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
[`AnyMiner`][pyasic.miners.base.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||
|
||||
@@ -4,10 +4,10 @@ Each miner has a unique API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI]
|
||||
@@ -21,7 +21,7 @@ Use these instead -
|
||||
<br>
|
||||
|
||||
## BaseMinerRPCAPI
|
||||
::: pyasic.rpc.BaseMinerRPCAPI
|
||||
::: pyasic.rpc.base.BaseMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
@@ -15,45 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic import settings
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import (
|
||||
BraiinsOSError,
|
||||
InnosiliconError,
|
||||
MinerData,
|
||||
WhatsminerError,
|
||||
X19Error,
|
||||
)
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic.miners.base import AnyMiner, DataOptions
|
||||
from pyasic.miners.miner_factory import MinerFactory, miner_factory
|
||||
from pyasic.miners.miner_listener import MinerListener
|
||||
from pyasic.miners import *
|
||||
from pyasic.network import MinerNetwork
|
||||
from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
from pyasic.rpc.unknown import UnknownRPCAPI
|
||||
|
||||
__all__ = [
|
||||
"BMMinerRPCAPI",
|
||||
"BOSMinerRPCAPI",
|
||||
"BTMinerRPCAPI",
|
||||
"CGMinerRPCAPI",
|
||||
"UnknownRPCAPI",
|
||||
"MinerConfig",
|
||||
"MinerData",
|
||||
"BraiinsOSError",
|
||||
"InnosiliconError",
|
||||
"WhatsminerError",
|
||||
"X19Error",
|
||||
"APIError",
|
||||
"APIWarning",
|
||||
"get_miner",
|
||||
"AnyMiner",
|
||||
"DataOptions",
|
||||
"MinerFactory",
|
||||
"miner_factory",
|
||||
"MinerListener",
|
||||
"MinerNetwork",
|
||||
"settings",
|
||||
]
|
||||
from pyasic.rpc import *
|
||||
from pyasic.ssh import *
|
||||
from pyasic.web import *
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from copy import deepcopy
|
||||
from dataclasses import asdict, dataclass, field
|
||||
|
||||
from pyasic.config.fans import FanModeConfig
|
||||
@@ -21,6 +20,7 @@ from pyasic.config.mining import MiningModeConfig
|
||||
from pyasic.config.pools import PoolConfig
|
||||
from pyasic.config.power_scaling import PowerScalingConfig
|
||||
from pyasic.config.temperature import TemperatureConfig
|
||||
from pyasic.misc import merge_dicts
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -93,7 +93,7 @@ class MinerConfig:
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**merge(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**merge_dicts(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**self.mining_mode.as_bosminer(),
|
||||
**self.pools.as_bosminer(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_bosminer(),
|
||||
@@ -110,13 +110,21 @@ class MinerConfig:
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_epic(),
|
||||
**self.temperature.as_epic(),
|
||||
**merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
|
||||
**self.mining_mode.as_epic(),
|
||||
**self.pools.as_epic(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_epic(),
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_auradine(),
|
||||
**self.temperature.as_auradine(),
|
||||
**self.mining_mode.as_auradine(),
|
||||
**self.pools.as_auradine(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_auradine(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
@@ -189,13 +197,10 @@ class MinerConfig:
|
||||
mining_mode=MiningModeConfig.from_vnish(web_settings),
|
||||
)
|
||||
|
||||
|
||||
def merge(a: dict, b: dict) -> dict:
|
||||
result = deepcopy(a)
|
||||
for b_key, b_val in b.items():
|
||||
a_val = result.get(b_key)
|
||||
if isinstance(a_val, dict) and isinstance(b_val, dict):
|
||||
result[b_key] = merge(a_val, b_val)
|
||||
else:
|
||||
result[b_key] = deepcopy(b_val)
|
||||
return result
|
||||
@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"]),
|
||||
)
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
|
||||
class MinerConfigOption(Enum):
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
return cls.default()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -53,6 +54,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)
|
||||
|
||||
@@ -64,10 +68,10 @@ class MinerConfigOption(Enum):
|
||||
@dataclass
|
||||
class MinerConfigValue:
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
return cls()
|
||||
|
||||
def as_dict(self):
|
||||
def as_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -99,3 +103,6 @@ class MinerConfigValue:
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
@@ -26,7 +27,7 @@ class FanModeNormal(MinerConfigValue):
|
||||
minimum_speed: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeNormal":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("minimum_fans") is not None:
|
||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||
@@ -35,7 +36,7 @@ class FanModeNormal(MinerConfigValue):
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_cooling_settings: dict):
|
||||
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeNormal":
|
||||
cls_conf = {}
|
||||
if web_cooling_settings.get("fan_min_count") is not None:
|
||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||
@@ -49,6 +50,17 @@ class FanModeNormal(MinerConfigValue):
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "auto"}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {
|
||||
"fans": {
|
||||
"Auto": {
|
||||
"Idle Speed": self.minimum_speed
|
||||
if not self.minimum_speed == 0
|
||||
else 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeManual(MinerConfigValue):
|
||||
@@ -57,7 +69,7 @@ class FanModeManual(MinerConfigValue):
|
||||
minimum_fans: int = 1
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeManual":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeManual":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("speed") is not None:
|
||||
cls_conf["speed"] = dict_conf["speed"]
|
||||
@@ -92,13 +104,19 @@ class FanModeManual(MinerConfigValue):
|
||||
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": self.speed}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"fans": {"Manual": {"speed": self.speed}}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
mode: str = field(init=False, default="immersion")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeImmersion":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -107,6 +125,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
|
||||
@@ -118,7 +139,7 @@ class FanModeConfig(MinerConfigOption):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
@@ -126,9 +147,9 @@ class FanModeConfig(MinerConfigOption):
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict):
|
||||
@@ -202,3 +223,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()
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
@@ -34,7 +35,7 @@ class MiningModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeNormal":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -43,13 +44,19 @@ class MiningModeNormal(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": self.mode}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"enabled": False}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
mode: str = field(init=False, default="sleep")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeSleep":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -58,13 +65,19 @@ class MiningModeSleep(MinerConfigValue):
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"sleep": "on"}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"algo": "Sleep", "target": 0}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="low")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeLPM":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -73,30 +86,74 @@ 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):
|
||||
mode: str = field(init=False, default="high")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHPM":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "turbo"}}
|
||||
|
||||
|
||||
class StandardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return VOptAlgo().as_epic()
|
||||
|
||||
|
||||
class VOptAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "VoltageOptimizer"
|
||||
|
||||
|
||||
class ChipTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "ChipTune"
|
||||
|
||||
|
||||
class TunerAlgo(MinerConfigOption):
|
||||
standard = StandardTuneAlgo
|
||||
voltage_optimizer = VOptAlgo
|
||||
chip_tune = ChipTuneAlgo
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.standard()
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="power_tuning")
|
||||
power: int = None
|
||||
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModePowerTune":
|
||||
return cls(dict_conf.get("power"))
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("power"):
|
||||
cls_conf["power"] = dict_conf["power"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = dict_conf["algo"]
|
||||
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
@@ -123,14 +180,18 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
),
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="hashrate_tuning")
|
||||
hashrate: int = None
|
||||
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHashrateTune":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
|
||||
return cls(dict_conf.get("hashrate"))
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -152,6 +213,12 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
)
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
@@ -159,7 +226,7 @@ class ManualBoardSettings(MinerConfigValue):
|
||||
volt: float
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "ManualBoardSettings":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
|
||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
@@ -172,10 +239,10 @@ class MiningModeManual(MinerConfigValue):
|
||||
|
||||
global_freq: float
|
||||
global_volt: float
|
||||
boards: Dict[int, ManualBoardSettings] = field(default_factory=dict)
|
||||
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
|
||||
return cls(
|
||||
global_freq=dict_conf["global_freq"],
|
||||
global_volt=dict_conf["global_volt"],
|
||||
@@ -214,7 +281,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
@@ -222,9 +289,9 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict):
|
||||
@@ -243,20 +310,18 @@ class MiningModeConfig(MinerConfigOption):
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
work_mode = web_conf["PerpetualTune"]["Running"]
|
||||
if work_mode:
|
||||
if (
|
||||
web_conf["PerpetualTune"]["Algorithm"].get("VoltageOptimizer")
|
||||
is not None
|
||||
):
|
||||
tuner_running = web_conf["PerpetualTune"]["Running"]
|
||||
if tuner_running:
|
||||
algo_info = web_conf["PerpetualTune"]["Algorithm"]
|
||||
if algo_info.get("VoltageOptimizer") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
web_conf["PerpetualTune"]["Algorithm"]["VoltageOptimizer"][
|
||||
"Target"
|
||||
]
|
||||
hashrate=algo_info["VoltageOptimizer"]["Target"],
|
||||
algo=TunerAlgo.voltage_optimizer,
|
||||
)
|
||||
else:
|
||||
return cls.hashrate_tuning(
|
||||
web_conf["PerpetualTune"]["Algorithm"]["ChipTune"]["Target"]
|
||||
hashrate=algo_info["ChipTune"]["Target"],
|
||||
algo=TunerAlgo.chip_tune,
|
||||
)
|
||||
else:
|
||||
return cls.normal()
|
||||
@@ -330,3 +395,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()
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Union
|
||||
from typing import List
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
@@ -27,7 +29,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 +38,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 +51,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 +64,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 +73,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 +91,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,8 +100,26 @@ 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}
|
||||
|
||||
def as_epic(self, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"pool": self.url,
|
||||
"login": f"{self.user}{user_suffix}",
|
||||
"password": self.password,
|
||||
}
|
||||
return {"pool": self.url, "login": self.user, "password": self.password}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
||||
return cls(
|
||||
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
||||
)
|
||||
@@ -160,7 +180,7 @@ class Pool(MinerConfigValue):
|
||||
|
||||
@dataclass
|
||||
class PoolGroup(MinerConfigValue):
|
||||
pools: List[Pool] = field(default_factory=list)
|
||||
pools: list[Pool] = field(default_factory=list)
|
||||
quota: int = 1
|
||||
name: str = None
|
||||
|
||||
@@ -210,7 +230,7 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
if len(self.pools) > 0:
|
||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return Pool("", "", "").as_avalon()
|
||||
@@ -241,8 +261,14 @@ 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]
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
|
||||
if dict_conf.get("quota") is not None:
|
||||
@@ -296,7 +322,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"]],
|
||||
@@ -318,14 +344,14 @@ class PoolConfig(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolConfig":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PoolConfig":
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
||||
|
||||
@classmethod
|
||||
def simple(cls, pools: List[Union[Pool, Dict[str, str]]]) -> "PoolConfig":
|
||||
def simple(cls, pools: list[Pool | dict[str, str]]) -> "PoolConfig":
|
||||
group_pools = []
|
||||
for pool in pools:
|
||||
if isinstance(pool, dict):
|
||||
@@ -373,6 +399,32 @@ 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()}}
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {
|
||||
"pools": {
|
||||
"coin": "Btc",
|
||||
"stratum_configs": self.groups[0].as_epic(user_suffix=user_suffix),
|
||||
"unique_id": False,
|
||||
}
|
||||
}
|
||||
return {
|
||||
"pools": {
|
||||
"coin": "Btc",
|
||||
"stratum_configs": [PoolGroup().as_epic()],
|
||||
"unique_id": False,
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
@@ -417,7 +469,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=[
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
@@ -31,7 +32,7 @@ class PowerScalingShutdownEnabled(MinerConfigValue):
|
||||
duration: int = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownEnabled":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownEnabled":
|
||||
return cls(duration=dict_conf.get("duration"))
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
@@ -51,7 +52,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="disabled")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownDisabled":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownDisabled":
|
||||
return cls()
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
@@ -66,7 +67,7 @@ class PowerScalingShutdown(MinerConfigOption):
|
||||
disabled = PowerScalingShutdownDisabled
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
@@ -107,9 +108,7 @@ class PowerScalingEnabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="enabled")
|
||||
power_step: int = None
|
||||
minimum_power: int = None
|
||||
shutdown_enabled: Union[
|
||||
PowerScalingShutdownEnabled, PowerScalingShutdownDisabled
|
||||
] = None
|
||||
shutdown_enabled: PowerScalingShutdownEnabled | PowerScalingShutdownDisabled = None
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
|
||||
@@ -122,7 +121,7 @@ class PowerScalingEnabled(MinerConfigValue):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingEnabled":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingEnabled":
|
||||
cls_conf = {
|
||||
"power_step": dict_conf.get("power_step"),
|
||||
"minimum_power": dict_conf.get("minimum_power"),
|
||||
@@ -175,7 +174,7 @@ class PowerScalingConfig(MinerConfigOption):
|
||||
return cls.disabled()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
@@ -39,8 +40,18 @@ class TemperatureConfig(MinerConfigValue):
|
||||
temp_cfg["dangerous_temp"] = self.danger
|
||||
return {"temp_control": temp_cfg}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
temps_config = {"temps": {}, "fans": {"Auto": {}}}
|
||||
if self.target is not None:
|
||||
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
|
||||
else:
|
||||
temps_config["fans"]["Auto"]["Target Temperature"] = 60
|
||||
if self.danger is not None:
|
||||
temps_config["temps"]["shutdown"] = self.danger
|
||||
return temps_config
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "TemperatureConfig":
|
||||
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
|
||||
return cls(
|
||||
target=dict_conf.get("target"),
|
||||
hot=dict_conf.get("hot"),
|
||||
@@ -72,7 +83,7 @@ class TemperatureConfig(MinerConfigValue):
|
||||
return cls(target=target_temp, danger=dangerous_temp)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict):
|
||||
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
|
||||
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
|
||||
@@ -81,7 +92,7 @@ class TemperatureConfig(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict):
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
temperature_conf = grpc_miner_conf["temperature"]
|
||||
except KeyError:
|
||||
|
||||
@@ -24,73 +24,9 @@ from typing import Any, List, Union
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
|
||||
from .boards import HashBoard
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
|
||||
|
||||
@dataclass
|
||||
class HashBoard:
|
||||
"""A Dataclass to standardize hashboard data.
|
||||
|
||||
Attributes:
|
||||
slot: The slot of the board as an int.
|
||||
hashrate: The hashrate of the board in TH/s as a float.
|
||||
temp: The temperature of the PCB as an int.
|
||||
chip_temp: The temperature of the chips as an int.
|
||||
chips: The chip count of the board as an int.
|
||||
expected_chips: The expected chip count of the board as an int.
|
||||
serial_number: The serial number of the board.
|
||||
missing: Whether the board is returned from the miners data as a bool.
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: float = None
|
||||
temp: int = None
|
||||
chip_temp: int = None
|
||||
chips: int = None
|
||||
expected_chips: int = None
|
||||
serial_number: str = None
|
||||
missing: bool = True
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fan:
|
||||
"""A Dataclass to standardize fan data.
|
||||
|
||||
Attributes:
|
||||
speed: The speed of the fan.
|
||||
"""
|
||||
|
||||
speed: int = None
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
from .fans import Fan
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
58
pyasic/data/boards.py
Normal file
58
pyasic/data/boards.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class HashBoard:
|
||||
"""A Dataclass to standardize hashboard data.
|
||||
|
||||
Attributes:
|
||||
slot: The slot of the board as an int.
|
||||
hashrate: The hashrate of the board in TH/s as a float.
|
||||
temp: The temperature of the PCB as an int.
|
||||
chip_temp: The temperature of the chips as an int.
|
||||
chips: The chip count of the board as an int.
|
||||
expected_chips: The expected chip count of the board as an int.
|
||||
serial_number: The serial number of the board.
|
||||
missing: Whether the board is returned from the miners data as a bool.
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: float = None
|
||||
temp: int = None
|
||||
chip_temp: int = None
|
||||
chips: int = None
|
||||
expected_chips: int = None
|
||||
serial_number: str = None
|
||||
missing: bool = True
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
44
pyasic/data/fans.py
Normal file
44
pyasic/data/fans.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fan:
|
||||
"""A Dataclass to standardize fan data.
|
||||
|
||||
Attributes:
|
||||
speed: The speed of the fan.
|
||||
"""
|
||||
|
||||
speed: int = None
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
@@ -20,7 +20,7 @@ from typing import List, Union
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners import AnyMiner
|
||||
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
||||
from pyasic.miners.types import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
||||
from pyasic.miners.models import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
||||
|
||||
FAN_USAGE = 50 # 50 W per fan
|
||||
|
||||
|
||||
@@ -14,13 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
from typing import Union
|
||||
|
||||
from pyasic.miners.base import AnyMiner, BaseMiner
|
||||
from pyasic.miners.miner_factory import miner_factory
|
||||
|
||||
|
||||
# abstracted version of get miner that is easier to access
|
||||
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
|
||||
return await miner_factory.get_miner(ip)
|
||||
from .base import AnyMiner
|
||||
from .data import DataOptions
|
||||
from .factory import get_miner, miner_factory
|
||||
from .listener import MinerListener
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro
|
||||
from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
|
||||
|
||||
|
||||
class BMMinerS17(AntminerOld, S17):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import T17, T17e, T17Plus
|
||||
from pyasic.miners.models import T17, T17e, T17Plus
|
||||
|
||||
|
||||
class BMMinerT17(AntminerOld, T17):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import (
|
||||
from pyasic.miners.models import (
|
||||
S19,
|
||||
S19L,
|
||||
S19XP,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import T19
|
||||
from pyasic.miners.models import T19
|
||||
|
||||
|
||||
class BMMinerT19(AntminerModern, T19):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import HS3
|
||||
from pyasic.miners.models import HS3
|
||||
|
||||
|
||||
class BMMinerHS3(AntminerModern, HS3):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import L3Plus
|
||||
from pyasic.miners.models import L3Plus
|
||||
|
||||
|
||||
class BMMinerL3Plus(AntminerOld, L3Plus):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import L7
|
||||
from pyasic.miners.models import L7
|
||||
|
||||
|
||||
class BMMinerL7(AntminerModern, L7):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import E9Pro
|
||||
from pyasic.miners.models import E9Pro
|
||||
|
||||
|
||||
class BMMinerE9Pro(AntminerModern, E9Pro):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BMMiner
|
||||
from pyasic.miners.types import S9, S9i, S9j
|
||||
from pyasic.miners.models import S9, S9i, S9j
|
||||
|
||||
|
||||
class BMMinerS9(BMMiner, S9):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BMMiner
|
||||
from pyasic.miners.types import T9
|
||||
from pyasic.miners.models import T9
|
||||
|
||||
|
||||
class BMMinerT9(BMMiner, T9):
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro
|
||||
from pyasic.miners.backends import BOSMiner
|
||||
from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
|
||||
|
||||
|
||||
class BOSMinerS17(BOSer, S17):
|
||||
class BOSMinerS17(BOSMiner, S17):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS17Plus(BOSer, S17Plus):
|
||||
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS17Pro(BOSer, S17Pro):
|
||||
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS17e(BOSer, S17e):
|
||||
class BOSMinerS17e(BOSMiner, S17e):
|
||||
pass
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.types import T17, T17e, T17Plus
|
||||
from pyasic.miners.backends import BOSMiner
|
||||
from pyasic.miners.models import T17, T17e, T17Plus
|
||||
|
||||
|
||||
class BOSMinerT17(BOSer, T17):
|
||||
class BOSMinerT17(BOSMiner, T17):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerT17Plus(BOSer, T17Plus):
|
||||
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerT17e(BOSer, T17e):
|
||||
class BOSMinerT17e(BOSMiner, T17e):
|
||||
pass
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.types import (
|
||||
from pyasic.miners.models import (
|
||||
S19,
|
||||
S19XP,
|
||||
S19a,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.types import T19
|
||||
from pyasic.miners.models import T19
|
||||
|
||||
|
||||
class BOSMinerT19(BOSer, T19):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import BOSMiner
|
||||
from pyasic.miners.types import S9
|
||||
from pyasic.miners.models import S9
|
||||
|
||||
|
||||
class BOSMinerS9(BOSMiner, S9):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import Z15
|
||||
from pyasic.miners.models import Z15
|
||||
|
||||
|
||||
class CGMinerZ15(AntminerOld, Z15):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import D3
|
||||
from pyasic.miners.models import D3
|
||||
|
||||
|
||||
class CGMinerD3(AntminerOld, D3):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AntminerOld
|
||||
from pyasic.miners.types import DR5
|
||||
from pyasic.miners.models import DR5
|
||||
|
||||
|
||||
class CGMinerDR5(AntminerOld, DR5):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import ePIC
|
||||
from pyasic.miners.types import S19, S19XP, S19j, S19jPro, S19jProPlus, S19kPro, S19Pro
|
||||
from pyasic.miners.models import S19, S19XP, S19j, S19jPro, S19jProPlus, S19kPro, S19Pro
|
||||
|
||||
|
||||
class ePICS19(ePIC, S19):
|
||||
|
||||
@@ -21,46 +21,46 @@ import asyncssh
|
||||
from pyasic.data import HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import Hiveon
|
||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.types import T9
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.models import T9
|
||||
|
||||
HIVEON_T9_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -84,15 +84,15 @@ class HiveonT9(Hiveon, T9):
|
||||
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=board, expected_chips=self.expected_chips)
|
||||
for board in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = self.api.stats()
|
||||
rpc_stats = self.rpc.stats()
|
||||
except APIError:
|
||||
return []
|
||||
|
||||
@@ -108,8 +108,8 @@ class HiveonT9(Hiveon, T9):
|
||||
for chipset in board_map[board]:
|
||||
if hashboards[board].chip_temp is None:
|
||||
try:
|
||||
hashboards[board].temp = api_stats["STATS"][1][f"temp{chipset}"]
|
||||
hashboards[board].chip_temp = api_stats["STATS"][1][
|
||||
hashboards[board].temp = rpc_stats["STATS"][1][f"temp{chipset}"]
|
||||
hashboards[board].chip_temp = rpc_stats["STATS"][1][
|
||||
f"temp2_{chipset}"
|
||||
]
|
||||
except (KeyError, IndexError):
|
||||
@@ -117,8 +117,8 @@ class HiveonT9(Hiveon, T9):
|
||||
else:
|
||||
hashboards[board].missing = False
|
||||
try:
|
||||
hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"]
|
||||
chips += api_stats["STATS"][1][f"chain_acn{chipset}"]
|
||||
hashrate += rpc_stats["STATS"][1][f"chain_rate{chipset}"]
|
||||
chips += rpc_stats["STATS"][1][f"chain_acn{chipset}"]
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
hashboards[board].hashrate = round(hashrate / 1000, 2)
|
||||
@@ -126,15 +126,15 @@ class HiveonT9(Hiveon, T9):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if not rpc_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
boards = api_stats.get("STATS")
|
||||
if rpc_stats:
|
||||
boards = rpc_stats.get("STATS")
|
||||
try:
|
||||
wattage_raw = boards[1]["chain_power"]
|
||||
except (KeyError, IndexError):
|
||||
@@ -143,23 +143,23 @@ class HiveonT9(Hiveon, T9):
|
||||
# parse wattage position out of raw data
|
||||
return round(float(wattage_raw.split(" ")[0]))
|
||||
|
||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
||||
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
env_temp_list = []
|
||||
board_map = {
|
||||
0: [2, 9, 10],
|
||||
1: [3, 11, 12],
|
||||
2: [4, 13, 14],
|
||||
}
|
||||
if not api_stats:
|
||||
if not rpc_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
if api_stats:
|
||||
if rpc_stats:
|
||||
for board in board_map.values():
|
||||
for chipset in board:
|
||||
try:
|
||||
env_temp = api_stats["STATS"][1][f"temp3_{chipset}"]
|
||||
env_temp = rpc_stats["STATS"][1][f"temp3_{chipset}"]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
except (KeyError, IndexError):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import LUXMiner
|
||||
from pyasic.miners.types import S9
|
||||
from pyasic.miners.models import S9
|
||||
|
||||
|
||||
class LUXMinerS9(LUXMiner, S9):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import VNish
|
||||
from pyasic.miners.types import S17Plus, S17Pro
|
||||
from pyasic.miners.models import S17Plus, S17Pro
|
||||
|
||||
|
||||
class VNishS17Plus(VNish, S17Plus):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import VNish
|
||||
from pyasic.miners.types import (
|
||||
from pyasic.miners.models import (
|
||||
S19,
|
||||
S19XP,
|
||||
S19a,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import VNish
|
||||
from pyasic.miners.types import T19
|
||||
from pyasic.miners.models import T19
|
||||
|
||||
|
||||
class VNishT19(VNish, T19):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import VNish
|
||||
from pyasic.miners.types import L3Plus
|
||||
from pyasic.miners.models import L3Plus
|
||||
|
||||
|
||||
class VnishL3Plus(VNish, L3Plus):
|
||||
|
||||
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.models 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.models 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.models 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.models 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.models 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.models 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 *
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1026
|
||||
from pyasic.miners.models import Avalon1026
|
||||
|
||||
|
||||
class CGMinerAvalon1026(AvalonMiner, Avalon1026):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1047
|
||||
from pyasic.miners.models import Avalon1047
|
||||
|
||||
|
||||
class CGMinerAvalon1047(AvalonMiner, Avalon1047):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1066
|
||||
from pyasic.miners.models import Avalon1066
|
||||
|
||||
|
||||
class CGMinerAvalon1066(AvalonMiner, Avalon1066):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1166Pro
|
||||
from pyasic.miners.models import Avalon1166Pro
|
||||
|
||||
|
||||
class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1246
|
||||
from pyasic.miners.models import Avalon1246
|
||||
|
||||
|
||||
class CGMinerAvalon1246(AvalonMiner, Avalon1246):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon721
|
||||
from pyasic.miners.models import Avalon721
|
||||
|
||||
|
||||
class CGMinerAvalon721(AvalonMiner, Avalon721):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon741
|
||||
from pyasic.miners.models import Avalon741
|
||||
|
||||
|
||||
class CGMinerAvalon741(AvalonMiner, Avalon741):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon761
|
||||
from pyasic.miners.models import Avalon761
|
||||
|
||||
|
||||
class CGMinerAvalon761(AvalonMiner, Avalon761):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon821
|
||||
from pyasic.miners.models import Avalon821
|
||||
|
||||
|
||||
class CGMinerAvalon821(AvalonMiner, Avalon821):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon841
|
||||
from pyasic.miners.models import Avalon841
|
||||
|
||||
|
||||
class CGMinerAvalon841(AvalonMiner, Avalon841):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon851
|
||||
from pyasic.miners.models import Avalon851
|
||||
|
||||
|
||||
class CGMinerAvalon851(AvalonMiner, Avalon851):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon921
|
||||
from pyasic.miners.models import Avalon921
|
||||
|
||||
|
||||
class CGMinerAvalon921(AvalonMiner, Avalon921):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,16 +19,16 @@ from typing import List, Optional, Union
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.miners.base import (
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc import APIError
|
||||
from pyasic.ssh.antminer import AntminerModernSSH
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
@@ -40,11 +40,11 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
@@ -52,15 +52,15 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
@@ -76,7 +76,7 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -206,13 +206,13 @@ class AntminerModern(BMMiner):
|
||||
]
|
||||
|
||||
try:
|
||||
api_stats = await self.api.send_command("stats", new_api=True)
|
||||
rpc_stats = await self.rpc.send_command("stats", new_rpc=True)
|
||||
except APIError:
|
||||
return hashboards
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
for board in api_stats["STATS"][0]["chain"]:
|
||||
for board in rpc_stats["STATS"][0]["chain"]:
|
||||
hashboards[board["index"]].hashrate = round(
|
||||
board["rate_real"] / 1000, 2
|
||||
)
|
||||
@@ -254,18 +254,18 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if api_stats is None:
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
@@ -336,16 +336,16 @@ class AntminerModern(BMMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -354,11 +354,11 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
@@ -366,15 +366,15 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
@@ -386,7 +386,7 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -479,47 +479,47 @@ class AntminerOld(CGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if api_stats is None:
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num + 2
|
||||
if fan_offset == -1:
|
||||
fan_offset = 3
|
||||
|
||||
for fan in range(self.expected_fans):
|
||||
fans_data[fan].speed = api_stats["STATS"][1].get(
|
||||
fans_data[fan].speed = rpc_stats["STATS"][1].get(
|
||||
f"fan{fan_offset+fan}", 0
|
||||
)
|
||||
except LookupError:
|
||||
pass
|
||||
return fans_data
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
boards = rpc_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
@@ -574,27 +574,27 @@ class AntminerOld(CGMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
api_summary = None
|
||||
rpc_summary = None
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if not api_summary == {}:
|
||||
if rpc_summary is not None:
|
||||
if not rpc_summary == {}:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
387
pyasic/miners/backends/auradine.py
Normal file
387
pyasic/miners/backends/auradine.py
Normal file
@@ -0,0 +1,387 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc.gcminer import GCMinerRPCAPI
|
||||
from pyasic.web.auradine import AuradineWebAPI
|
||||
|
||||
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("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("rpc_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("rpc_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"""
|
||||
|
||||
_rpc_cls = GCMinerRPCAPI
|
||||
rpc: GCMinerRPCAPI
|
||||
_web_cls = AuradineWebAPI
|
||||
web: AuradineWebAPI
|
||||
|
||||
data_locations = AURADINE_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
supports_autotuning = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
try:
|
||||
await self.web.set_led(code=int(AuradineLEDCodes.LOCATE_MINER))
|
||||
return True
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
try:
|
||||
await self.web.set_led(code=int(AuradineLEDCodes.NORMAL))
|
||||
return True
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
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, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(rpc_summary["SUMMARY"][0]["MHS 5s"]) / 1000000), 2
|
||||
)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(
|
||||
self, rpc_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 rpc_devs is None:
|
||||
try:
|
||||
rpc_devs = await self.rpc.devs()
|
||||
except APIError:
|
||||
pass
|
||||
if web_ipreport is None:
|
||||
try:
|
||||
web_ipreport = await self.web.ipreport()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
for board in rpc_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, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return rpc_summary["SUMMARY"][0]["Elapsed"]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -20,53 +20,53 @@ from typing import List, Optional
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
|
||||
AVALON_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
[RPCAPICommand("rpc_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -79,7 +79,7 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
try:
|
||||
data = await self.api.ascset(0, "led", "1-1")
|
||||
data = await self.rpc.ascset(0, "led", "1-1")
|
||||
except APIError:
|
||||
return False
|
||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||
@@ -88,7 +88,7 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
try:
|
||||
data = await self.api.ascset(0, "led", "1-0")
|
||||
data = await self.rpc.ascset(0, "led", "1-0")
|
||||
except APIError:
|
||||
return False
|
||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||
@@ -97,7 +97,7 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
try:
|
||||
data = await self.api.restart()
|
||||
data = await self.rpc.restart()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
@@ -155,16 +155,16 @@ class AvalonMiner(CGMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_mac(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
base_mac = api_version["VERSION"][0]["MAC"]
|
||||
base_mac = rpc_version["VERSION"][0]["MAC"]
|
||||
base_mac = base_mac.upper()
|
||||
mac = ":".join(
|
||||
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
||||
@@ -173,34 +173,34 @@ class AvalonMiner(CGMiner):
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||
if api_devs is None:
|
||||
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
rpc_devs = await self.rpc.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs is not None:
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
|
||||
return round(float(rpc_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
return hashboards
|
||||
@@ -234,62 +234,62 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if api_stats is None:
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return round(float(parsed_stats["GHSmm"]) / 1000, 2)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
||||
if api_stats is None:
|
||||
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return float(parsed_stats["Temp"])
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_wattage_limit(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return int(parsed_stats["MPO"])
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if api_stats is None:
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
except LookupError:
|
||||
return fans_data
|
||||
@@ -301,18 +301,18 @@ class AvalonMiner(CGMiner):
|
||||
pass
|
||||
return fans_data
|
||||
|
||||
async def _get_fault_light(self, api_stats: dict = None) -> Optional[bool]:
|
||||
async def _get_fault_light(self, rpc_stats: dict = None) -> Optional[bool]:
|
||||
if self.light:
|
||||
return self.light
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
led = int(parsed_stats["Led"])
|
||||
return True if led == 1 else False
|
||||
@@ -320,7 +320,7 @@ class AvalonMiner(CGMiner):
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.api.ascset(0, "led", "1-255")
|
||||
data = await self.rpc.ascset(0, "led", "1-255")
|
||||
except APIError:
|
||||
return False
|
||||
try:
|
||||
|
||||
@@ -19,40 +19,35 @@ from typing import List, Optional
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
|
||||
|
||||
BFGMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -61,15 +56,15 @@ BFGMINER_DATA_LOC = DataLocations(
|
||||
class BFGMiner(BaseMiner):
|
||||
"""Base handler for BFGMiner based miners."""
|
||||
|
||||
_api_cls = BFGMinerRPCAPI
|
||||
api: BFGMinerRPCAPI
|
||||
_rpc_cls = BFGMinerRPCAPI
|
||||
rpc: BFGMinerRPCAPI
|
||||
|
||||
data_locations = BFGMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
pools = await self.api.pools()
|
||||
pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
return self.config
|
||||
|
||||
@@ -80,63 +75,63 @@ class BFGMiner(BaseMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
boards = rpc_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
@@ -178,28 +173,28 @@ class BFGMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if api_stats is None:
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [None, None, None, None]
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
for fan_num in range(0, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||
if not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
|
||||
for fan in range(self.expected_fans):
|
||||
fans_data[fan] = api_stats["STATS"][1].get(
|
||||
fans_data[fan] = rpc_stats["STATS"][1].get(
|
||||
f"fan{fan_offset+fan}", 0
|
||||
)
|
||||
except LookupError:
|
||||
@@ -208,19 +203,19 @@ class BFGMiner(BaseMiner):
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
# X19 method, not sure compatibility
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
|
||||
@@ -19,44 +19,39 @@ from typing import List, Optional
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
|
||||
BMMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -65,15 +60,15 @@ BMMINER_DATA_LOC = DataLocations(
|
||||
class BMMiner(BaseMiner):
|
||||
"""Base handler for BMMiner based miners."""
|
||||
|
||||
_api_cls = BMMinerRPCAPI
|
||||
api: BMMinerRPCAPI
|
||||
_rpc_cls = BMMinerRPCAPI
|
||||
rpc: BMMinerRPCAPI
|
||||
|
||||
data_locations = BMMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
pools = await self.api.pools()
|
||||
pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
return self.config
|
||||
|
||||
@@ -84,63 +79,63 @@ class BMMiner(BaseMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
boards = rpc_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
@@ -195,28 +190,28 @@ class BMMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if api_stats is None:
|
||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
|
||||
for fan in range(self.expected_fans):
|
||||
fans[fan].speed = api_stats["STATS"][1].get(
|
||||
fans[fan].speed = rpc_stats["STATS"][1].get(
|
||||
f"fan{fan_offset+fan}", 0
|
||||
)
|
||||
except LookupError:
|
||||
@@ -224,19 +219,19 @@ class BMMiner(BaseMiner):
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
# X19 method, not sure compatibility
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
@@ -248,15 +243,15 @@ class BMMiner(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -24,12 +24,11 @@ from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
GRPCCommand,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
@@ -45,7 +44,7 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
@@ -53,43 +52,43 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
[RPCAPICommand("rpc_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("api_temps", "temps"),
|
||||
RPCAPICommand("api_devdetails", "devdetails"),
|
||||
RPCAPICommand("api_devs", "devs"),
|
||||
RPCAPICommand("rpc_temps", "temps"),
|
||||
RPCAPICommand("rpc_devdetails", "devdetails"),
|
||||
RPCAPICommand("rpc_devs", "devs"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
[RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
[RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_fans", "fans")],
|
||||
[RPCAPICommand("rpc_fans", "fans")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
[RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_devdetails", "devdetails")],
|
||||
[RPCAPICommand("rpc_devdetails", "devdetails")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -98,8 +97,8 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
class BOSMiner(BaseMiner):
|
||||
"""Handler for old versions of BraiinsOS+ (pre-gRPC)"""
|
||||
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
api: BOSMinerRPCAPI
|
||||
_rpc_cls = BOSMinerRPCAPI
|
||||
rpc: BOSMinerRPCAPI
|
||||
_web_cls = BOSMinerWebAPI
|
||||
web: BOSMinerWebAPI
|
||||
_ssh_cls = BOSMinerSSH
|
||||
@@ -140,7 +139,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
try:
|
||||
data = await self.api.pause()
|
||||
data = await self.rpc.pause()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
@@ -151,7 +150,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
try:
|
||||
data = await self.api.resume()
|
||||
data = await self.rpc.resume()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
@@ -275,7 +274,7 @@ class BOSMiner(BaseMiner):
|
||||
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
|
||||
if web_net_conf is None:
|
||||
try:
|
||||
web_net_conf = await self.web.luci.get_net_conf()
|
||||
web_net_conf = await self.web.get_net_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -293,28 +292,28 @@ class BOSMiner(BaseMiner):
|
||||
# if result:
|
||||
# return result.upper().strip()
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
# Now get the API version
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
api_ver = api_version["VERSION"][0]["API"]
|
||||
rpc_ver = rpc_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
api_ver = None
|
||||
self.api_ver = api_ver
|
||||
self.api.api_ver = self.api_ver
|
||||
rpc_ver = None
|
||||
self.api_ver = rpc_ver
|
||||
self.rpc.rpc_ver = self.api_ver
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
|
||||
if web_bos_info is None:
|
||||
try:
|
||||
web_bos_info = await self.web.luci.get_bos_info()
|
||||
web_bos_info = await self.web.get_bos_info()
|
||||
except APIError:
|
||||
return None
|
||||
|
||||
@@ -334,29 +333,31 @@ 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
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(
|
||||
self,
|
||||
api_temps: dict = None,
|
||||
api_devdetails: dict = None,
|
||||
api_devs: dict = None,
|
||||
rpc_temps: dict = None,
|
||||
rpc_devdetails: dict = None,
|
||||
rpc_devs: dict = None,
|
||||
) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
@@ -364,34 +365,34 @@ class BOSMiner(BaseMiner):
|
||||
]
|
||||
|
||||
cmds = []
|
||||
if api_temps is None:
|
||||
if rpc_temps is None:
|
||||
cmds.append("temps")
|
||||
if api_devdetails is None:
|
||||
if rpc_devdetails is None:
|
||||
cmds.append("devdetails")
|
||||
if api_devs is None:
|
||||
if rpc_devs is None:
|
||||
cmds.append("devs")
|
||||
if len(cmds) > 0:
|
||||
try:
|
||||
d = await self.api.multicommand(*cmds)
|
||||
d = await self.rpc.multicommand(*cmds)
|
||||
except APIError:
|
||||
d = {}
|
||||
try:
|
||||
api_temps = d["temps"][0]
|
||||
rpc_temps = d["temps"][0]
|
||||
except LookupError:
|
||||
api_temps = None
|
||||
rpc_temps = None
|
||||
try:
|
||||
api_devdetails = d["devdetails"][0]
|
||||
rpc_devdetails = d["devdetails"][0]
|
||||
except (KeyError, IndexError):
|
||||
api_devdetails = None
|
||||
rpc_devdetails = None
|
||||
try:
|
||||
api_devs = d["devs"][0]
|
||||
rpc_devs = d["devs"][0]
|
||||
except LookupError:
|
||||
api_devs = None
|
||||
if api_temps is not None:
|
||||
rpc_devs = None
|
||||
if rpc_temps is not None:
|
||||
try:
|
||||
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
|
||||
offset = 6 if rpc_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
for board in api_temps["TEMPS"]:
|
||||
for board in rpc_temps["TEMPS"]:
|
||||
_id = board["ID"] - offset
|
||||
chip_temp = round(board["Chip"])
|
||||
board_temp = round(board["Board"])
|
||||
@@ -400,11 +401,11 @@ class BOSMiner(BaseMiner):
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if api_devdetails is not None:
|
||||
if rpc_devdetails is not None:
|
||||
try:
|
||||
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
|
||||
offset = 6 if rpc_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
for board in api_devdetails["DEVDETAILS"]:
|
||||
for board in rpc_devdetails["DEVDETAILS"]:
|
||||
_id = board["ID"] - offset
|
||||
chips = board["Chips"]
|
||||
hashboards[_id].chips = chips
|
||||
@@ -412,11 +413,11 @@ class BOSMiner(BaseMiner):
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
if api_devs is not None:
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
|
||||
offset = 6 if rpc_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
for board in api_devs["DEVS"]:
|
||||
for board in rpc_devs["DEVS"]:
|
||||
_id = board["ID"] - offset
|
||||
hashrate = round(float(board["MHS 1m"] / 1000000), 2)
|
||||
hashboards[_id].hashrate = hashrate
|
||||
@@ -425,62 +426,62 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
|
||||
if api_tunerstatus is None:
|
||||
async def _get_wattage(self, rpc_tunerstatus: dict = None) -> Optional[int]:
|
||||
if rpc_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus is not None:
|
||||
if rpc_tunerstatus is not None:
|
||||
try:
|
||||
return api_tunerstatus["TUNERSTATUS"][0][
|
||||
return rpc_tunerstatus["TUNERSTATUS"][0][
|
||||
"ApproximateMinerPowerConsumption"
|
||||
]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]:
|
||||
if api_tunerstatus is None:
|
||||
async def _get_wattage_limit(self, rpc_tunerstatus: dict = None) -> Optional[int]:
|
||||
if rpc_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus is not None:
|
||||
if rpc_tunerstatus is not None:
|
||||
try:
|
||||
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
|
||||
return rpc_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
|
||||
if api_fans is None:
|
||||
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
||||
if rpc_fans is None:
|
||||
try:
|
||||
api_fans = await self.api.fans()
|
||||
rpc_fans = await self.rpc.fans()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_fans is not None:
|
||||
if rpc_fans is not None:
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(api_fans["FANS"][n]["RPM"]))
|
||||
fans.append(Fan(rpc_fans["FANS"][n]["RPM"]))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if api_tunerstatus is None:
|
||||
async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if rpc_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus is not None:
|
||||
if rpc_tunerstatus is not None:
|
||||
errors = []
|
||||
try:
|
||||
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
chain_status = rpc_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
if chain_status and len(chain_status) > 0:
|
||||
offset = (
|
||||
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
|
||||
@@ -512,18 +513,18 @@ class BOSMiner(BaseMiner):
|
||||
except (TypeError, AttributeError):
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||
if api_devs is None:
|
||||
async def _get_expected_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
rpc_devs = await self.rpc.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs is not None:
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
hr_list = []
|
||||
|
||||
for board in api_devs["DEVS"]:
|
||||
for board in rpc_devs["DEVS"]:
|
||||
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
|
||||
if expected_hashrate:
|
||||
hr_list.append(expected_hashrate)
|
||||
@@ -536,31 +537,31 @@ class BOSMiner(BaseMiner):
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||
if api_devdetails is None:
|
||||
async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
|
||||
if rpc_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.send_command(
|
||||
rpc_devdetails = await self.rpc.send_command(
|
||||
"devdetails", ignore_errors=True, allow_warning=False
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails is not None:
|
||||
if rpc_devdetails is not None:
|
||||
try:
|
||||
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -569,63 +570,63 @@ BOSER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[GRPCCommand("grpc_miner_details", "get_miner_details")],
|
||||
[WebAPICommand("grpc_miner_details", "get_miner_details")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[GRPCCommand("api_version", "get_api_version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[GRPCCommand("grpc_miner_details", "get_miner_details")],
|
||||
[WebAPICommand("grpc_miner_details", "get_miner_details")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[GRPCCommand("grpc_miner_details", "get_miner_details")],
|
||||
[WebAPICommand("grpc_miner_details", "get_miner_details")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[GRPCCommand("grpc_miner_details", "get_miner_details")],
|
||||
[WebAPICommand("grpc_miner_details", "get_miner_details")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[GRPCCommand("grpc_hashboards", "get_hashboards")],
|
||||
[WebAPICommand("grpc_hashboards", "get_hashboards")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[GRPCCommand("grpc_miner_stats", "get_miner_stats")],
|
||||
[WebAPICommand("grpc_miner_stats", "get_miner_stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[
|
||||
GRPCCommand(
|
||||
WebAPICommand(
|
||||
"grpc_active_performance_mode", "get_active_performance_mode"
|
||||
)
|
||||
],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[GRPCCommand("grpc_cooling_state", "get_cooling_state")],
|
||||
[WebAPICommand("grpc_cooling_state", "get_cooling_state")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
[RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_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(
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_devdetails", "devdetails")],
|
||||
[RPCAPICommand("rpc_devdetails", "devdetails")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -634,7 +635,7 @@ BOSER_DATA_LOC = DataLocations(
|
||||
class BOSer(BaseMiner):
|
||||
"""Handler for new versions of BraiinsOS+ (post-gRPC)"""
|
||||
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
_rpc_cls = BOSMinerRPCAPI
|
||||
web: BOSMinerRPCAPI
|
||||
_web_cls = BOSerWebAPI
|
||||
web: BOSerWebAPI
|
||||
@@ -645,13 +646,13 @@ class BOSer(BaseMiner):
|
||||
supports_shutdown = True
|
||||
|
||||
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):
|
||||
return True
|
||||
return False
|
||||
|
||||
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 == {}:
|
||||
return True
|
||||
return False
|
||||
@@ -660,37 +661,37 @@ class BOSer(BaseMiner):
|
||||
return await self.restart_boser()
|
||||
|
||||
async def restart_boser(self) -> bool:
|
||||
await self.web.grpc.restart()
|
||||
await self.web.restart()
|
||||
return True
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
try:
|
||||
await self.web.grpc.pause_mining()
|
||||
await self.web.pause_mining()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
try:
|
||||
await self.web.grpc.resume_mining()
|
||||
await self.web.resume_mining()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
ret = await self.web.grpc.reboot()
|
||||
ret = await self.web.reboot()
|
||||
if ret == {}:
|
||||
return True
|
||||
return False
|
||||
|
||||
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)
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
result = await self.web.grpc.set_power_target(wattage)
|
||||
result = await self.web.set_power_target(wattage)
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
@@ -708,7 +709,7 @@ class BOSer(BaseMiner):
|
||||
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
grpc_miner_details = await self.web.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -718,27 +719,27 @@ class BOSer(BaseMiner):
|
||||
except (LookupError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
api_ver = api_version["VERSION"][0]["API"]
|
||||
rpc_ver = rpc_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
api_ver = None
|
||||
self.api_ver = api_ver
|
||||
self.api.api_ver = self.api_ver
|
||||
rpc_ver = None
|
||||
self.api_ver = rpc_ver
|
||||
self.rpc.rpc_ver = self.api_ver
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
grpc_miner_details = await self.web.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -761,7 +762,7 @@ class BOSer(BaseMiner):
|
||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
grpc_miner_details = await self.web.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -771,16 +772,16 @@ class BOSer(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -789,7 +790,7 @@ class BOSer(BaseMiner):
|
||||
) -> Optional[float]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
grpc_miner_details = await self.web.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -807,7 +808,7 @@ class BOSer(BaseMiner):
|
||||
|
||||
if grpc_hashboards is None:
|
||||
try:
|
||||
grpc_hashboards = await self.web.grpc.get_hashboards()
|
||||
grpc_hashboards = await self.web.get_hashboards()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -838,7 +839,7 @@ class BOSer(BaseMiner):
|
||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
||||
if grpc_miner_stats is None:
|
||||
try:
|
||||
grpc_miner_stats = self.web.grpc.get_miner_stats()
|
||||
grpc_miner_stats = self.web.get_miner_stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -853,9 +854,7 @@ class BOSer(BaseMiner):
|
||||
) -> Optional[int]:
|
||||
if grpc_active_performance_mode is None:
|
||||
try:
|
||||
grpc_active_performance_mode = (
|
||||
self.web.grpc.get_active_performance_mode()
|
||||
)
|
||||
grpc_active_performance_mode = self.web.get_active_performance_mode()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -870,7 +869,7 @@ class BOSer(BaseMiner):
|
||||
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
||||
if grpc_cooling_state is None:
|
||||
try:
|
||||
grpc_cooling_state = self.web.grpc.get_cooling_state()
|
||||
grpc_cooling_state = self.web.get_cooling_state()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -884,17 +883,17 @@ class BOSer(BaseMiner):
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if api_tunerstatus is None:
|
||||
async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if rpc_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus is not None:
|
||||
if rpc_tunerstatus is not None:
|
||||
errors = []
|
||||
try:
|
||||
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
chain_status = rpc_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
if chain_status and len(chain_status) > 0:
|
||||
offset = (
|
||||
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
|
||||
@@ -920,9 +919,7 @@ class BOSer(BaseMiner):
|
||||
|
||||
if grpc_locate_device_status is None:
|
||||
try:
|
||||
grpc_locate_device_status = (
|
||||
await self.web.grpc.get_locate_device_status()
|
||||
)
|
||||
grpc_locate_device_status = await self.web.get_locate_device_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -934,30 +931,30 @@ class BOSer(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||
if api_devdetails is None:
|
||||
async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
|
||||
if rpc_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.send_command(
|
||||
rpc_devdetails = await self.rpc.send_command(
|
||||
"devdetails", ignore_errors=True, allow_warning=False
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails is not None:
|
||||
if rpc_devdetails is not None:
|
||||
try:
|
||||
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -21,13 +21,8 @@ from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
|
||||
BTMINER_DATA_LOC = DataLocations(
|
||||
@@ -35,81 +30,81 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("api_get_miner_info", "get_miner_info"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
RPCAPICommand("rpc_get_miner_info", "get_miner_info"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_get_version", "get_version")],
|
||||
[RPCAPICommand("rpc_get_version", "get_version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[
|
||||
RPCAPICommand("api_get_version", "get_version"),
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("rpc_get_version", "get_version"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
|
||||
[RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
[RPCAPICommand("rpc_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("api_get_psu", "get_psu"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
RPCAPICommand("rpc_get_psu", "get_psu"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction(
|
||||
"_get_fan_psu",
|
||||
[
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("api_get_psu", "get_psu"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
RPCAPICommand("rpc_get_psu", "get_psu"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[
|
||||
RPCAPICommand("api_get_error_code", "get_error_code"),
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("rpc_get_error_code", "get_error_code"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
|
||||
[RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_status", "status")],
|
||||
[RPCAPICommand("rpc_status", "status")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -118,16 +113,16 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
class BTMiner(BaseMiner):
|
||||
"""Base handler for BTMiner based miners."""
|
||||
|
||||
_api_cls = BTMinerRPCAPI
|
||||
api: BTMinerRPCAPI
|
||||
_rpc_cls = BTMinerRPCAPI
|
||||
rpc: BTMinerRPCAPI
|
||||
|
||||
data_locations = BTMINER_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
|
||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
||||
async def _reset_rpc_pwd_to_admin(self, pwd: str):
|
||||
try:
|
||||
data = await self.api.update_pwd(pwd, "admin")
|
||||
data = await self.rpc.update_pwd(pwd, "admin")
|
||||
except APIError:
|
||||
return False
|
||||
if data:
|
||||
@@ -138,7 +133,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
try:
|
||||
data = await self.api.set_led(auto=True)
|
||||
data = await self.rpc.set_led(auto=True)
|
||||
except APIError:
|
||||
return False
|
||||
if data:
|
||||
@@ -150,8 +145,8 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
try:
|
||||
data = await self.api.set_led(auto=False)
|
||||
await self.api.set_led(
|
||||
data = await self.rpc.set_led(auto=False)
|
||||
await self.rpc.set_led(
|
||||
auto=False, color="green", start=0, period=1, duration=0
|
||||
)
|
||||
except APIError:
|
||||
@@ -165,7 +160,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
try:
|
||||
data = await self.api.reboot()
|
||||
data = await self.rpc.reboot()
|
||||
except APIError:
|
||||
return False
|
||||
if data.get("Msg"):
|
||||
@@ -175,7 +170,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
try:
|
||||
data = await self.api.restart()
|
||||
data = await self.rpc.restart()
|
||||
except APIError:
|
||||
return False
|
||||
if data.get("Msg"):
|
||||
@@ -185,7 +180,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
try:
|
||||
data = await self.api.power_off(respbefore=True)
|
||||
data = await self.rpc.power_off(respbefore=True)
|
||||
except APIError:
|
||||
return False
|
||||
if data.get("Msg"):
|
||||
@@ -195,7 +190,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
try:
|
||||
data = await self.api.power_on()
|
||||
data = await self.rpc.power_on()
|
||||
except APIError:
|
||||
return False
|
||||
if data.get("Msg"):
|
||||
@@ -210,16 +205,16 @@ class BTMiner(BaseMiner):
|
||||
pools_conf = conf["pools"]
|
||||
|
||||
try:
|
||||
await self.api.update_pools(**pools_conf)
|
||||
await self.rpc.update_pools(**pools_conf)
|
||||
|
||||
if conf["mode"] == "normal":
|
||||
await self.api.set_normal_power()
|
||||
await self.rpc.set_normal_power()
|
||||
elif conf["mode"] == "high":
|
||||
await self.api.set_high_power()
|
||||
await self.rpc.set_high_power()
|
||||
elif conf["mode"] == "low":
|
||||
await self.api.set_low_power()
|
||||
await self.rpc.set_low_power()
|
||||
elif conf["mode"] == "power_tuning":
|
||||
await self.api.adjust_power_limit(conf["power_tuning"]["wattage"])
|
||||
await self.rpc.adjust_power_limit(conf["power_tuning"]["wattage"])
|
||||
except APIError:
|
||||
# cannot update, no API access usually
|
||||
pass
|
||||
@@ -229,7 +224,7 @@ class BTMiner(BaseMiner):
|
||||
summary = None
|
||||
status = None
|
||||
try:
|
||||
data = await self.api.multicommand("pools", "summary", "status")
|
||||
data = await self.rpc.multicommand("pools", "summary", "status")
|
||||
pools = data["pools"][0]
|
||||
summary = data["summary"][0]
|
||||
status = data["status"][0]
|
||||
@@ -276,7 +271,7 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
await self.api.adjust_power_limit(wattage)
|
||||
await self.rpc.adjust_power_limit(wattage)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} set_power_limit: {e}")
|
||||
return False
|
||||
@@ -288,85 +283,85 @@ class BTMiner(BaseMiner):
|
||||
##################################################
|
||||
|
||||
async def _get_mac(
|
||||
self, api_summary: dict = None, api_get_miner_info: dict = None
|
||||
self, rpc_summary: dict = None, rpc_get_miner_info: dict = None
|
||||
) -> Optional[str]:
|
||||
if api_get_miner_info is None:
|
||||
if rpc_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_miner_info is not None:
|
||||
if rpc_get_miner_info is not None:
|
||||
try:
|
||||
mac = api_get_miner_info["Msg"]["mac"]
|
||||
mac = rpc_get_miner_info["Msg"]["mac"]
|
||||
return str(mac).upper()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
mac = api_summary["SUMMARY"][0]["MAC"]
|
||||
mac = rpc_summary["SUMMARY"][0]["MAC"]
|
||||
return str(mac).upper()
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
|
||||
if api_get_version is None:
|
||||
async def _get_api_ver(self, rpc_get_version: dict = None) -> Optional[str]:
|
||||
if rpc_get_version is None:
|
||||
try:
|
||||
api_get_version = await self.api.get_version()
|
||||
rpc_get_version = await self.rpc.get_version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_version is not None:
|
||||
if "Code" in api_get_version.keys():
|
||||
if api_get_version["Code"] == 131:
|
||||
if rpc_get_version is not None:
|
||||
if "Code" in rpc_get_version.keys():
|
||||
if rpc_get_version["Code"] == 131:
|
||||
try:
|
||||
api_ver = api_get_version["Msg"]
|
||||
if not isinstance(api_ver, str):
|
||||
api_ver = api_ver["api_ver"]
|
||||
self.api_ver = api_ver.replace("whatsminer v", "")
|
||||
rpc_ver = rpc_get_version["Msg"]
|
||||
if not isinstance(rpc_ver, str):
|
||||
rpc_ver = rpc_ver["rpc_ver"]
|
||||
self.api_ver = rpc_ver.replace("whatsminer v", "")
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
else:
|
||||
self.api.api_ver = self.api_ver
|
||||
self.rpc.rpc_ver = self.api_ver
|
||||
return self.api_ver
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(
|
||||
self, api_get_version: dict = None, api_summary: dict = None
|
||||
self, rpc_get_version: dict = None, rpc_summary: dict = None
|
||||
) -> Optional[str]:
|
||||
if api_get_version is None:
|
||||
if rpc_get_version is None:
|
||||
try:
|
||||
api_get_version = await self.api.get_version()
|
||||
rpc_get_version = await self.rpc.get_version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_version is not None:
|
||||
if "Code" in api_get_version.keys():
|
||||
if api_get_version["Code"] == 131:
|
||||
if rpc_get_version is not None:
|
||||
if "Code" in rpc_get_version.keys():
|
||||
if rpc_get_version["Code"] == 131:
|
||||
try:
|
||||
self.fw_ver = api_get_version["Msg"]["fw_ver"]
|
||||
self.fw_ver = rpc_get_version["Msg"]["fw_ver"]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
else:
|
||||
return self.fw_ver
|
||||
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if rpc_summary:
|
||||
try:
|
||||
self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace(
|
||||
self.fw_ver = rpc_summary["SUMMARY"][0]["Firmware Version"].replace(
|
||||
"'", ""
|
||||
)
|
||||
except LookupError:
|
||||
@@ -374,50 +369,50 @@ class BTMiner(BaseMiner):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
|
||||
async def _get_hostname(self, rpc_get_miner_info: dict = None) -> Optional[str]:
|
||||
hostname = None
|
||||
if api_get_miner_info is None:
|
||||
if rpc_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||
except APIError:
|
||||
return None # only one way to get this
|
||||
|
||||
if api_get_miner_info is not None:
|
||||
if rpc_get_miner_info is not None:
|
||||
try:
|
||||
hostname = api_get_miner_info["Msg"]["hostname"]
|
||||
hostname = rpc_get_miner_info["Msg"]["hostname"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_devs: 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:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
rpc_devs = await self.rpc.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs is not None:
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
for board in api_devs["DEVS"]:
|
||||
for board in rpc_devs["DEVS"]:
|
||||
if len(hashboards) < board["ASC"] + 1:
|
||||
hashboards.append(
|
||||
HashBoard(
|
||||
@@ -438,62 +433,62 @@ class BTMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_env_temp(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Env Temp"]
|
||||
return rpc_summary["SUMMARY"][0]["Env Temp"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
async def _get_wattage(self, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
wattage = api_summary["SUMMARY"][0]["Power"]
|
||||
wattage = rpc_summary["SUMMARY"][0]["Power"]
|
||||
return wattage if not wattage == -1 else None
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
async def _get_wattage_limit(self, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Power Limit"]
|
||||
return rpc_summary["SUMMARY"][0]["Power Limit"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fans(
|
||||
self, api_summary: dict = None, api_get_psu: dict = None
|
||||
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
||||
) -> List[Fan]:
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
if self.expected_fans > 0:
|
||||
fans = [
|
||||
Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
||||
Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
||||
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
||||
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
||||
]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -501,45 +496,45 @@ class BTMiner(BaseMiner):
|
||||
return fans
|
||||
|
||||
async def _get_fan_psu(
|
||||
self, api_summary: dict = None, api_get_psu: dict = None
|
||||
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
||||
) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
|
||||
return int(rpc_summary["SUMMARY"][0]["Power Fanspeed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
if api_get_psu is None:
|
||||
if rpc_get_psu is None:
|
||||
try:
|
||||
api_get_psu = await self.api.get_psu()
|
||||
rpc_get_psu = await self.rpc.get_psu()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_psu is not None:
|
||||
if rpc_get_psu is not None:
|
||||
try:
|
||||
return int(api_get_psu["Msg"]["fan_speed"])
|
||||
return int(rpc_get_psu["Msg"]["fan_speed"])
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_errors(
|
||||
self, api_summary: dict = None, api_get_error_code: dict = None
|
||||
self, rpc_summary: dict = None, rpc_get_error_code: dict = None
|
||||
) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
if api_get_error_code is None and api_summary is None:
|
||||
if rpc_get_error_code is None and rpc_summary is None:
|
||||
try:
|
||||
api_get_error_code = await self.api.get_error_code()
|
||||
rpc_get_error_code = await self.rpc.get_error_code()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_error_code is not None:
|
||||
if rpc_get_error_code is not None:
|
||||
try:
|
||||
for err in api_get_error_code["Msg"]["error_code"]:
|
||||
for err in rpc_get_error_code["Msg"]["error_code"]:
|
||||
if isinstance(err, dict):
|
||||
for code in err:
|
||||
errors.append(WhatsminerError(error_code=int(code)))
|
||||
@@ -548,48 +543,48 @@ class BTMiner(BaseMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
|
||||
err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
|
||||
for i in range(rpc_summary["SUMMARY"][0]["Error Code Count"]):
|
||||
err = rpc_summary["SUMMARY"][0].get(f"Error Code {i}")
|
||||
if err:
|
||||
errors.append(WhatsminerError(error_code=err))
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def _get_expected_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_expected_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
|
||||
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
|
||||
if expected_hashrate:
|
||||
return round(expected_hashrate / 1000, 2)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fault_light(self, api_get_miner_info: dict = None) -> Optional[bool]:
|
||||
if api_get_miner_info is None:
|
||||
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> Optional[bool]:
|
||||
if rpc_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||
except APIError:
|
||||
if not self.light:
|
||||
self.light = False
|
||||
|
||||
if api_get_miner_info is not None:
|
||||
if rpc_get_miner_info is not None:
|
||||
try:
|
||||
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto")
|
||||
self.light = not (rpc_get_miner_info["Msg"]["ledstat"] == "auto")
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@@ -605,46 +600,46 @@ class BTMiner(BaseMiner):
|
||||
):
|
||||
if not hostname:
|
||||
hostname = await self.get_hostname()
|
||||
await self.api.net_config(
|
||||
await self.rpc.net_config(
|
||||
ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
|
||||
)
|
||||
|
||||
async def set_dhcp(self, hostname: str = None):
|
||||
if hostname:
|
||||
await self.set_hostname(hostname)
|
||||
await self.api.net_config()
|
||||
await self.rpc.net_config()
|
||||
|
||||
async def set_hostname(self, hostname: str):
|
||||
await self.api.set_hostname(hostname)
|
||||
await self.rpc.set_hostname(hostname)
|
||||
|
||||
async def _is_mining(self, api_status: dict = None) -> Optional[bool]:
|
||||
if api_status is None:
|
||||
async def _is_mining(self, rpc_status: dict = None) -> Optional[bool]:
|
||||
if rpc_status is None:
|
||||
try:
|
||||
api_status = await self.api.status()
|
||||
rpc_status = await self.rpc.status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_status is not None:
|
||||
if rpc_status is not None:
|
||||
try:
|
||||
if api_status["Msg"].get("btmineroff"):
|
||||
if rpc_status["Msg"].get("btmineroff"):
|
||||
try:
|
||||
await self.api.devdetails()
|
||||
await self.rpc.devdetails()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
return True if api_status["Msg"]["mineroff"] == "false" else False
|
||||
return True if rpc_status["Msg"]["mineroff"] == "false" else False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if api_summary is None:
|
||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -18,44 +18,39 @@ from typing import Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
|
||||
CGMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -64,15 +59,15 @@ CGMINER_DATA_LOC = DataLocations(
|
||||
class CGMiner(BaseMiner):
|
||||
"""Base handler for CGMiner based miners"""
|
||||
|
||||
_api_cls = CGMinerRPCAPI
|
||||
api: CGMinerRPCAPI
|
||||
_rpc_cls = CGMinerRPCAPI
|
||||
rpc: CGMinerRPCAPI
|
||||
|
||||
data_locations = CGMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
pools = await self.api.pools()
|
||||
pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
return self.config
|
||||
|
||||
@@ -83,60 +78,60 @@ class CGMiner(BaseMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if api_version is None:
|
||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||
if rpc_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
rpc_version = await self.rpc.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version is not None:
|
||||
if rpc_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
|
||||
self.fw_ver = rpc_version["VERSION"][0]["CGMiner"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
float(float(rpc_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -21,13 +21,8 @@ from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.web.epic import ePICWebAPI
|
||||
|
||||
EPIC_DATA_LOC = DataLocations(
|
||||
@@ -112,6 +107,31 @@ class ePIC(BaseMiner):
|
||||
self.config = cfg
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
conf = self.config.as_epic(user_suffix=user_suffix)
|
||||
|
||||
try:
|
||||
# Temps
|
||||
if not conf.get("temps", {}) == {}:
|
||||
await self.web.set_shutdown_temp(conf["temps"]["shutdown"])
|
||||
# Fans
|
||||
# set with sub-keys instead of conf["fans"] because sometimes both can be set
|
||||
if not conf["fans"].get("Manual", {}) == {}:
|
||||
await self.web.set_fan({"Manual": conf["fans"]["Manual"]})
|
||||
elif not conf["fans"].get("Auto", {}) == {}:
|
||||
await self.web.set_fan({"Auto": conf["fans"]["Auto"]})
|
||||
|
||||
# Mining Mode -- Need to handle that you may not be able to change while miner is tuning
|
||||
if conf["ptune"].get("enabled", True):
|
||||
await self.web.set_ptune_enable(True)
|
||||
await self.web.set_ptune_algo(**conf["ptune"])
|
||||
|
||||
## Pools
|
||||
await self.web.set_pools(conf["pools"])
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
data = await self.web.restart_epic()
|
||||
if data:
|
||||
|
||||
@@ -20,7 +20,7 @@ from pyasic.data import HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.backends import BFGMiner
|
||||
from pyasic.miners.base import (
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
@@ -37,7 +37,7 @@ GOLDSHELL_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
@@ -45,22 +45,22 @@ GOLDSHELL_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("api_devs", "devs"),
|
||||
RPCAPICommand("api_devdetails", "devdetails"),
|
||||
RPCAPICommand("rpc_devs", "devs"),
|
||||
RPCAPICommand("rpc_devdetails", "devdetails"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -130,11 +130,11 @@ class GoldshellMiner(BFGMiner):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(
|
||||
self, api_devs: dict = None, api_devdetails: dict = None
|
||||
self, rpc_devs: dict = None, rpc_devdetails: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if api_devs is None:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
rpc_devs = await self.rpc.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -143,9 +143,9 @@ class GoldshellMiner(BFGMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_devs is not None:
|
||||
if api_devs.get("DEVS"):
|
||||
for board in api_devs["DEVS"]:
|
||||
if rpc_devs is not None:
|
||||
if rpc_devs.get("DEVS"):
|
||||
for board in rpc_devs["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
try:
|
||||
b_id = board["ID"]
|
||||
@@ -157,17 +157,17 @@ class GoldshellMiner(BFGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
logger.error(self, api_devs)
|
||||
logger.error(self, rpc_devs)
|
||||
|
||||
if api_devdetails is None:
|
||||
if rpc_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
rpc_devdetails = await self.rpc.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails is not None:
|
||||
if api_devdetails.get("DEVS"):
|
||||
for board in api_devdetails["DEVS"]:
|
||||
if rpc_devdetails is not None:
|
||||
if rpc_devdetails.get("DEVS"):
|
||||
for board in rpc_devdetails["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
try:
|
||||
b_id = board["ID"]
|
||||
@@ -175,6 +175,6 @@ class GoldshellMiner(BFGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
logger.error(self, api_devdetails)
|
||||
logger.error(self, rpc_devdetails)
|
||||
|
||||
return hashboards
|
||||
|
||||
@@ -21,7 +21,7 @@ from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.data.error_codes.innosilicon import InnosiliconError
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import CGMiner
|
||||
from pyasic.miners.base import (
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
@@ -41,23 +41,23 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[
|
||||
RPCAPICommand("api_summary", "summary"),
|
||||
RPCAPICommand("rpc_summary", "summary"),
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
RPCAPICommand("api_stats", "stats"),
|
||||
RPCAPICommand("rpc_stats", "stats"),
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
],
|
||||
),
|
||||
@@ -65,7 +65,7 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
"_get_wattage",
|
||||
[
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
RPCAPICommand("api_stats", "stats"),
|
||||
RPCAPICommand("rpc_stats", "stats"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
@@ -88,7 +88,7 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -168,14 +168,14 @@ class Innosilicon(CGMiner):
|
||||
pass
|
||||
|
||||
async def _get_hashrate(
|
||||
self, api_summary: dict = None, web_get_all: dict = None
|
||||
self, rpc_summary: dict = None, web_get_all: dict = None
|
||||
) -> Optional[float]:
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if api_summary is None and web_get_all is None:
|
||||
if rpc_summary is None and web_get_all is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -193,14 +193,14 @@ class Innosilicon(CGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(
|
||||
self, api_stats: dict = None, web_get_all: dict = None
|
||||
self, rpc_stats: dict = None, web_get_all: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
@@ -210,9 +210,9 @@ class Innosilicon(CGMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
@@ -224,9 +224,9 @@ class Innosilicon(CGMiner):
|
||||
else:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if api_stats is not None:
|
||||
if api_stats.get("STATS"):
|
||||
for board in api_stats["STATS"]:
|
||||
if rpc_stats is not None:
|
||||
if rpc_stats.get("STATS"):
|
||||
for board in rpc_stats["STATS"]:
|
||||
try:
|
||||
idx = board["Chain ID"]
|
||||
chips = board["Num active chips"]
|
||||
@@ -258,7 +258,7 @@ class Innosilicon(CGMiner):
|
||||
return hashboards
|
||||
|
||||
async def _get_wattage(
|
||||
self, web_get_all: dict = None, api_stats: dict = None
|
||||
self, web_get_all: dict = None, rpc_stats: dict = None
|
||||
) -> Optional[int]:
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
@@ -277,15 +277,15 @@ class Innosilicon(CGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if api_stats.get("STATS"):
|
||||
for board in api_stats["STATS"]:
|
||||
if rpc_stats is not None:
|
||||
if rpc_stats.get("STATS"):
|
||||
for board in rpc_stats["STATS"]:
|
||||
try:
|
||||
wattage = board["power"]
|
||||
except KeyError:
|
||||
|
||||
@@ -18,43 +18,38 @@ from typing import List, Optional
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.rpc.luxminer import LUXMinerRPCAPI
|
||||
|
||||
LUXMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[RPCAPICommand("api_config", "config")],
|
||||
[RPCAPICommand("rpc_config", "config")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_power", "power")],
|
||||
[RPCAPICommand("rpc_power", "power")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_fans", "fans")],
|
||||
[RPCAPICommand("rpc_fans", "fans")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_uptime", [RPCAPICommand("rpc_stats", "stats")]
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -63,8 +58,8 @@ LUXMINER_DATA_LOC = DataLocations(
|
||||
class LUXMiner(BaseMiner):
|
||||
"""Handler for LuxOS miners"""
|
||||
|
||||
_api_cls = LUXMinerRPCAPI
|
||||
api: LUXMinerRPCAPI
|
||||
_rpc_cls = LUXMinerRPCAPI
|
||||
rpc: LUXMinerRPCAPI
|
||||
|
||||
firmware = "LuxOS"
|
||||
|
||||
@@ -72,14 +67,14 @@ class LUXMiner(BaseMiner):
|
||||
|
||||
async def _get_session(self) -> Optional[str]:
|
||||
try:
|
||||
data = await self.api.session()
|
||||
data = await self.rpc.session()
|
||||
if not data["SESSION"][0]["SessionID"] == "":
|
||||
return data["SESSION"][0]["SessionID"]
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.api.logon()
|
||||
data = await self.rpc.logon()
|
||||
return data["SESSION"][0]["SessionID"]
|
||||
except (LookupError, APIError):
|
||||
return
|
||||
@@ -88,7 +83,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.ledset(session_id, "red", "blink")
|
||||
await self.rpc.ledset(session_id, "red", "blink")
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -98,7 +93,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.ledset(session_id, "red", "off")
|
||||
await self.rpc.ledset(session_id, "red", "off")
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -111,7 +106,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.resetminer(session_id)
|
||||
await self.rpc.resetminer(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -121,7 +116,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.curtail(session_id)
|
||||
await self.rpc.curtail(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -131,7 +126,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.wakeup(session_id)
|
||||
await self.rpc.wakeup(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -140,7 +135,7 @@ class LUXMiner(BaseMiner):
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.rebootdevice(session_id)
|
||||
await self.rpc.rebootdevice(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
@@ -153,48 +148,48 @@ class LUXMiner(BaseMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, api_config: dict = None) -> Optional[str]:
|
||||
async def _get_mac(self, rpc_config: dict = None) -> Optional[str]:
|
||||
mac = None
|
||||
if api_config is None:
|
||||
if rpc_config is None:
|
||||
try:
|
||||
api_config = await self.api.config()
|
||||
rpc_config = await self.rpc.config()
|
||||
except APIError:
|
||||
return None
|
||||
|
||||
if api_config is not None:
|
||||
if rpc_config is not None:
|
||||
try:
|
||||
mac = api_config["CONFIG"][0]["MACAddr"]
|
||||
mac = rpc_config["CONFIG"][0]["MACAddr"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
return mac
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
return round(float(rpc_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if api_stats is None:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
boards = rpc_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
@@ -236,48 +231,48 @@ class LUXMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_wattage(self, api_power: dict = None) -> Optional[int]:
|
||||
if api_power is None:
|
||||
async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]:
|
||||
if rpc_power is None:
|
||||
try:
|
||||
api_power = await self.api.power()
|
||||
rpc_power = await self.rpc.power()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_power is not None:
|
||||
if rpc_power is not None:
|
||||
try:
|
||||
return api_power["POWER"][0]["Watts"]
|
||||
return rpc_power["POWER"][0]["Watts"]
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
|
||||
if api_fans is None:
|
||||
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
||||
if rpc_fans is None:
|
||||
try:
|
||||
api_fans = await self.api.fans()
|
||||
rpc_fans = await self.rpc.fans()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = []
|
||||
|
||||
if api_fans is not None:
|
||||
if rpc_fans is not None:
|
||||
for fan in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(api_fans["FANS"][fan]["RPM"]))
|
||||
fans.append(Fan(rpc_fans["FANS"][fan]["RPM"]))
|
||||
except (LookupError, ValueError, TypeError):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if api_stats is None:
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
@@ -289,15 +284,15 @@ class LUXMiner(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if api_stats is None:
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats is not None:
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 List, Optional, Tuple
|
||||
|
||||
@@ -32,7 +30,7 @@ class UnknownMiner(BaseMiner):
|
||||
) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
self.api = UnknownRPCAPI(ip)
|
||||
self.rpc = UnknownRPCAPI(ip)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Unknown: {str(self.ip)}"
|
||||
@@ -19,7 +19,7 @@ from typing import Optional
|
||||
from pyasic import MinerConfig
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.base import (
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
@@ -36,7 +36,7 @@ VNISH_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
@@ -48,15 +48,15 @@ VNISH_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
@@ -68,11 +68,11 @@ VNISH_DATA_LOC = DataLocations(
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -172,18 +172,18 @@ class VNish(BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if api_summary is None:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
rpc_summary = await self.rpc.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
float(float(rpc_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -205,13 +205,14 @@ class VNish(BMMiner):
|
||||
if web_summary is None:
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
fw_ver = None
|
||||
if web_summary is not None:
|
||||
try:
|
||||
fw_ver = web_summary["miner"]["miner_type"]
|
||||
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
|
||||
return fw_ver
|
||||
except KeyError:
|
||||
pass
|
||||
except LookupError:
|
||||
return fw_ver
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
try:
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import warnings
|
||||
from dataclasses import dataclass, field, make_dataclass
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
@@ -25,94 +23,16 @@ from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
|
||||
|
||||
class DataOptions(Enum):
|
||||
MAC = "mac"
|
||||
API_VERSION = "api_ver"
|
||||
FW_VERSION = "fw_ver"
|
||||
HOSTNAME = "hostname"
|
||||
HASHRATE = "hashrate"
|
||||
EXPECTED_HASHRATE = "expected_hashrate"
|
||||
HASHBOARDS = "hashboards"
|
||||
ENVIRONMENT_TEMP = "env_temp"
|
||||
WATTAGE = "wattage"
|
||||
WATTAGE_LIMIT = "wattage_limit"
|
||||
FANS = "fans"
|
||||
FAN_PSU = "fan_psu"
|
||||
ERRORS = "errors"
|
||||
FAULT_LIGHT = "fault_light"
|
||||
IS_MINING = "is_mining"
|
||||
UPTIME = "uptime"
|
||||
CONFIG = "config"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def default_command(self):
|
||||
if str(self.value) == "config":
|
||||
return "get_config"
|
||||
elif str(self.value) == "is_mining":
|
||||
return "_is_mining"
|
||||
else:
|
||||
return f"_get_{str(self.value)}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RPCAPICommand:
|
||||
name: str
|
||||
cmd: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebAPICommand:
|
||||
name: str
|
||||
cmd: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class GRPCCommand(WebAPICommand):
|
||||
name: str
|
||||
cmd: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class GraphQLCommand(WebAPICommand):
|
||||
name: str
|
||||
cmd: dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataFunction:
|
||||
cmd: str
|
||||
kwargs: List[
|
||||
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
|
||||
] = field(default_factory=list)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
|
||||
DataLocations = make_dataclass(
|
||||
"DataLocations",
|
||||
[
|
||||
(
|
||||
enum_value.value,
|
||||
DataFunction,
|
||||
field(default_factory=DataFunction(enum_value.default_command())),
|
||||
)
|
||||
for enum_value in DataOptions
|
||||
],
|
||||
)
|
||||
from pyasic.miners.data import DataLocations, DataOptions, RPCAPICommand, WebAPICommand
|
||||
|
||||
|
||||
class MinerProtocol(Protocol):
|
||||
_api_cls: Type = None
|
||||
_rpc_cls: Type = None
|
||||
_web_cls: Type = None
|
||||
_ssh_cls: Type = None
|
||||
|
||||
ip: str = None
|
||||
api: _api_cls = None
|
||||
rpc: _rpc_cls = None
|
||||
web: _web_cls = None
|
||||
ssh: _ssh_cls = None
|
||||
|
||||
@@ -153,6 +73,10 @@ class MinerProtocol(Protocol):
|
||||
model_data.append(f"({self.firmware})")
|
||||
return " ".join(model_data)
|
||||
|
||||
@property
|
||||
def api(self):
|
||||
return self.rpc
|
||||
|
||||
async def check_light(self) -> bool:
|
||||
return await self.get_fault_light()
|
||||
|
||||
@@ -568,8 +492,8 @@ class BaseMiner(MinerProtocol):
|
||||
)
|
||||
|
||||
# interfaces
|
||||
if self._api_cls is not None:
|
||||
self.api = self._api_cls(ip)
|
||||
if self._rpc_cls is not None:
|
||||
self.rpc = self._rpc_cls(ip)
|
||||
if self._web_cls is not None:
|
||||
self.web = self._web_cls(ip)
|
||||
if self._ssh_cls is not None:
|
||||
|
||||
84
pyasic/miners/data.py
Normal file
84
pyasic/miners/data.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field, make_dataclass
|
||||
from enum import Enum
|
||||
from typing import List, Union
|
||||
|
||||
|
||||
class DataOptions(Enum):
|
||||
MAC = "mac"
|
||||
API_VERSION = "api_ver"
|
||||
FW_VERSION = "fw_ver"
|
||||
HOSTNAME = "hostname"
|
||||
HASHRATE = "hashrate"
|
||||
EXPECTED_HASHRATE = "expected_hashrate"
|
||||
HASHBOARDS = "hashboards"
|
||||
ENVIRONMENT_TEMP = "env_temp"
|
||||
WATTAGE = "wattage"
|
||||
WATTAGE_LIMIT = "wattage_limit"
|
||||
FANS = "fans"
|
||||
FAN_PSU = "fan_psu"
|
||||
ERRORS = "errors"
|
||||
FAULT_LIGHT = "fault_light"
|
||||
IS_MINING = "is_mining"
|
||||
UPTIME = "uptime"
|
||||
CONFIG = "config"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def default_command(self):
|
||||
if str(self.value) == "config":
|
||||
return "get_config"
|
||||
elif str(self.value) == "is_mining":
|
||||
return "_is_mining"
|
||||
else:
|
||||
return f"_get_{str(self.value)}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RPCAPICommand:
|
||||
name: str
|
||||
cmd: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebAPICommand:
|
||||
name: str
|
||||
cmd: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataFunction:
|
||||
cmd: str
|
||||
kwargs: List[Union[RPCAPICommand, WebAPICommand]] = field(default_factory=list)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
|
||||
DataLocations = make_dataclass(
|
||||
"DataLocations",
|
||||
[
|
||||
(
|
||||
enum_value.value,
|
||||
DataFunction,
|
||||
field(default_factory=DataFunction(enum_value.default_command())),
|
||||
)
|
||||
for enum_value in DataOptions
|
||||
],
|
||||
)
|
||||
@@ -13,12 +13,14 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
import ipaddress
|
||||
import json
|
||||
import re
|
||||
from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union
|
||||
from typing import Any, AsyncGenerator, Callable
|
||||
|
||||
import anyio
|
||||
import httpx
|
||||
@@ -26,23 +28,25 @@ 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.backends.unknown import UnknownMiner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
from pyasic.miners.unknown import UnknownMiner
|
||||
from pyasic.miners.whatsminer import *
|
||||
|
||||
|
||||
@@ -57,6 +61,7 @@ class MinerTypes(enum.Enum):
|
||||
HIVEON = 7
|
||||
LUX_OS = 8
|
||||
EPIC = 9
|
||||
AURADINE = 10
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -284,7 +289,9 @@ MINER_CLASSES = {
|
||||
"M50S++VK30": BTMinerM50SPlusPlusVK30,
|
||||
"M53VH30": BTMinerM53VH30,
|
||||
"M53SVH30": BTMinerM53SVH30,
|
||||
"M53SVJ40": BTMinerM53SVJ40,
|
||||
"M53S+VJ30": BTMinerM53SPlusVJ30,
|
||||
"M53S++VK10": BTMinerM53SPlusPlusVK10,
|
||||
"M56VH30": BTMinerM56VH30,
|
||||
"M56SVH30": BTMinerM56SVH30,
|
||||
"M56S+VJ30": BTMinerM56SPlusVJ30,
|
||||
@@ -392,10 +399,20 @@ 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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def concurrent_get_first_result(tasks: list, verification_func: Callable):
|
||||
async def concurrent_get_first_result(tasks: list, verification_func: Callable) -> Any:
|
||||
res = None
|
||||
for fut in asyncio.as_completed(tasks):
|
||||
res = await fut
|
||||
@@ -411,15 +428,9 @@ 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]:
|
||||
self, ips: list[str], limit: int = 200
|
||||
) -> list[AnyMiner]:
|
||||
results = []
|
||||
|
||||
async for miner in self.get_miner_generator(ips, limit):
|
||||
@@ -427,7 +438,9 @@ class MinerFactory:
|
||||
|
||||
return results
|
||||
|
||||
async def get_miner_generator(self, ips: list, limit: int = 200) -> AsyncGenerator:
|
||||
async def get_miner_generator(
|
||||
self, ips: list, limit: int = 200
|
||||
) -> AsyncGenerator[AnyMiner]:
|
||||
tasks = []
|
||||
semaphore = asyncio.Semaphore(limit)
|
||||
|
||||
@@ -440,10 +453,8 @@ class MinerFactory:
|
||||
if result is not None:
|
||||
yield result
|
||||
|
||||
async def get_miner(self, ip: str):
|
||||
async def get_miner(self, ip: str | ipaddress.ip_address) -> AnyMiner | None:
|
||||
ip = str(ip)
|
||||
if ip in self.cache:
|
||||
return self.cache[ip]
|
||||
|
||||
miner_type = None
|
||||
|
||||
@@ -472,6 +483,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,11 +503,9 @@ 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):
|
||||
async def _get_miner_type(self, ip: str) -> MinerTypes | None:
|
||||
tasks = [
|
||||
asyncio.create_task(self._get_miner_web(ip)),
|
||||
asyncio.create_task(self._get_miner_socket(ip)),
|
||||
@@ -503,7 +513,7 @@ class MinerFactory:
|
||||
|
||||
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||
|
||||
async def _get_miner_web(self, ip: str):
|
||||
async def _get_miner_web(self, ip: str) -> MinerTypes | None:
|
||||
tasks = []
|
||||
try:
|
||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||
@@ -532,7 +542,7 @@ class MinerFactory:
|
||||
@staticmethod
|
||||
async def _web_ping(
|
||||
session: httpx.AsyncClient, url: str
|
||||
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
||||
) -> tuple[str | None, httpx.Response | None]:
|
||||
try:
|
||||
resp = await session.get(url, follow_redirects=True)
|
||||
return resp.text, resp
|
||||
@@ -546,7 +556,7 @@ class MinerFactory:
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def _parse_web_type(web_text: str, web_resp: httpx.Response) -> MinerTypes:
|
||||
def _parse_web_type(web_text: str, web_resp: httpx.Response) -> MinerTypes | None:
|
||||
if web_resp.status_code == 401 and 'realm="antMiner' in web_resp.headers.get(
|
||||
"www-authenticate", ""
|
||||
):
|
||||
@@ -571,8 +581,10 @@ 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):
|
||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||
tasks = []
|
||||
try:
|
||||
commands = ["version", "devdetails"]
|
||||
@@ -596,7 +608,7 @@ class MinerFactory:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
|
||||
async def _socket_ping(ip: str, cmd: str) -> str | None:
|
||||
data = b""
|
||||
try:
|
||||
reader, writer = await asyncio.wait_for(
|
||||
@@ -642,7 +654,7 @@ class MinerFactory:
|
||||
return data.decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def _parse_socket_type(data: str) -> MinerTypes:
|
||||
def _parse_socket_type(data: str) -> MinerTypes | None:
|
||||
upper_data = data.upper()
|
||||
if "BOSMINER" in upper_data or "BOSER" in upper_data:
|
||||
return MinerTypes.BRAIINS_OS
|
||||
@@ -660,17 +672,19 @@ 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,
|
||||
ip: Union[ipaddress.ip_address, str],
|
||||
ip: str,
|
||||
location: str,
|
||||
auth: Optional[httpx.DigestAuth] = None,
|
||||
) -> Optional[dict]:
|
||||
auth: httpx.DigestAuth = None,
|
||||
) -> dict | None:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||
try:
|
||||
data = await session.get(
|
||||
f"http://{str(ip)}{location}",
|
||||
f"http://{ip}{location}",
|
||||
auth=auth,
|
||||
timeout=settings.get("factory_get_timeout", 3),
|
||||
)
|
||||
@@ -689,12 +703,10 @@ class MinerFactory:
|
||||
else:
|
||||
return json_data
|
||||
|
||||
async def send_api_command(
|
||||
self, ip: Union[ipaddress.ip_address, str], command: str
|
||||
) -> Optional[dict]:
|
||||
async def send_api_command(self, ip: str, command: str) -> dict | None:
|
||||
data = b""
|
||||
try:
|
||||
reader, writer = await asyncio.open_connection(str(ip), 4028)
|
||||
reader, writer = await asyncio.open_connection(ip, 4028)
|
||||
except (ConnectionError, OSError):
|
||||
return
|
||||
cmd = {"command": command}
|
||||
@@ -732,7 +744,7 @@ class MinerFactory:
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
async def _fix_api_data(data: bytes):
|
||||
async def _fix_api_data(data: bytes) -> str:
|
||||
if data.endswith(b"\x00"):
|
||||
str_data = data.decode("utf-8")[:-1]
|
||||
else:
|
||||
@@ -769,9 +781,9 @@ class MinerFactory:
|
||||
@staticmethod
|
||||
def _select_miner_from_classes(
|
||||
ip: ipaddress.ip_address,
|
||||
miner_model: Union[str, None],
|
||||
miner_type: Union[MinerTypes, None],
|
||||
) -> AnyMiner:
|
||||
miner_model: str | None,
|
||||
miner_type: MinerTypes | None,
|
||||
) -> AnyMiner | None:
|
||||
try:
|
||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
||||
except LookupError:
|
||||
@@ -779,7 +791,7 @@ class MinerFactory:
|
||||
return MINER_CLASSES[miner_type][None](ip)
|
||||
return UnknownMiner(str(ip))
|
||||
|
||||
async def get_miner_model_antminer(self, ip: str):
|
||||
async def get_miner_model_antminer(self, ip: str) -> str | None:
|
||||
tasks = [
|
||||
asyncio.create_task(self._get_model_antminer_web(ip)),
|
||||
asyncio.create_task(self._get_model_antminer_sock(ip)),
|
||||
@@ -787,7 +799,7 @@ class MinerFactory:
|
||||
|
||||
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||
|
||||
async def _get_model_antminer_web(self, ip: str):
|
||||
async def _get_model_antminer_web(self, ip: str) -> str | None:
|
||||
# last resort, this is slow
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
web_json_data = await self.send_web_command(
|
||||
@@ -801,7 +813,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def _get_model_antminer_sock(self, ip: str):
|
||||
async def _get_model_antminer_sock(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||
@@ -826,7 +838,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_goldshell(self, ip: str):
|
||||
async def get_miner_model_goldshell(self, ip: str) -> str | None:
|
||||
json_data = await self.send_web_command(ip, "/mcb/status")
|
||||
|
||||
try:
|
||||
@@ -836,7 +848,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_whatsminer(self, ip: str):
|
||||
async def get_miner_model_whatsminer(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
||||
@@ -846,7 +858,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_avalonminer(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["PROD"]
|
||||
@@ -857,7 +869,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_innosilicon(self, ip: str) -> str | None:
|
||||
try:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||
auth_req = await session.post(
|
||||
@@ -877,7 +889,7 @@ class MinerFactory:
|
||||
except (httpx.HTTPError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_braiins_os(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace(
|
||||
@@ -901,7 +913,7 @@ class MinerFactory:
|
||||
except (httpx.HTTPError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_vnish(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "stats")
|
||||
try:
|
||||
miner_model = sock_json_data["STATS"][0]["Type"]
|
||||
@@ -919,7 +931,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_epic(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_epic(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
|
||||
try:
|
||||
miner_model = sock_json_data["Model"]
|
||||
@@ -927,7 +939,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_hiveon(self, ip: str) -> Optional[str]:
|
||||
async def get_miner_model_hiveon(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_type = sock_json_data["VERSION"][0]["Type"]
|
||||
@@ -936,7 +948,7 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_luxos(self, ip: str):
|
||||
async def get_miner_model_luxos(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||
@@ -948,5 +960,17 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_auradine(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
return sock_json_data["DEVDETAILS"][0]["Model"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
|
||||
# abstracted version of get miner that is easier to access
|
||||
async def get_miner(ip: ipaddress.ip_address | str) -> AnyMiner:
|
||||
return await miner_factory.get_miner(ip)
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import CK5
|
||||
from pyasic.miners.models import CK5
|
||||
|
||||
|
||||
class GoldshellCK5(GoldshellMiner, CK5):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import HS5
|
||||
from pyasic.miners.models import HS5
|
||||
|
||||
|
||||
class GoldshellHS5(GoldshellMiner, HS5):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import KD5
|
||||
from pyasic.miners.models import KD5
|
||||
|
||||
|
||||
class GoldshellKD5(GoldshellMiner, KD5):
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import KDMax
|
||||
from pyasic.miners.models import KDMax
|
||||
|
||||
|
||||
class GoldshellKDMax(GoldshellMiner, KDMax):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends.innosilicon import Innosilicon
|
||||
from pyasic.miners.types import A10X
|
||||
from pyasic.miners.models import A10X
|
||||
|
||||
|
||||
class InnosiliconA10X(Innosilicon, A10X):
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends.innosilicon import Innosilicon
|
||||
from pyasic.miners.types import T3HPlus
|
||||
from pyasic.miners.models import T3HPlus
|
||||
|
||||
|
||||
class InnosiliconT3HPlus(Innosilicon, T3HPlus):
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from pyasic.misc import Singleton
|
||||
|
||||
|
||||
class _MinerListener:
|
||||
class MinerListenerProtocol(asyncio.Protocol):
|
||||
def __init__(self):
|
||||
self.responses = {}
|
||||
self.transport = None
|
||||
@@ -44,11 +42,12 @@ class _MinerListener:
|
||||
pass
|
||||
|
||||
|
||||
class MinerListener(metaclass=Singleton):
|
||||
def __init__(self):
|
||||
class MinerListener:
|
||||
def __init__(self, bind_addr: str = "0.0.0.0"):
|
||||
self.found_miners = []
|
||||
self.new_miner = None
|
||||
self.stop = False
|
||||
self.bind_addr = bind_addr
|
||||
|
||||
async def listen(self):
|
||||
self.stop = False
|
||||
@@ -56,10 +55,10 @@ class MinerListener(metaclass=Singleton):
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
transport_14235, _ = await loop.create_datagram_endpoint(
|
||||
_MinerListener, local_addr=("0.0.0.0", 14235)
|
||||
MinerListenerProtocol, local_addr=(self.bind_addr, 14235)
|
||||
)
|
||||
transport_8888, _ = await loop.create_datagram_endpoint(
|
||||
_MinerListener, local_addr=("0.0.0.0", 8888)
|
||||
MinerListenerProtocol, local_addr=(self.bind_addr, 8888)
|
||||
)
|
||||
|
||||
while True:
|
||||
@@ -75,21 +74,3 @@ class MinerListener(metaclass=Singleton):
|
||||
|
||||
async def cancel(self):
|
||||
self.stop = True
|
||||
|
||||
|
||||
async def main():
|
||||
await asyncio.gather(run(), cancel())
|
||||
|
||||
|
||||
async def run():
|
||||
async for miner in MinerListener().listen():
|
||||
print(miner)
|
||||
|
||||
|
||||
async def cancel():
|
||||
await asyncio.sleep(60)
|
||||
await MinerListener().cancel()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -35,3 +35,7 @@ class InnosiliconMake(BaseMiner):
|
||||
|
||||
class GoldshellMake(BaseMiner):
|
||||
make = "Goldshell"
|
||||
|
||||
|
||||
class AuradineMake(BaseMiner):
|
||||
make = "Auradine"
|
||||
@@ -15,6 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .antminer import *
|
||||
from .auradine import *
|
||||
from .avalonminer import *
|
||||
from .goldshell import *
|
||||
from .innosilicon import *
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user