Compare commits

...

60 Commits

Author SHA1 Message Date
b-rowan
c50d55e87c version: bump version number. 2024-02-07 20:16:26 -07:00
b-rowan
5e5516bfb3 bug: fix serial numbers for antminer. 2024-02-07 20:15:38 -07:00
UpstreamData
4b068c57c5 version: bump version number. 2024-02-07 11:17:29 -07:00
UpstreamData
203f199aec feature: add wmt.pyasic.org. 2024-02-07 11:09:27 -07:00
b-rowan
895f17aaf9 version: bump version number. 2024-02-03 00:36:44 -07:00
b-rowan
8a64ff3559 bug: swap to asyncio.read() in base RPC to try to handle possible missed messages. 2024-02-03 00:36:03 -07:00
UpstreamData
4c45d356c4 version: bump version number. 2024-02-02 10:07:08 -07:00
UpstreamData
4dec329f11 bug: Try to return something when checking vnish fw version. 2024-02-02 10:06:33 -07:00
UpstreamData
b563ed118e bug: fix vnish firmware version bug. 2024-02-02 10:05:34 -07:00
UpstreamData
75b2ec40b1 bug: fix ePIC config parsing to use hashrate tuning instead of power tuning. 2024-01-31 09:21:32 -07:00
b-rowan
d9adaf6667 version: bump version number. 2024-01-30 21:41:49 -07:00
b-rowan
9343308f41 feature: Add support for new whatsminers, and try to handle whatsminer errors when receiving data. 2024-01-30 21:41:10 -07:00
UpstreamData
88769e40ae version: bump version number. 2024-01-30 13:34:24 -07:00
UpstreamData
be45eb7400 bug: fix issues with bosminer multicommand, and update X17 to use BOSMiner instead of BOSer. 2024-01-30 13:34:00 -07:00
b-rowan
2f719a03a4 version: bump version number. 2024-01-29 20:57:01 -07:00
b-rowan
64196f9754 bug: update whatsminer set_target_freq to match docs. 2024-01-29 20:56:36 -07:00
UpstreamData
49a77f1b79 version: bump version number. 2024-01-29 12:47:54 -07:00
UpstreamData
3838c4f2f9 bug: fix missing validation import for BTMiner. 2024-01-29 12:47:30 -07:00
UpstreamData
80d89c95b5 version: bump version number. 2024-01-29 12:33:07 -07:00
UpstreamData
30cd8b5cfe bug: fix some issues with rpc renaming. 2024-01-29 12:32:54 -07:00
b-rowan
c443170f78 refactor: improve epic web send_command implementation. 2024-01-27 09:42:35 -07:00
b-rowan
a2c2aa2377 version: bump version number. 2024-01-27 09:26:13 -07:00
b-rowan
4f0eb49a02 bug: fix some issues with epic send config. 2024-01-27 09:25:20 -07:00
b-rowan
a821357b4f Merge pull request #102 from jpcomps/master
fix send_config for ePIC
2024-01-27 09:05:23 -07:00
John-Paul Compagnone
3c7679a22d fix send_config for ePIC 2024-01-27 10:50:37 -05:00
UpstreamData
a52737e236 refactor: add some type hints for epic config. 2024-01-26 13:58:08 -07:00
UpstreamData
7c96bbe153 version: bump version number. 2024-01-26 13:56:15 -07:00
UpstreamData
e8bbf22aa7 bug: fix a bug with epic mining mode configs. 2024-01-26 13:55:54 -07:00
UpstreamData
5ac8b27cb6 version: bump version number. 2024-01-26 12:49:45 -07:00
UpstreamData
6c14902484 Add ePIC send_config and config.as_epic (#101)
* feature: Add epic send_config.

* feature: remove UID from epic config.

* feature: add default for temp configs in epic.
2024-01-26 12:47:19 -07:00
UpstreamData
96aa346f00 refactor: rename miner.api to miner.rpc. Add miner.api property linked to miner.rpc. 2024-01-26 10:15:20 -07:00
UpstreamData
c2b6cc7468 refactor: improve validate_command_output, and move it out of the miner rpc api. 2024-01-26 09:51:09 -07:00
UpstreamData
ac7f41be44 refactor: move bind addr to init in MinerListener. 2024-01-25 16:34:46 -07:00
UpstreamData
718b87fd12 refactor: rename overwritten method. 2024-01-25 16:32:33 -07:00
UpstreamData
5ad23c6cd0 refactor: remove unused imports. 2024-01-25 16:31:19 -07:00
UpstreamData
66be443dc3 refactor: re-arrange some imports. 2024-01-25 16:25:25 -07:00
UpstreamData
a9135e21d4 docs: update docs. 2024-01-25 14:35:31 -07:00
UpstreamData
dd4c087749 refactor: move base classes to base.py in their directories, move data locations to miners.data, and rename types to models. 2024-01-25 14:26:53 -07:00
UpstreamData
aa1d7c1b6f refactor: make web handlers much more consistent across types, remove graphql, and make luci and grpc the dedicated web apis for BOSer and BOSMiner respectively. 2024-01-25 13:50:04 -07:00
UpstreamData
b328a27f04 refactor: Update config to use future annotations and move merge_dicts to misc. 2024-01-25 11:32:03 -07:00
UpstreamData
c5eed797ec refactor: update type annotations in config. 2024-01-25 10:07:19 -07:00
b-rowan
4fd2199435 version: bump version number. 2024-01-24 18:39:50 -07:00
b-rowan
3226d47846 Merge branch 'dev_fluxos' 2024-01-24 18:39:12 -07:00
b-rowan
6c1931fe7e bug: fix some naming issues with auradine, and add chip count for AT1500. 2024-01-24 18:37:29 -07:00
b-rowan
1dd87ac102 feature: add expected chips for M50S++VK10 2024-01-24 18:32:50 -07:00
b-rowan
95d1e40b4f bug: fix auradine fan config parsing. 2024-01-24 18:28:10 -07:00
b-rowan
31682b7fae bug: fix auradine fan data and config parsing. 2024-01-24 18:22:28 -07:00
b-rowan
e6523fc7d5 bug: fix auradine wattage data. 2024-01-24 18:18:11 -07:00
b-rowan
91de12467b bug: add multicommand flag to auradine multicommand output. 2024-01-24 18:08:22 -07:00
b-rowan
d81e3e9f88 bug: fix auradine multicommand format for get_data. 2024-01-24 18:05:32 -07:00
b-rowan
49fc0f3c54 bug: fix auradine hashboards. 2024-01-24 17:55:47 -07:00
b-rowan
4b36044e56 bug: fix auradine web api token format. 2024-01-24 17:45:42 -07:00
b-rowan
90fb67f586 bug: fix auradine web api token. 2024-01-24 17:38:49 -07:00
b-rowan
edf31ae7df bug: fix auradine identification. 2024-01-24 17:33:41 -07:00
b-rowan
af354fd8e2 feature: add auradine to web selection options. 2024-01-24 17:17:03 -07:00
UpstreamData
6a2a3e836d bug: fix auradine selection. 2024-01-24 16:23:25 -07:00
b-rowan
41709e4706 feature: add auradine data functions. 2024-01-23 16:15:34 -07:00
b-rowan
b60c7a55d4 feature: add auradine control functions. 2024-01-23 15:28:37 -07:00
b-rowan
eed1973345 feature: add auradine models. 2024-01-23 14:23:57 -07:00
b-rowan
64774d2017 feature: add basic auradine miner framework. 2024-01-23 14:06:54 -07:00
290 changed files with 3258 additions and 2214 deletions

View File

@@ -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")

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"]),
)

View File

@@ -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 {}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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=[

View File

@@ -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()

View File

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

View File

@@ -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
View 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
View 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}")

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import (
from pyasic.miners.models import (
S19,
S19L,
S19XP,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer
from pyasic.miners.types import (
from pyasic.miners.models import (
S19,
S19XP,
S19a,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish
from pyasic.miners.types import (
from pyasic.miners.models import (
S19,
S19XP,
S19a,

View File

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

View File

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

View File

@@ -0,0 +1 @@
from .flux import *

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import Auradine
from pyasic.miners.models import AuradineAT1500
class AuradineFluxAT1500(AuradineAT1500, Auradine):
pass

View 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

View File

@@ -0,0 +1,2 @@
from .AT1 import AuradineFluxAT1500
from .AT2 import AuradineFluxAT2860, AuradineFluxAT2880

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import Auradine
from pyasic.miners.models import AuradineAI2500
class AuradineFluxAI2500(AuradineAI2500, Auradine):
pass

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import Auradine
from pyasic.miners.models import AuradineAI3680
class AuradineFluxAI3680(AuradineAI3680, Auradine):
pass

View File

@@ -0,0 +1,2 @@
from .AI2 import AuradineFluxAI2500
from .AI3 import AuradineFluxAI3680

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import Auradine
from pyasic.miners.models import AuradineAD2500
class AuradineFluxAD2500(AuradineAD2500, Auradine):
pass

View File

@@ -0,0 +1,6 @@
from pyasic.miners.backends import Auradine
from pyasic.miners.models import AuradineAD3500
class AuradineFluxAD3500(AuradineAD3500, Auradine):
pass

View File

@@ -0,0 +1,2 @@
from .AD2 import AuradineFluxAD2500
from .AD3 import AuradineFluxAD3500

View File

@@ -0,0 +1,3 @@
from .AD import *
from .AI import *
from .AT import *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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_api=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

View 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

View File

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

View File

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

View File

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

View File

@@ -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
@@ -341,24 +340,24 @@ class BOSMiner(BaseMiner):
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)
@@ -366,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"])
@@ -402,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
@@ -414,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
@@ -427,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
@@ -514,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)
@@ -538,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
@@ -571,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")],
),
}
)
@@ -636,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
@@ -647,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
@@ -662,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
@@ -710,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
@@ -720,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
@@ -763,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
@@ -773,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
@@ -791,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
@@ -809,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
@@ -840,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
@@ -855,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
@@ -872,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
@@ -886,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
@@ -922,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
@@ -936,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)}"

View File

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

View File

@@ -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
View 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
],
)

View File

@@ -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
@@ -412,8 +429,8 @@ async def concurrent_get_first_result(tasks: list, verification_func: Callable):
class MinerFactory:
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):
@@ -421,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)
@@ -434,7 +453,7 @@ 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)
miner_type = None
@@ -464,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)
@@ -485,7 +505,7 @@ class MinerFactory:
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)),
@@ -493,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}/"]
@@ -522,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
@@ -536,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", ""
):
@@ -561,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"]
@@ -586,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(
@@ -632,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
@@ -650,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),
)
@@ -679,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}
@@ -722,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:
@@ -759,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:
@@ -769,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)),
@@ -777,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(
@@ -791,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"]
@@ -816,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:
@@ -826,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("_", "")
@@ -836,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"]
@@ -847,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(
@@ -867,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(
@@ -891,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"]
@@ -909,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"]
@@ -917,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"]
@@ -926,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"]
@@ -938,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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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())

View File

@@ -35,3 +35,7 @@ class InnosiliconMake(BaseMiner):
class GoldshellMake(BaseMiner):
make = "Goldshell"
class AuradineMake(BaseMiner):
make = "Auradine"

View File

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