Compare commits

..

70 Commits

Author SHA1 Message Date
b-rowan
880c598b1a version: bump version number. 2024-02-10 14:26:09 -07:00
b-rowan
3632c2c4d8 feature: add support for goldshell shutdown. 2024-02-10 14:25:42 -07:00
b-rowan
09bc9686ae feature: add support for goldshell mode settings. 2024-02-10 14:23:17 -07:00
b-rowan
34584ab098 feature: add support for KDBoxPro and KDBoxII. 2024-02-10 13:56:00 -07:00
UpstreamData
554d99ca08 bug: update API unlocker format. 2024-02-09 14:25:14 -07:00
UpstreamData
5c5d688ffa Update api opener. 2024-02-09 13:39:48 -07:00
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
b-rowan
e9751d6cd1 version: bump version number. 2024-01-22 19:54:52 -07:00
b-rowan
e2b0a76e67 bug: fix unneeded error handling when getting hostname fails. 2024-01-22 19:40:07 -07:00
b-rowan
1c5c39fa97 version: bump version number. 2024-01-22 18:43:34 -07:00
b-rowan
27c48764a8 refactor: remove miner factory cache. 2024-01-22 18:41:19 -07:00
295 changed files with 3387 additions and 2231 deletions

View File

@@ -3,7 +3,7 @@ import importlib
import os import os
import warnings import warnings
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes from pyasic.miners.factory import MINER_CLASSES, MinerTypes
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")

View File

@@ -47,7 +47,7 @@ if __name__ == "__main__":
--- ---
##### Creating miners based on IP ##### 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. 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 ```python
import asyncio # asyncio for handling the async part import asyncio # asyncio for handling the async part

View File

@@ -1,7 +1,7 @@
# pyasic # pyasic
## Miner Factory ## 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`. 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()`. 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 handler: python
options: options:
show_root_heading: false show_root_heading: false
@@ -25,12 +25,12 @@ Finally, there is functionality to get multiple miners without using `asyncio.ga
<br> <br>
## AnyMiner ## AnyMiner
::: pyasic.miners.miner_factory.AnyMiner ::: pyasic.miners.base.AnyMiner
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions. [`AnyMiner`][pyasic.miners.base.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], 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. 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 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`. 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.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.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`. [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
Use these instead - Use these instead -
#### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI] #### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI]
@@ -21,7 +21,7 @@ Use these instead -
<br> <br>
## BaseMinerRPCAPI ## BaseMinerRPCAPI
::: pyasic.rpc.BaseMinerRPCAPI ::: pyasic.rpc.base.BaseMinerRPCAPI
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4

View File

@@ -15,45 +15,10 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic import settings from pyasic import settings
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import ( from pyasic.data import MinerData
BraiinsOSError,
InnosiliconError,
MinerData,
WhatsminerError,
X19Error,
)
from pyasic.errors import APIError, APIWarning from pyasic.errors import APIError, APIWarning
from pyasic.miners import get_miner from pyasic.miners import *
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.network import MinerNetwork from pyasic.network import MinerNetwork
from pyasic.rpc.bmminer import BMMinerRPCAPI from pyasic.rpc import *
from pyasic.rpc.bosminer import BOSMinerRPCAPI from pyasic.ssh import *
from pyasic.rpc.btminer import BTMinerRPCAPI from pyasic.web import *
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",
]

View File

@@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from copy import deepcopy
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from pyasic.config.fans import FanModeConfig 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.pools import PoolConfig
from pyasic.config.power_scaling import PowerScalingConfig from pyasic.config.power_scaling import PowerScalingConfig
from pyasic.config.temperature import TemperatureConfig from pyasic.config.temperature import TemperatureConfig
from pyasic.misc import merge_dicts
@dataclass @dataclass
@@ -93,7 +93,7 @@ class MinerConfig:
def as_bosminer(self, user_suffix: str = None) -> dict: def as_bosminer(self, user_suffix: str = None) -> dict:
return { 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.mining_mode.as_bosminer(),
**self.pools.as_bosminer(user_suffix=user_suffix), **self.pools.as_bosminer(user_suffix=user_suffix),
**self.power_scaling.as_bosminer(), **self.power_scaling.as_bosminer(),
@@ -110,13 +110,21 @@ class MinerConfig:
def as_epic(self, user_suffix: str = None) -> dict: def as_epic(self, user_suffix: str = None) -> dict:
return { return {
**self.fan_mode.as_epic(), **merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
**self.temperature.as_epic(),
**self.mining_mode.as_epic(), **self.mining_mode.as_epic(),
**self.pools.as_epic(user_suffix=user_suffix), **self.pools.as_epic(user_suffix=user_suffix),
**self.power_scaling.as_epic(), **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 @classmethod
def from_dict(cls, dict_conf: dict) -> "MinerConfig": def from_dict(cls, dict_conf: dict) -> "MinerConfig":
return cls( return cls(
@@ -189,13 +197,10 @@ class MinerConfig:
mining_mode=MiningModeConfig.from_vnish(web_settings), mining_mode=MiningModeConfig.from_vnish(web_settings),
) )
@classmethod
def merge(a: dict, b: dict) -> dict: def from_auradine(cls, web_conf: dict) -> "MinerConfig":
result = deepcopy(a) return cls(
for b_key, b_val in b.items(): pools=PoolConfig.from_api(web_conf["pools"]),
a_val = result.get(b_key) fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
if isinstance(a_val, dict) and isinstance(b_val, dict): mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
result[b_key] = merge(a_val, b_val) )
else:
result[b_key] = deepcopy(b_val)
return result

View File

@@ -13,14 +13,15 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from enum import Enum from enum import Enum
from typing import Union
class MinerConfigOption(Enum): class MinerConfigOption(Enum):
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
return cls.default() return cls.default()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -53,6 +54,9 @@ class MinerConfigOption(Enum):
def as_vnish(self) -> dict: def as_vnish(self) -> dict:
return self.value.as_vnish() return self.value.as_vnish()
def as_auradine(self) -> dict:
return self.value.as_auradine()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs) return self.value(*args, **kwargs)
@@ -64,10 +68,10 @@ class MinerConfigOption(Enum):
@dataclass @dataclass
class MinerConfigValue: class MinerConfigValue:
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
return cls() return cls()
def as_dict(self): def as_dict(self) -> dict:
return asdict(self) return asdict(self)
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -99,3 +103,6 @@ class MinerConfigValue:
def as_vnish(self) -> dict: def as_vnish(self) -> dict:
return {} return {}
def as_auradine(self) -> dict:
return {}

View File

@@ -13,8 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@@ -26,7 +27,7 @@ class FanModeNormal(MinerConfigValue):
minimum_speed: int = 0 minimum_speed: int = 0
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal": def from_dict(cls, dict_conf: dict | None) -> "FanModeNormal":
cls_conf = {} cls_conf = {}
if dict_conf.get("minimum_fans") is not None: if dict_conf.get("minimum_fans") is not None:
cls_conf["minimum_fans"] = dict_conf["minimum_fans"] cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
@@ -35,7 +36,7 @@ class FanModeNormal(MinerConfigValue):
return cls(**cls_conf) return cls(**cls_conf)
@classmethod @classmethod
def from_vnish(cls, web_cooling_settings: dict): def from_vnish(cls, web_cooling_settings: dict) -> "FanModeNormal":
cls_conf = {} cls_conf = {}
if web_cooling_settings.get("fan_min_count") is not None: if web_cooling_settings.get("fan_min_count") is not None:
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"] cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
@@ -49,6 +50,17 @@ class FanModeNormal(MinerConfigValue):
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
return {"temp_control": {"mode": "auto"}} 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 @dataclass
class FanModeManual(MinerConfigValue): class FanModeManual(MinerConfigValue):
@@ -57,7 +69,7 @@ class FanModeManual(MinerConfigValue):
minimum_fans: int = 1 minimum_fans: int = 1
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeManual": def from_dict(cls, dict_conf: dict | None) -> "FanModeManual":
cls_conf = {} cls_conf = {}
if dict_conf.get("speed") is not None: if dict_conf.get("speed") is not None:
cls_conf["speed"] = dict_conf["speed"] cls_conf["speed"] = dict_conf["speed"]
@@ -92,13 +104,19 @@ class FanModeManual(MinerConfigValue):
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed}, "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 @dataclass
class FanModeImmersion(MinerConfigValue): class FanModeImmersion(MinerConfigValue):
mode: str = field(init=False, default="immersion") mode: str = field(init=False, default="immersion")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeImmersion": def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
return cls() return cls()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -107,6 +125,9 @@ class FanModeImmersion(MinerConfigValue):
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
return {"temp_control": {"mode": "disabled"}} return {"temp_control": {"mode": "disabled"}}
def as_auradine(self) -> dict:
return {"fan": {"percentage": 0}}
class FanModeConfig(MinerConfigOption): class FanModeConfig(MinerConfigOption):
normal = FanModeNormal normal = FanModeNormal
@@ -118,7 +139,7 @@ class FanModeConfig(MinerConfigOption):
return cls.normal() return cls.normal()
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
if dict_conf is None: if dict_conf is None:
return cls.default() return cls.default()
@@ -126,9 +147,9 @@ class FanModeConfig(MinerConfigOption):
if mode is None: if mode is None:
return cls.default() return cls.default()
clsattr = getattr(cls, mode) cls_attr = getattr(cls, mode)
if clsattr is not None: if cls_attr is not None:
return clsattr().from_dict(dict_conf) return cls_attr().from_dict(dict_conf)
@classmethod @classmethod
def from_am_modern(cls, web_conf: dict): def from_am_modern(cls, web_conf: dict):
@@ -202,3 +223,13 @@ class FanModeConfig(MinerConfigOption):
if "minimumRequiredFans" in keys: if "minimumRequiredFans" in keys:
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"]) conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
return cls.manual(**conf) 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 - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
@@ -34,7 +35,7 @@ class MiningModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = field(init=False, default="normal")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeNormal": def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
return cls() return cls()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -43,13 +44,22 @@ class MiningModeNormal(MinerConfigValue):
def as_wm(self) -> dict: def as_wm(self) -> dict:
return {"mode": self.mode} return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": self.mode}}
def as_epic(self) -> dict:
return {"ptune": {"enabled": False}}
def as_goldshell(self) -> dict:
return {"settings": {"select": 0}}
@dataclass @dataclass
class MiningModeSleep(MinerConfigValue): class MiningModeSleep(MinerConfigValue):
mode: str = field(init=False, default="sleep") mode: str = field(init=False, default="sleep")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeSleep": def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
return cls() return cls()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -58,13 +68,22 @@ class MiningModeSleep(MinerConfigValue):
def as_wm(self) -> dict: def as_wm(self) -> dict:
return {"mode": self.mode} return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"sleep": "on"}}
def as_epic(self) -> dict:
return {"ptune": {"algo": "Sleep", "target": 0}}
def as_goldshell(self) -> dict:
return {"settings": {"select": 2}}
@dataclass @dataclass
class MiningModeLPM(MinerConfigValue): class MiningModeLPM(MinerConfigValue):
mode: str = field(init=False, default="low") mode: str = field(init=False, default="low")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeLPM": def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
return cls() return cls()
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -73,30 +92,77 @@ class MiningModeLPM(MinerConfigValue):
def as_wm(self) -> dict: def as_wm(self) -> dict:
return {"mode": self.mode} return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": "eco"}}
def as_goldshell(self) -> dict:
return {"settings": {"select": 1}}
@dataclass @dataclass
class MiningModeHPM(MinerConfigValue): class MiningModeHPM(MinerConfigValue):
mode: str = field(init=False, default="high") mode: str = field(init=False, default="high")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHPM": def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
return cls() return cls()
def as_am_modern(self): def as_am_modern(self) -> dict:
return {"miner-mode": "0"} return {"miner-mode": "0"}
def as_wm(self) -> dict: def as_wm(self) -> dict:
return {"mode": self.mode} 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 @dataclass
class MiningModePowerTune(MinerConfigValue): class MiningModePowerTune(MinerConfigValue):
mode: str = field(init=False, default="power_tuning") mode: str = field(init=False, default="power_tuning")
power: int = None power: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModePowerTune": def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
return cls(dict_conf.get("power")) 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: def as_am_modern(self) -> dict:
return {"miner-mode": "0"} return {"miner-mode": "0"}
@@ -123,14 +189,18 @@ class MiningModePowerTune(MinerConfigValue):
), ),
} }
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
@dataclass @dataclass
class MiningModeHashrateTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning") mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None hashrate: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod @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")) return cls(dict_conf.get("hashrate"))
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -152,6 +222,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 @dataclass
class ManualBoardSettings(MinerConfigValue): class ManualBoardSettings(MinerConfigValue):
@@ -159,7 +235,7 @@ class ManualBoardSettings(MinerConfigValue):
volt: float volt: float
@classmethod @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"]) return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
@@ -172,10 +248,10 @@ class MiningModeManual(MinerConfigValue):
global_freq: float global_freq: float
global_volt: float global_volt: float
boards: Dict[int, ManualBoardSettings] = field(default_factory=dict) boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual": def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
return cls( return cls(
global_freq=dict_conf["global_freq"], global_freq=dict_conf["global_freq"],
global_volt=dict_conf["global_volt"], global_volt=dict_conf["global_volt"],
@@ -214,7 +290,7 @@ class MiningModeConfig(MinerConfigOption):
return cls.normal() return cls.normal()
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
if dict_conf is None: if dict_conf is None:
return cls.default() return cls.default()
@@ -222,9 +298,9 @@ class MiningModeConfig(MinerConfigOption):
if mode is None: if mode is None:
return cls.default() return cls.default()
clsattr = getattr(cls, mode) cls_attr = getattr(cls, mode)
if clsattr is not None: if cls_attr is not None:
return clsattr().from_dict(dict_conf) return cls_attr().from_dict(dict_conf)
@classmethod @classmethod
def from_am_modern(cls, web_conf: dict): def from_am_modern(cls, web_conf: dict):
@@ -243,20 +319,18 @@ class MiningModeConfig(MinerConfigOption):
@classmethod @classmethod
def from_epic(cls, web_conf: dict): def from_epic(cls, web_conf: dict):
try: try:
work_mode = web_conf["PerpetualTune"]["Running"] tuner_running = web_conf["PerpetualTune"]["Running"]
if work_mode: if tuner_running:
if ( algo_info = web_conf["PerpetualTune"]["Algorithm"]
web_conf["PerpetualTune"]["Algorithm"].get("VoltageOptimizer") if algo_info.get("VoltageOptimizer") is not None:
is not None
):
return cls.hashrate_tuning( return cls.hashrate_tuning(
web_conf["PerpetualTune"]["Algorithm"]["VoltageOptimizer"][ hashrate=algo_info["VoltageOptimizer"]["Target"],
"Target" algo=TunerAlgo.voltage_optimizer,
]
) )
else: else:
return cls.hashrate_tuning( return cls.hashrate_tuning(
web_conf["PerpetualTune"]["Algorithm"]["ChipTune"]["Target"] hashrate=algo_info["ChipTune"]["Target"],
algo=TunerAlgo.chip_tune,
) )
else: else:
return cls.normal() return cls.normal()
@@ -330,3 +404,22 @@ class MiningModeConfig(MinerConfigOption):
return cls.hashrate_tuning( return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]) 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 - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import random import random
import string import string
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Union from typing import List
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@@ -27,7 +29,7 @@ class Pool(MinerConfigValue):
user: str user: str
password: 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: if user_suffix is not None:
return { return {
"url": self.url, "url": self.url,
@@ -36,7 +38,7 @@ class Pool(MinerConfigValue):
} }
return {"url": self.url, "user": self.user, "pass": self.password} 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: if user_suffix is not None:
return { return {
f"pool_{idx}": self.url, f"pool_{idx}": self.url,
@@ -49,7 +51,7 @@ class Pool(MinerConfigValue):
f"passwd_{idx}": self.password, 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: if user_suffix is not None:
return { return {
f"_ant_pool{idx}url": self.url, f"_ant_pool{idx}url": self.url,
@@ -62,7 +64,7 @@ class Pool(MinerConfigValue):
f"_ant_pool{idx}pw": self.password, 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: if user_suffix is not None:
return { return {
"url": self.url, "url": self.url,
@@ -71,12 +73,12 @@ class Pool(MinerConfigValue):
} }
return {"url": self.url, "user": self.user, "pass": self.password} 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: if user_suffix is not None:
return ",".join([self.url, f"{self.user}{user_suffix}", self.password]) return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
return ",".join([self.url, self.user, 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: if user_suffix is not None:
return { return {
f"Pool{idx}": self.url, f"Pool{idx}": self.url,
@@ -89,7 +91,7 @@ class Pool(MinerConfigValue):
f"Password{idx}": self.password, 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: if user_suffix is not None:
return { return {
"url": self.url, "url": self.url,
@@ -98,8 +100,26 @@ class Pool(MinerConfigValue):
} }
return {"url": self.url, "user": self.user, "password": self.password} 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 @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool": def from_dict(cls, dict_conf: dict | None) -> "Pool":
return cls( return cls(
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"] url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
) )
@@ -160,7 +180,7 @@ class Pool(MinerConfigValue):
@dataclass @dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
pools: List[Pool] = field(default_factory=list) pools: list[Pool] = field(default_factory=list)
quota: int = 1 quota: int = 1
name: str = None name: str = None
@@ -210,7 +230,7 @@ class PoolGroup(MinerConfigValue):
def as_goldshell(self, user_suffix: str = None) -> list: def as_goldshell(self, user_suffix: str = None) -> list:
return [pool.as_goldshell(user_suffix) for pool in self.pools] 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: if len(self.pools) > 0:
return self.pools[0].as_avalon(user_suffix=user_suffix) return self.pools[0].as_avalon(user_suffix=user_suffix)
return Pool("", "", "").as_avalon() return Pool("", "", "").as_avalon()
@@ -241,8 +261,14 @@ class PoolGroup(MinerConfigValue):
return conf return conf
return {"name": "Group", "pool": []} 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 @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup": def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
cls_conf = {} cls_conf = {}
if dict_conf.get("quota") is not None: 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]) return cls([Pool.from_vnish(p) for p in web_settings_pools])
@classmethod @classmethod
def from_boser(cls, grpc_pool_group: dict): def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
try: try:
return cls( return cls(
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]], pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
@@ -318,14 +344,14 @@ class PoolConfig(MinerConfigValue):
return cls() return cls()
@classmethod @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: if dict_conf is None:
return cls.default() return cls.default()
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]]) return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
@classmethod @classmethod
def simple(cls, pools: List[Union[Pool, Dict[str, str]]]) -> "PoolConfig": def simple(cls, pools: list[Pool | dict[str, str]]) -> "PoolConfig":
group_pools = [] group_pools = []
for pool in pools: for pool in pools:
if isinstance(pool, dict): if isinstance(pool, dict):
@@ -373,6 +399,32 @@ class PoolConfig(MinerConfigValue):
def as_boser(self, user_suffix: str = None) -> dict: def as_boser(self, user_suffix: str = None) -> dict:
return {} 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 @classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig": def from_api(cls, api_pools: dict) -> "PoolConfig":
try: try:
@@ -417,7 +469,7 @@ class PoolConfig(MinerConfigValue):
return cls() return cls()
@classmethod @classmethod
def from_boser(cls, grpc_miner_conf: dict): def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig":
try: try:
return cls( return cls(
groups=[ groups=[

View File

@@ -13,8 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
@@ -31,7 +32,7 @@ class PowerScalingShutdownEnabled(MinerConfigValue):
duration: int = None duration: int = None
@classmethod @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")) return cls(duration=dict_conf.get("duration"))
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
@@ -51,7 +52,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue):
mode: str = field(init=False, default="disabled") mode: str = field(init=False, default="disabled")
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownDisabled": def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownDisabled":
return cls() return cls()
def as_bosminer(self) -> dict: def as_bosminer(self) -> dict:
@@ -66,7 +67,7 @@ class PowerScalingShutdown(MinerConfigOption):
disabled = PowerScalingShutdownDisabled disabled = PowerScalingShutdownDisabled
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
if dict_conf is None: if dict_conf is None:
return cls.default() return cls.default()
@@ -107,9 +108,7 @@ class PowerScalingEnabled(MinerConfigValue):
mode: str = field(init=False, default="enabled") mode: str = field(init=False, default="enabled")
power_step: int = None power_step: int = None
minimum_power: int = None minimum_power: int = None
shutdown_enabled: Union[ shutdown_enabled: PowerScalingShutdownEnabled | PowerScalingShutdownDisabled = None
PowerScalingShutdownEnabled, PowerScalingShutdownDisabled
] = None
@classmethod @classmethod
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled": def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
@@ -122,7 +121,7 @@ class PowerScalingEnabled(MinerConfigValue):
) )
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingEnabled": def from_dict(cls, dict_conf: dict | None) -> "PowerScalingEnabled":
cls_conf = { cls_conf = {
"power_step": dict_conf.get("power_step"), "power_step": dict_conf.get("power_step"),
"minimum_power": dict_conf.get("minimum_power"), "minimum_power": dict_conf.get("minimum_power"),
@@ -175,7 +174,7 @@ class PowerScalingConfig(MinerConfigOption):
return cls.disabled() return cls.disabled()
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]): def from_dict(cls, dict_conf: dict | None):
if dict_conf is None: if dict_conf is None:
return cls.default() return cls.default()

View File

@@ -13,8 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@@ -39,8 +40,18 @@ class TemperatureConfig(MinerConfigValue):
temp_cfg["dangerous_temp"] = self.danger temp_cfg["dangerous_temp"] = self.danger
return {"temp_control": temp_cfg} 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 @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "TemperatureConfig": def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
return cls( return cls(
target=dict_conf.get("target"), target=dict_conf.get("target"),
hot=dict_conf.get("hot"), hot=dict_conf.get("hot"),
@@ -72,7 +83,7 @@ class TemperatureConfig(MinerConfigValue):
return cls(target=target_temp, danger=dangerous_temp) return cls(target=target_temp, danger=dangerous_temp)
@classmethod @classmethod
def from_vnish(cls, web_settings: dict): def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
try: try:
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto": if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"]) return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
@@ -81,7 +92,7 @@ class TemperatureConfig(MinerConfigValue):
return cls() return cls()
@classmethod @classmethod
def from_boser(cls, grpc_miner_conf: dict): def from_boser(cls, grpc_miner_conf: dict) -> "TemperatureConfig":
try: try:
temperature_conf = grpc_miner_conf["temperature"] temperature_conf = grpc_miner_conf["temperature"]
except KeyError: except KeyError:

View File

@@ -24,73 +24,9 @@ from typing import Any, List, Union
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune from pyasic.config.mining import MiningModePowerTune
from .boards import HashBoard
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
from .fans import Fan
@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}")
@dataclass @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.errors import APIError
from pyasic.miners import AnyMiner from pyasic.miners import AnyMiner
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner 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 FAN_USAGE = 50 # 50 W per fan

View File

@@ -14,13 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import ipaddress from .base import AnyMiner
from typing import Union from .data import DataOptions
from .factory import get_miner, miner_factory
from pyasic.miners.base import AnyMiner, BaseMiner from .listener import MinerListener
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)

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld 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): class BMMinerS17(AntminerOld, S17):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld 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): class BMMinerT17(AntminerOld, T17):

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import T19 from pyasic.miners.models import T19
class BMMinerT19(AntminerModern, T19): class BMMinerT19(AntminerModern, T19):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import HS3 from pyasic.miners.models import HS3
class BMMinerHS3(AntminerModern, HS3): class BMMinerHS3(AntminerModern, HS3):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld from pyasic.miners.backends import AntminerOld
from pyasic.miners.types import L3Plus from pyasic.miners.models import L3Plus
class BMMinerL3Plus(AntminerOld, L3Plus): class BMMinerL3Plus(AntminerOld, L3Plus):

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import L7 from pyasic.miners.models import L7
class BMMinerL7(AntminerModern, L7): class BMMinerL7(AntminerModern, L7):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerModern from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import E9Pro from pyasic.miners.models import E9Pro
class BMMinerE9Pro(AntminerModern, E9Pro): class BMMinerE9Pro(AntminerModern, E9Pro):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BMMiner 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): class BMMinerS9(BMMiner, S9):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BMMiner from pyasic.miners.backends import BMMiner
from pyasic.miners.types import T9 from pyasic.miners.models import T9
class BMMinerT9(BMMiner, T9): class BMMinerT9(BMMiner, T9):

View File

@@ -14,21 +14,21 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer from pyasic.miners.backends import BOSMiner
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
class BOSMinerS17(BOSer, S17): class BOSMinerS17(BOSMiner, S17):
pass pass
class BOSMinerS17Plus(BOSer, S17Plus): class BOSMinerS17Plus(BOSMiner, S17Plus):
pass pass
class BOSMinerS17Pro(BOSer, S17Pro): class BOSMinerS17Pro(BOSMiner, S17Pro):
pass pass
class BOSMinerS17e(BOSer, S17e): class BOSMinerS17e(BOSMiner, S17e):
pass pass

View File

@@ -14,17 +14,17 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer from pyasic.miners.backends import BOSMiner
from pyasic.miners.types import T17, T17e, T17Plus from pyasic.miners.models import T17, T17e, T17Plus
class BOSMinerT17(BOSer, T17): class BOSMinerT17(BOSMiner, T17):
pass pass
class BOSMinerT17Plus(BOSer, T17Plus): class BOSMinerT17Plus(BOSMiner, T17Plus):
pass pass
class BOSMinerT17e(BOSer, T17e): class BOSMinerT17e(BOSMiner, T17e):
pass pass

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSer from pyasic.miners.backends import BOSer
from pyasic.miners.types import T19 from pyasic.miners.models import T19
class BOSMinerT19(BOSer, T19): class BOSMinerT19(BOSer, T19):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BOSMiner from pyasic.miners.backends import BOSMiner
from pyasic.miners.types import S9 from pyasic.miners.models import S9
class BOSMinerS9(BOSMiner, S9): class BOSMinerS9(BOSMiner, S9):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld from pyasic.miners.backends import AntminerOld
from pyasic.miners.types import Z15 from pyasic.miners.models import Z15
class CGMinerZ15(AntminerOld, Z15): class CGMinerZ15(AntminerOld, Z15):

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld from pyasic.miners.backends import AntminerOld
from pyasic.miners.types import D3 from pyasic.miners.models import D3
class CGMinerD3(AntminerOld, D3): class CGMinerD3(AntminerOld, D3):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AntminerOld from pyasic.miners.backends import AntminerOld
from pyasic.miners.types import DR5 from pyasic.miners.models import DR5
class CGMinerDR5(AntminerOld, DR5): class CGMinerDR5(AntminerOld, DR5):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import ePIC 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): class ePICS19(ePIC, S19):

View File

@@ -21,46 +21,46 @@ import asyncssh
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import Hiveon
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.types import T9 from pyasic.miners.models import T9
HIVEON_T9_DATA_LOC = DataLocations( HIVEON_T9_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp", "_get_env_temp",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_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): except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
pass pass
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=board, expected_chips=self.expected_chips) HashBoard(slot=board, expected_chips=self.expected_chips)
for board in range(self.expected_hashboards) for board in range(self.expected_hashboards)
] ]
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = self.api.stats() rpc_stats = self.rpc.stats()
except APIError: except APIError:
return [] return []
@@ -108,8 +108,8 @@ class HiveonT9(Hiveon, T9):
for chipset in board_map[board]: for chipset in board_map[board]:
if hashboards[board].chip_temp is None: if hashboards[board].chip_temp is None:
try: try:
hashboards[board].temp = api_stats["STATS"][1][f"temp{chipset}"] hashboards[board].temp = rpc_stats["STATS"][1][f"temp{chipset}"]
hashboards[board].chip_temp = api_stats["STATS"][1][ hashboards[board].chip_temp = rpc_stats["STATS"][1][
f"temp2_{chipset}" f"temp2_{chipset}"
] ]
except (KeyError, IndexError): except (KeyError, IndexError):
@@ -117,8 +117,8 @@ class HiveonT9(Hiveon, T9):
else: else:
hashboards[board].missing = False hashboards[board].missing = False
try: try:
hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"] hashrate += rpc_stats["STATS"][1][f"chain_rate{chipset}"]
chips += api_stats["STATS"][1][f"chain_acn{chipset}"] chips += rpc_stats["STATS"][1][f"chain_acn{chipset}"]
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
hashboards[board].hashrate = round(hashrate / 1000, 2) hashboards[board].hashrate = round(hashrate / 1000, 2)
@@ -126,15 +126,15 @@ class HiveonT9(Hiveon, T9):
return hashboards return hashboards
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if not api_stats: if not rpc_stats:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats: if rpc_stats:
boards = api_stats.get("STATS") boards = rpc_stats.get("STATS")
try: try:
wattage_raw = boards[1]["chain_power"] wattage_raw = boards[1]["chain_power"]
except (KeyError, IndexError): except (KeyError, IndexError):
@@ -143,23 +143,23 @@ class HiveonT9(Hiveon, T9):
# parse wattage position out of raw data # parse wattage position out of raw data
return round(float(wattage_raw.split(" ")[0])) 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 = [] env_temp_list = []
board_map = { board_map = {
0: [2, 9, 10], 0: [2, 9, 10],
1: [3, 11, 12], 1: [3, 11, 12],
2: [4, 13, 14], 2: [4, 13, 14],
} }
if not api_stats: if not rpc_stats:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats: if rpc_stats:
for board in board_map.values(): for board in board_map.values():
for chipset in board: for chipset in board:
try: try:
env_temp = api_stats["STATS"][1][f"temp3_{chipset}"] env_temp = rpc_stats["STATS"][1][f"temp3_{chipset}"]
if not env_temp == 0: if not env_temp == 0:
env_temp_list.append(int(env_temp)) env_temp_list.append(int(env_temp))
except (KeyError, IndexError): except (KeyError, IndexError):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import LUXMiner from pyasic.miners.backends import LUXMiner
from pyasic.miners.types import S9 from pyasic.miners.models import S9
class LUXMinerS9(LUXMiner, S9): class LUXMinerS9(LUXMiner, S9):

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish from pyasic.miners.backends import VNish
from pyasic.miners.types import S17Plus, S17Pro from pyasic.miners.models import S17Plus, S17Pro
class VNishS17Plus(VNish, S17Plus): class VNishS17Plus(VNish, S17Plus):

View File

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

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish from pyasic.miners.backends import VNish
from pyasic.miners.types import T19 from pyasic.miners.models import T19
class VNishT19(VNish, T19): class VNishT19(VNish, T19):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish from pyasic.miners.backends import VNish
from pyasic.miners.types import L3Plus from pyasic.miners.models import L3Plus
class VnishL3Plus(VNish, 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.backends import AvalonMiner
from pyasic.miners.types import Avalon1026 from pyasic.miners.models import Avalon1026
class CGMinerAvalon1026(AvalonMiner, Avalon1026): class CGMinerAvalon1026(AvalonMiner, Avalon1026):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1047 from pyasic.miners.models import Avalon1047
class CGMinerAvalon1047(AvalonMiner, Avalon1047): class CGMinerAvalon1047(AvalonMiner, Avalon1047):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1066 from pyasic.miners.models import Avalon1066
class CGMinerAvalon1066(AvalonMiner, Avalon1066): class CGMinerAvalon1066(AvalonMiner, Avalon1066):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1166Pro from pyasic.miners.models import Avalon1166Pro
class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro): class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1246 from pyasic.miners.models import Avalon1246
class CGMinerAvalon1246(AvalonMiner, Avalon1246): class CGMinerAvalon1246(AvalonMiner, Avalon1246):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon721 from pyasic.miners.models import Avalon721
class CGMinerAvalon721(AvalonMiner, Avalon721): class CGMinerAvalon721(AvalonMiner, Avalon721):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon741 from pyasic.miners.models import Avalon741
class CGMinerAvalon741(AvalonMiner, Avalon741): class CGMinerAvalon741(AvalonMiner, Avalon741):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon761 from pyasic.miners.models import Avalon761
class CGMinerAvalon761(AvalonMiner, Avalon761): class CGMinerAvalon761(AvalonMiner, Avalon761):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon821 from pyasic.miners.models import Avalon821
class CGMinerAvalon821(AvalonMiner, Avalon821): class CGMinerAvalon821(AvalonMiner, Avalon821):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon841 from pyasic.miners.models import Avalon841
class CGMinerAvalon841(AvalonMiner, Avalon841): class CGMinerAvalon841(AvalonMiner, Avalon841):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon851 from pyasic.miners.models import Avalon851
class CGMinerAvalon851(AvalonMiner, Avalon851): class CGMinerAvalon851(AvalonMiner, Avalon851):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import AvalonMiner from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon921 from pyasic.miners.models import Avalon921
class CGMinerAvalon921(AvalonMiner, Avalon921): class CGMinerAvalon921(AvalonMiner, Avalon921):

View File

@@ -14,6 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .antminer import AntminerModern, AntminerOld from .antminer import AntminerModern, AntminerOld
from .auradine import Auradine
from .avalonminer import AvalonMiner from .avalonminer import AvalonMiner
from .bfgminer import BFGMiner from .bfgminer import BFGMiner
from .bmminer import BMMiner from .bmminer import BMMiner
@@ -23,6 +24,7 @@ from .cgminer import CGMiner
from .epic import ePIC from .epic import ePIC
from .goldshell import GoldshellMiner from .goldshell import GoldshellMiner
from .hiveon import Hiveon from .hiveon import Hiveon
from .innosilicon import Innosilicon
from .luxminer import LUXMiner from .luxminer import LUXMiner
from .vnish import VNish from .vnish import VNish
from .whatsminer import M2X, M3X, M5X, M6X 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.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.errors import APIError
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.base import ( from pyasic.miners.data import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
from pyasic.rpc import APIError
from pyasic.ssh.antminer import AntminerModernSSH from pyasic.ssh.antminer import AntminerModernSSH
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
@@ -40,11 +40,11 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", "_get_hostname",
@@ -52,15 +52,15 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
@@ -76,7 +76,7 @@ ANTMINER_MODERN_DATA_LOC = DataLocations(
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -206,13 +206,13 @@ class AntminerModern(BMMiner):
] ]
try: 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: except APIError:
return hashboards return hashboards
if api_stats is not None: if rpc_stats is not None:
try: try:
for board in api_stats["STATS"][0]["chain"]: for board in rpc_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = round( hashboards[board["index"]].hashrate = round(
board["rate_real"] / 1000, 2 board["rate_real"] / 1000, 2
) )
@@ -254,18 +254,18 @@ class AntminerModern(BMMiner):
pass pass
return self.light return self.light
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try: try:
rate_unit = api_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
if rate_unit == "GH": if rate_unit == "GH":
@@ -336,16 +336,16 @@ class AntminerModern(BMMiner):
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass
@@ -354,11 +354,11 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", "_get_hostname",
@@ -366,15 +366,15 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
@@ -386,7 +386,7 @@ ANTMINER_OLD_DATA_LOC = DataLocations(
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -479,47 +479,47 @@ class AntminerOld(CGMiner):
except KeyError: except KeyError:
pass pass
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
fans_data = [Fan() for _ in range(self.expected_fans)] fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None: if rpc_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
for fan_num in range(1, 8, 4): for fan_num in range(1, 8, 4):
for _f_num in range(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: if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num + 2 fan_offset = fan_num + 2
if fan_offset == -1: if fan_offset == -1:
fan_offset = 3 fan_offset = 3
for fan in range(self.expected_fans): 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 f"fan{fan_offset+fan}", 0
) )
except LookupError: except LookupError:
pass pass
return fans_data 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 = [] hashboards = []
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = rpc_stats["STATS"]
if len(boards) > 1: if len(boards) > 1:
for board_num in range(1, 16, 5): for board_num in range(1, 16, 5):
for _b_num in range(5): for _b_num in range(5):
@@ -574,27 +574,27 @@ class AntminerOld(CGMiner):
except LookupError: except LookupError:
pass pass
api_summary = None rpc_summary = None
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
if not api_summary == {}: if not rpc_summary == {}:
return True return True
else: else:
return False return False
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass 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.data import Fan, HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends.cgminer import CGMiner 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( AVALON_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", "_get_mac",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_devs", "devs")], [RPCAPICommand("rpc_devs", "devs")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp", "_get_env_temp",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", "_get_wattage_limit",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -79,7 +79,7 @@ class AvalonMiner(CGMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
try: try:
data = await self.api.ascset(0, "led", "1-1") data = await self.rpc.ascset(0, "led", "1-1")
except APIError: except APIError:
return False return False
if data["STATUS"][0]["Msg"] == "ASC 0 set OK": if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
@@ -88,7 +88,7 @@ class AvalonMiner(CGMiner):
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
try: try:
data = await self.api.ascset(0, "led", "1-0") data = await self.rpc.ascset(0, "led", "1-0")
except APIError: except APIError:
return False return False
if data["STATUS"][0]["Msg"] == "ASC 0 set OK": if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
@@ -97,7 +97,7 @@ class AvalonMiner(CGMiner):
async def reboot(self) -> bool: async def reboot(self) -> bool:
try: try:
data = await self.api.restart() data = await self.rpc.restart()
except APIError: except APIError:
return False return False
@@ -155,16 +155,16 @@ class AvalonMiner(CGMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self, api_version: dict = None) -> Optional[str]: async def _get_mac(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
base_mac = api_version["VERSION"][0]["MAC"] base_mac = rpc_version["VERSION"][0]["MAC"]
base_mac = base_mac.upper() base_mac = base_mac.upper()
mac = ":".join( mac = ":".join(
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)] [base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
@@ -173,34 +173,34 @@ class AvalonMiner(CGMiner):
except (KeyError, ValueError): except (KeyError, ValueError):
pass pass
async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]: async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
if api_devs is None: if rpc_devs is None:
try: try:
api_devs = await self.api.devs() rpc_devs = await self.rpc.devs()
except APIError: except APIError:
pass pass
if api_devs is not None: if rpc_devs is not None:
try: 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): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
return hashboards return hashboards
@@ -234,62 +234,62 @@ class AvalonMiner(CGMiner):
return hashboards return hashboards
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return round(float(parsed_stats["GHSmm"]) / 1000, 2) return round(float(parsed_stats["GHSmm"]) / 1000, 2)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return float(parsed_stats["Temp"]) return float(parsed_stats["Temp"])
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage_limit(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["MPO"]) return int(parsed_stats["MPO"])
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
fans_data = [Fan() for _ in range(self.expected_fans)] fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
except LookupError: except LookupError:
return fans_data return fans_data
@@ -301,18 +301,18 @@ class AvalonMiner(CGMiner):
pass pass
return fans_data 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: if self.light:
return self.light return self.light
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
led = int(parsed_stats["Led"]) led = int(parsed_stats["Led"])
return True if led == 1 else False return True if led == 1 else False
@@ -320,7 +320,7 @@ class AvalonMiner(CGMiner):
pass pass
try: try:
data = await self.api.ascset(0, "led", "1-255") data = await self.rpc.ascset(0, "led", "1-255")
except APIError: except APIError:
return False return False
try: try:

View File

@@ -19,40 +19,35 @@ from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.bfgminer import BFGMinerRPCAPI from pyasic.rpc.bfgminer import BFGMinerRPCAPI
BFGMINER_DATA_LOC = DataLocations( BFGMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -61,15 +56,15 @@ BFGMINER_DATA_LOC = DataLocations(
class BFGMiner(BaseMiner): class BFGMiner(BaseMiner):
"""Base handler for BFGMiner based miners.""" """Base handler for BFGMiner based miners."""
_api_cls = BFGMinerRPCAPI _rpc_cls = BFGMinerRPCAPI
api: BFGMinerRPCAPI rpc: BFGMinerRPCAPI
data_locations = BFGMINER_DATA_LOC data_locations = BFGMINER_DATA_LOC
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
try: try:
pools = await self.api.pools() pools = await self.rpc.pools()
except APIError: except APIError:
return self.config return self.config
@@ -80,63 +75,63 @@ class BFGMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError: except LookupError:
pass pass
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"] self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
except LookupError: except LookupError:
pass pass
return self.fw_ver 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 # get hr from API
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (LookupError, ValueError, TypeError):
pass pass
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = rpc_stats["STATS"]
if len(boards) > 1: if len(boards) > 1:
for board_num in range(1, 16, 5): for board_num in range(1, 16, 5):
for _b_num in range(5): for _b_num in range(5):
@@ -178,28 +173,28 @@ class BFGMiner(BaseMiner):
return hashboards return hashboards
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
fans_data = [None, None, None, None] fans_data = [None, None, None, None]
if api_stats is not None: if rpc_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
for fan_num in range(0, 8, 4): for fan_num in range(0, 8, 4):
for _f_num in range(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: if not f == 0 and fan_offset == -1:
fan_offset = fan_num fan_offset = fan_num
if fan_offset == -1: if fan_offset == -1:
fan_offset = 1 fan_offset = 1
for fan in range(self.expected_fans): 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 f"fan{fan_offset+fan}", 0
) )
except LookupError: except LookupError:
@@ -208,19 +203,19 @@ class BFGMiner(BaseMiner):
return fans 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 # X19 method, not sure compatibility
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try: try:
rate_unit = api_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
if 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.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.bmminer import BMMinerRPCAPI from pyasic.rpc.bmminer import BMMinerRPCAPI
BMMINER_DATA_LOC = DataLocations( BMMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -65,15 +60,15 @@ BMMINER_DATA_LOC = DataLocations(
class BMMiner(BaseMiner): class BMMiner(BaseMiner):
"""Base handler for BMMiner based miners.""" """Base handler for BMMiner based miners."""
_api_cls = BMMinerRPCAPI _rpc_cls = BMMinerRPCAPI
api: BMMinerRPCAPI rpc: BMMinerRPCAPI
data_locations = BMMINER_DATA_LOC data_locations = BMMINER_DATA_LOC
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
try: try:
pools = await self.api.pools() pools = await self.rpc.pools()
except APIError: except APIError:
return self.config return self.config
@@ -84,63 +79,63 @@ class BMMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError: except LookupError:
pass pass
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"] self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
except LookupError: except LookupError:
pass pass
return self.fw_ver 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 # get hr from API
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (LookupError, ValueError, TypeError):
pass pass
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = rpc_stats["STATS"]
if len(boards) > 1: if len(boards) > 1:
for board_num in range(1, 16, 5): for board_num in range(1, 16, 5):
for _b_num in range(5): for _b_num in range(5):
@@ -195,28 +190,28 @@ class BMMiner(BaseMiner):
return hashboards return hashboards
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
fans = [Fan() for _ in range(self.expected_fans)] fans = [Fan() for _ in range(self.expected_fans)]
if api_stats is not None: if rpc_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
for fan_num in range(1, 8, 4): for fan_num in range(1, 8, 4):
for _f_num in range(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: if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num fan_offset = fan_num
if fan_offset == -1: if fan_offset == -1:
fan_offset = 1 fan_offset = 1
for fan in range(self.expected_fans): 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 f"fan{fan_offset+fan}", 0
) )
except LookupError: except LookupError:
@@ -224,19 +219,19 @@ class BMMiner(BaseMiner):
return fans 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 # X19 method, not sure compatibility
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try: try:
rate_unit = api_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
if rate_unit == "GH": if rate_unit == "GH":
@@ -248,15 +243,15 @@ class BMMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -24,12 +24,11 @@ from pyasic.config.mining import MiningModePowerTune
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
GRPCCommand,
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
@@ -45,7 +44,7 @@ BOSMINER_DATA_LOC = DataLocations(
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
@@ -53,43 +52,43 @@ BOSMINER_DATA_LOC = DataLocations(
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_devs", "devs")], [RPCAPICommand("rpc_devs", "devs")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[ [
RPCAPICommand("api_temps", "temps"), RPCAPICommand("rpc_temps", "temps"),
RPCAPICommand("api_devdetails", "devdetails"), RPCAPICommand("rpc_devdetails", "devdetails"),
RPCAPICommand("api_devs", "devs"), RPCAPICommand("rpc_devs", "devs"),
], ],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", "_get_wattage_limit",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_fans", "fans")], [RPCAPICommand("rpc_fans", "fans")],
), ),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", "_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")], [RPCAPICommand("rpc_devdetails", "devdetails")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
} }
) )
@@ -98,8 +97,8 @@ BOSMINER_DATA_LOC = DataLocations(
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
"""Handler for old versions of BraiinsOS+ (pre-gRPC)""" """Handler for old versions of BraiinsOS+ (pre-gRPC)"""
_api_cls = BOSMinerRPCAPI _rpc_cls = BOSMinerRPCAPI
api: BOSMinerRPCAPI rpc: BOSMinerRPCAPI
_web_cls = BOSMinerWebAPI _web_cls = BOSMinerWebAPI
web: BOSMinerWebAPI web: BOSMinerWebAPI
_ssh_cls = BOSMinerSSH _ssh_cls = BOSMinerSSH
@@ -140,7 +139,7 @@ class BOSMiner(BaseMiner):
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
data = await self.api.pause() data = await self.rpc.pause()
except APIError: except APIError:
return False return False
@@ -151,7 +150,7 @@ class BOSMiner(BaseMiner):
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
data = await self.api.resume() data = await self.rpc.resume()
except APIError: except APIError:
return False return False
@@ -275,7 +274,7 @@ class BOSMiner(BaseMiner):
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
if web_net_conf is None: if web_net_conf is None:
try: try:
web_net_conf = await self.web.luci.get_net_conf() web_net_conf = await self.web.get_net_conf()
except APIError: except APIError:
pass pass
@@ -293,28 +292,28 @@ class BOSMiner(BaseMiner):
# if result: # if result:
# return result.upper().strip() # return result.upper().strip()
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
# Now get the API version # Now get the API version
if api_version is not None: if rpc_version is not None:
try: try:
api_ver = api_version["VERSION"][0]["API"] rpc_ver = rpc_version["VERSION"][0]["API"]
except LookupError: except LookupError:
api_ver = None rpc_ver = None
self.api_ver = api_ver self.api_ver = rpc_ver
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, web_bos_info: dict = None) -> Optional[str]: async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
if web_bos_info is None: if web_bos_info is None:
try: try:
web_bos_info = await self.web.luci.get_bos_info() web_bos_info = await self.web.get_bos_info()
except APIError: except APIError:
return None return None
@@ -334,29 +333,31 @@ class BOSMiner(BaseMiner):
async def _get_hostname(self) -> Union[str, None]: async def _get_hostname(self) -> Union[str, None]:
try: try:
hostname = (await self.ssh.get_hostname()).strip() hostname = (await self.ssh.get_hostname()).strip()
except AttributeError:
return None
except Exception as e: except Exception as e:
logging.error(f"{self} - Getting hostname failed: {e}") logging.error(f"{self} - Getting hostname failed: {e}")
return None return None
return hostname return hostname
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (KeyError, IndexError, ValueError, TypeError):
pass pass
async def _get_hashboards( async def _get_hashboards(
self, self,
api_temps: dict = None, rpc_temps: dict = None,
api_devdetails: dict = None, rpc_devdetails: dict = None,
api_devs: dict = None, rpc_devs: dict = None,
) -> List[HashBoard]: ) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
@@ -364,34 +365,34 @@ class BOSMiner(BaseMiner):
] ]
cmds = [] cmds = []
if api_temps is None: if rpc_temps is None:
cmds.append("temps") cmds.append("temps")
if api_devdetails is None: if rpc_devdetails is None:
cmds.append("devdetails") cmds.append("devdetails")
if api_devs is None: if rpc_devs is None:
cmds.append("devs") cmds.append("devs")
if len(cmds) > 0: if len(cmds) > 0:
try: try:
d = await self.api.multicommand(*cmds) d = await self.rpc.multicommand(*cmds)
except APIError: except APIError:
d = {} d = {}
try: try:
api_temps = d["temps"][0] rpc_temps = d["temps"][0]
except LookupError: except LookupError:
api_temps = None rpc_temps = None
try: try:
api_devdetails = d["devdetails"][0] rpc_devdetails = d["devdetails"][0]
except (KeyError, IndexError): except (KeyError, IndexError):
api_devdetails = None rpc_devdetails = None
try: try:
api_devs = d["devs"][0] rpc_devs = d["devs"][0]
except LookupError: except LookupError:
api_devs = None rpc_devs = None
if api_temps is not None: if rpc_temps is not None:
try: 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 _id = board["ID"] - offset
chip_temp = round(board["Chip"]) chip_temp = round(board["Chip"])
board_temp = round(board["Board"]) board_temp = round(board["Board"])
@@ -400,11 +401,11 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
if api_devdetails is not None: if rpc_devdetails is not None:
try: 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 _id = board["ID"] - offset
chips = board["Chips"] chips = board["Chips"]
hashboards[_id].chips = chips hashboards[_id].chips = chips
@@ -412,11 +413,11 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
if api_devs is not None: if rpc_devs is not None:
try: 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 _id = board["ID"] - offset
hashrate = round(float(board["MHS 1m"] / 1000000), 2) hashrate = round(float(board["MHS 1m"] / 1000000), 2)
hashboards[_id].hashrate = hashrate hashboards[_id].hashrate = hashrate
@@ -425,62 +426,62 @@ class BOSMiner(BaseMiner):
return hashboards return hashboards
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_tunerstatus: dict = None) -> Optional[int]:
if api_tunerstatus is None: if rpc_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() rpc_tunerstatus = await self.rpc.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus is not None: if rpc_tunerstatus is not None:
try: try:
return api_tunerstatus["TUNERSTATUS"][0][ return rpc_tunerstatus["TUNERSTATUS"][0][
"ApproximateMinerPowerConsumption" "ApproximateMinerPowerConsumption"
] ]
except LookupError: except LookupError:
pass pass
async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage_limit(self, rpc_tunerstatus: dict = None) -> Optional[int]:
if api_tunerstatus is None: if rpc_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() rpc_tunerstatus = await self.rpc.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus is not None: if rpc_tunerstatus is not None:
try: try:
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] return rpc_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
except LookupError: except LookupError:
pass pass
async def _get_fans(self, api_fans: dict = None) -> List[Fan]: async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
if api_fans is None: if rpc_fans is None:
try: try:
api_fans = await self.api.fans() rpc_fans = await self.rpc.fans()
except APIError: except APIError:
pass pass
if api_fans is not None: if rpc_fans is not None:
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
fans.append(Fan(api_fans["FANS"][n]["RPM"])) fans.append(Fan(rpc_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return fans return fans
return [Fan() for _ in range(self.expected_fans)] return [Fan() for _ in range(self.expected_fans)]
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
if api_tunerstatus is None: if rpc_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() rpc_tunerstatus = await self.rpc.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus is not None: if rpc_tunerstatus is not None:
errors = [] errors = []
try: try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] chain_status = rpc_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
if chain_status and len(chain_status) > 0: if chain_status and len(chain_status) > 0:
offset = ( offset = (
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0 6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
@@ -512,18 +513,18 @@ class BOSMiner(BaseMiner):
except (TypeError, AttributeError): except (TypeError, AttributeError):
return self.light return self.light
async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
if api_devs is None: if rpc_devs is None:
try: try:
api_devs = await self.api.devs() rpc_devs = await self.rpc.devs()
except APIError: except APIError:
pass pass
if api_devs is not None: if rpc_devs is not None:
try: try:
hr_list = [] hr_list = []
for board in api_devs["DEVS"]: for board in rpc_devs["DEVS"]:
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
if expected_hashrate: if expected_hashrate:
hr_list.append(expected_hashrate) hr_list.append(expected_hashrate)
@@ -536,31 +537,31 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
if api_devdetails is None: if rpc_devdetails is None:
try: try:
api_devdetails = await self.api.send_command( rpc_devdetails = await self.rpc.send_command(
"devdetails", ignore_errors=True, allow_warning=False "devdetails", ignore_errors=True, allow_warning=False
) )
except APIError: except APIError:
pass pass
if api_devdetails is not None: if rpc_devdetails is not None:
try: try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(rpc_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:
pass pass
@@ -569,63 +570,63 @@ BOSER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", "_get_mac",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[GRPCCommand("api_version", "get_api_version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", "_get_hostname",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[GRPCCommand("grpc_miner_details", "get_miner_details")], [WebAPICommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[GRPCCommand("grpc_hashboards", "get_hashboards")], [WebAPICommand("grpc_hashboards", "get_hashboards")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[GRPCCommand("grpc_miner_stats", "get_miner_stats")], [WebAPICommand("grpc_miner_stats", "get_miner_stats")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", "_get_wattage_limit",
[ [
GRPCCommand( WebAPICommand(
"grpc_active_performance_mode", "get_active_performance_mode" "grpc_active_performance_mode", "get_active_performance_mode"
) )
], ],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[GRPCCommand("grpc_cooling_state", "get_cooling_state")], [WebAPICommand("grpc_cooling_state", "get_cooling_state")],
), ),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("rpc_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], [WebAPICommand("grpc_locate_device_status", "get_locate_device_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", "_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")], [RPCAPICommand("rpc_devdetails", "devdetails")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
} }
) )
@@ -634,7 +635,7 @@ BOSER_DATA_LOC = DataLocations(
class BOSer(BaseMiner): class BOSer(BaseMiner):
"""Handler for new versions of BraiinsOS+ (post-gRPC)""" """Handler for new versions of BraiinsOS+ (post-gRPC)"""
_api_cls = BOSMinerRPCAPI _rpc_cls = BOSMinerRPCAPI
web: BOSMinerRPCAPI web: BOSMinerRPCAPI
_web_cls = BOSerWebAPI _web_cls = BOSerWebAPI
web: BOSerWebAPI web: BOSerWebAPI
@@ -645,13 +646,13 @@ class BOSer(BaseMiner):
supports_shutdown = True supports_shutdown = True
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(True) resp = await self.web.set_locate_device_status(True)
if resp.get("enabled", False): if resp.get("enabled", False):
return True return True
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(False) resp = await self.web.set_locate_device_status(False)
if resp == {}: if resp == {}:
return True return True
return False return False
@@ -660,37 +661,37 @@ class BOSer(BaseMiner):
return await self.restart_boser() return await self.restart_boser()
async def restart_boser(self) -> bool: async def restart_boser(self) -> bool:
await self.web.grpc.restart() await self.web.restart()
return True return True
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
await self.web.grpc.pause_mining() await self.web.pause_mining()
except APIError: except APIError:
return False return False
return True return True
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
await self.web.grpc.resume_mining() await self.web.resume_mining()
except APIError: except APIError:
return False return False
return True return True
async def reboot(self) -> bool: async def reboot(self) -> bool:
ret = await self.web.grpc.reboot() ret = await self.web.reboot()
if ret == {}: if ret == {}:
return True return True
return False return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
grpc_conf = await self.web.grpc.get_miner_configuration() grpc_conf = await self.web.get_miner_configuration()
return MinerConfig.from_boser(grpc_conf) return MinerConfig.from_boser(grpc_conf)
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
result = await self.web.grpc.set_power_target(wattage) result = await self.web.set_power_target(wattage)
except APIError: except APIError:
return False return False
@@ -708,7 +709,7 @@ class BOSer(BaseMiner):
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -718,27 +719,27 @@ class BOSer(BaseMiner):
except (LookupError, TypeError): except (LookupError, TypeError):
pass pass
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
api_ver = api_version["VERSION"][0]["API"] rpc_ver = rpc_version["VERSION"][0]["API"]
except LookupError: except LookupError:
api_ver = None rpc_ver = None
self.api_ver = api_ver self.api_ver = rpc_ver
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, grpc_miner_details: dict = None) -> Optional[str]: async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -761,7 +762,7 @@ class BOSer(BaseMiner):
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]: async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -771,16 +772,16 @@ class BOSer(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -789,7 +790,7 @@ class BOSer(BaseMiner):
) -> Optional[float]: ) -> Optional[float]:
if grpc_miner_details is None: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.get_miner_details()
except APIError: except APIError:
pass pass
@@ -807,7 +808,7 @@ class BOSer(BaseMiner):
if grpc_hashboards is None: if grpc_hashboards is None:
try: try:
grpc_hashboards = await self.web.grpc.get_hashboards() grpc_hashboards = await self.web.get_hashboards()
except APIError: except APIError:
pass pass
@@ -838,7 +839,7 @@ class BOSer(BaseMiner):
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
if grpc_miner_stats is None: if grpc_miner_stats is None:
try: try:
grpc_miner_stats = self.web.grpc.get_miner_stats() grpc_miner_stats = self.web.get_miner_stats()
except APIError: except APIError:
pass pass
@@ -853,9 +854,7 @@ class BOSer(BaseMiner):
) -> Optional[int]: ) -> Optional[int]:
if grpc_active_performance_mode is None: if grpc_active_performance_mode is None:
try: try:
grpc_active_performance_mode = ( grpc_active_performance_mode = self.web.get_active_performance_mode()
self.web.grpc.get_active_performance_mode()
)
except APIError: except APIError:
pass pass
@@ -870,7 +869,7 @@ class BOSer(BaseMiner):
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
if grpc_cooling_state is None: if grpc_cooling_state is None:
try: try:
grpc_cooling_state = self.web.grpc.get_cooling_state() grpc_cooling_state = self.web.get_cooling_state()
except APIError: except APIError:
pass pass
@@ -884,17 +883,17 @@ class BOSer(BaseMiner):
return fans return fans
return [Fan() for _ in range(self.expected_fans)] return [Fan() for _ in range(self.expected_fans)]
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
if api_tunerstatus is None: if rpc_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() rpc_tunerstatus = await self.rpc.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus is not None: if rpc_tunerstatus is not None:
errors = [] errors = []
try: try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] chain_status = rpc_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
if chain_status and len(chain_status) > 0: if chain_status and len(chain_status) > 0:
offset = ( offset = (
6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0 6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0
@@ -920,9 +919,7 @@ class BOSer(BaseMiner):
if grpc_locate_device_status is None: if grpc_locate_device_status is None:
try: try:
grpc_locate_device_status = ( grpc_locate_device_status = await self.web.get_locate_device_status()
await self.web.grpc.get_locate_device_status()
)
except APIError: except APIError:
pass pass
@@ -934,30 +931,30 @@ class BOSer(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
if api_devdetails is None: if rpc_devdetails is None:
try: try:
api_devdetails = await self.api.send_command( rpc_devdetails = await self.rpc.send_command(
"devdetails", ignore_errors=True, allow_warning=False "devdetails", ignore_errors=True, allow_warning=False
) )
except APIError: except APIError:
pass pass
if api_devdetails is not None: if rpc_devdetails is not None:
try: try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(rpc_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -21,13 +21,8 @@ from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.btminer import BTMinerRPCAPI from pyasic.rpc.btminer import BTMinerRPCAPI
BTMINER_DATA_LOC = DataLocations( BTMINER_DATA_LOC = DataLocations(
@@ -35,81 +30,81 @@ BTMINER_DATA_LOC = DataLocations(
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", "_get_mac",
[ [
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
RPCAPICommand("api_get_miner_info", "get_miner_info"), RPCAPICommand("rpc_get_miner_info", "get_miner_info"),
], ],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_get_version", "get_version")], [RPCAPICommand("rpc_get_version", "get_version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[ [
RPCAPICommand("api_get_version", "get_version"), RPCAPICommand("rpc_get_version", "get_version"),
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
], ],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", "_get_hostname",
[RPCAPICommand("api_get_miner_info", "get_miner_info")], [RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_devs", "devs")], [RPCAPICommand("rpc_devs", "devs")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp", "_get_env_temp",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", "_get_wattage_limit",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[ [
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
RPCAPICommand("api_get_psu", "get_psu"), RPCAPICommand("rpc_get_psu", "get_psu"),
], ],
), ),
str(DataOptions.FAN_PSU): DataFunction( str(DataOptions.FAN_PSU): DataFunction(
"_get_fan_psu", "_get_fan_psu",
[ [
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
RPCAPICommand("api_get_psu", "get_psu"), RPCAPICommand("rpc_get_psu", "get_psu"),
], ],
), ),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[ [
RPCAPICommand("api_get_error_code", "get_error_code"), RPCAPICommand("rpc_get_error_code", "get_error_code"),
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
], ],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[RPCAPICommand("api_get_miner_info", "get_miner_info")], [RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", "_is_mining",
[RPCAPICommand("api_status", "status")], [RPCAPICommand("rpc_status", "status")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
} }
) )
@@ -118,16 +113,16 @@ BTMINER_DATA_LOC = DataLocations(
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
"""Base handler for BTMiner based miners.""" """Base handler for BTMiner based miners."""
_api_cls = BTMinerRPCAPI _rpc_cls = BTMinerRPCAPI
api: BTMinerRPCAPI rpc: BTMinerRPCAPI
data_locations = BTMINER_DATA_LOC data_locations = BTMINER_DATA_LOC
supports_shutdown = True supports_shutdown = True
async def _reset_api_pwd_to_admin(self, pwd: str): async def _reset_rpc_pwd_to_admin(self, pwd: str):
try: try:
data = await self.api.update_pwd(pwd, "admin") data = await self.rpc.update_pwd(pwd, "admin")
except APIError: except APIError:
return False return False
if data: if data:
@@ -138,7 +133,7 @@ class BTMiner(BaseMiner):
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
try: try:
data = await self.api.set_led(auto=True) data = await self.rpc.set_led(auto=True)
except APIError: except APIError:
return False return False
if data: if data:
@@ -150,8 +145,8 @@ class BTMiner(BaseMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
try: try:
data = await self.api.set_led(auto=False) data = await self.rpc.set_led(auto=False)
await self.api.set_led( await self.rpc.set_led(
auto=False, color="green", start=0, period=1, duration=0 auto=False, color="green", start=0, period=1, duration=0
) )
except APIError: except APIError:
@@ -165,7 +160,7 @@ class BTMiner(BaseMiner):
async def reboot(self) -> bool: async def reboot(self) -> bool:
try: try:
data = await self.api.reboot() data = await self.rpc.reboot()
except APIError: except APIError:
return False return False
if data.get("Msg"): if data.get("Msg"):
@@ -175,7 +170,7 @@ class BTMiner(BaseMiner):
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
try: try:
data = await self.api.restart() data = await self.rpc.restart()
except APIError: except APIError:
return False return False
if data.get("Msg"): if data.get("Msg"):
@@ -185,7 +180,7 @@ class BTMiner(BaseMiner):
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
try: try:
data = await self.api.power_off(respbefore=True) data = await self.rpc.power_off(respbefore=True)
except APIError: except APIError:
return False return False
if data.get("Msg"): if data.get("Msg"):
@@ -195,7 +190,7 @@ class BTMiner(BaseMiner):
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
try: try:
data = await self.api.power_on() data = await self.rpc.power_on()
except APIError: except APIError:
return False return False
if data.get("Msg"): if data.get("Msg"):
@@ -210,16 +205,16 @@ class BTMiner(BaseMiner):
pools_conf = conf["pools"] pools_conf = conf["pools"]
try: try:
await self.api.update_pools(**pools_conf) await self.rpc.update_pools(**pools_conf)
if conf["mode"] == "normal": if conf["mode"] == "normal":
await self.api.set_normal_power() await self.rpc.set_normal_power()
elif conf["mode"] == "high": elif conf["mode"] == "high":
await self.api.set_high_power() await self.rpc.set_high_power()
elif conf["mode"] == "low": elif conf["mode"] == "low":
await self.api.set_low_power() await self.rpc.set_low_power()
elif conf["mode"] == "power_tuning": 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: except APIError:
# cannot update, no API access usually # cannot update, no API access usually
pass pass
@@ -229,7 +224,7 @@ class BTMiner(BaseMiner):
summary = None summary = None
status = None status = None
try: try:
data = await self.api.multicommand("pools", "summary", "status") data = await self.rpc.multicommand("pools", "summary", "status")
pools = data["pools"][0] pools = data["pools"][0]
summary = data["summary"][0] summary = data["summary"][0]
status = data["status"][0] status = data["status"][0]
@@ -276,7 +271,7 @@ class BTMiner(BaseMiner):
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
await self.api.adjust_power_limit(wattage) await self.rpc.adjust_power_limit(wattage)
except Exception as e: except Exception as e:
logging.warning(f"{self} set_power_limit: {e}") logging.warning(f"{self} set_power_limit: {e}")
return False return False
@@ -288,85 +283,85 @@ class BTMiner(BaseMiner):
################################################## ##################################################
async def _get_mac( 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]: ) -> Optional[str]:
if api_get_miner_info is None: if rpc_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() rpc_get_miner_info = await self.rpc.get_miner_info()
except APIError: except APIError:
pass pass
if api_get_miner_info is not None: if rpc_get_miner_info is not None:
try: try:
mac = api_get_miner_info["Msg"]["mac"] mac = rpc_get_miner_info["Msg"]["mac"]
return str(mac).upper() return str(mac).upper()
except KeyError: except KeyError:
pass pass
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
mac = api_summary["SUMMARY"][0]["MAC"] mac = rpc_summary["SUMMARY"][0]["MAC"]
return str(mac).upper() return str(mac).upper()
except LookupError: except LookupError:
pass pass
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_get_version: dict = None) -> Optional[str]:
if api_get_version is None: if rpc_get_version is None:
try: try:
api_get_version = await self.api.get_version() rpc_get_version = await self.rpc.get_version()
except APIError: except APIError:
pass pass
if api_get_version is not None: if rpc_get_version is not None:
if "Code" in api_get_version.keys(): if "Code" in rpc_get_version.keys():
if api_get_version["Code"] == 131: if rpc_get_version["Code"] == 131:
try: try:
api_ver = api_get_version["Msg"] rpc_ver = rpc_get_version["Msg"]
if not isinstance(api_ver, str): if not isinstance(rpc_ver, str):
api_ver = api_ver["api_ver"] rpc_ver = rpc_ver["rpc_ver"]
self.api_ver = api_ver.replace("whatsminer v", "") self.api_ver = rpc_ver.replace("whatsminer v", "")
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
else: else:
self.api.api_ver = self.api_ver self.rpc.rpc_ver = self.api_ver
return self.api_ver return self.api_ver
return self.api_ver return self.api_ver
async def _get_fw_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]: ) -> Optional[str]:
if api_get_version is None: if rpc_get_version is None:
try: try:
api_get_version = await self.api.get_version() rpc_get_version = await self.rpc.get_version()
except APIError: except APIError:
pass pass
if api_get_version is not None: if rpc_get_version is not None:
if "Code" in api_get_version.keys(): if "Code" in rpc_get_version.keys():
if api_get_version["Code"] == 131: if rpc_get_version["Code"] == 131:
try: try:
self.fw_ver = api_get_version["Msg"]["fw_ver"] self.fw_ver = rpc_get_version["Msg"]["fw_ver"]
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
else: else:
return self.fw_ver return self.fw_ver
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary: if rpc_summary:
try: try:
self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace( self.fw_ver = rpc_summary["SUMMARY"][0]["Firmware Version"].replace(
"'", "" "'", ""
) )
except LookupError: except LookupError:
@@ -374,50 +369,50 @@ class BTMiner(BaseMiner):
return self.fw_ver 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 hostname = None
if api_get_miner_info is None: if rpc_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() rpc_get_miner_info = await self.rpc.get_miner_info()
except APIError: except APIError:
return None # only one way to get this 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: try:
hostname = api_get_miner_info["Msg"]["hostname"] hostname = rpc_get_miner_info["Msg"]["hostname"]
except KeyError: except KeyError:
return None return None
return hostname return hostname
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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: except LookupError:
pass pass
async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_devs: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if api_devs is None: if rpc_devs is None:
try: try:
api_devs = await self.api.devs() rpc_devs = await self.rpc.devs()
except APIError: except APIError:
pass pass
if api_devs is not None: if rpc_devs is not None:
try: try:
for board in api_devs["DEVS"]: for board in rpc_devs["DEVS"]:
if len(hashboards) < board["ASC"] + 1: if len(hashboards) < board["ASC"] + 1:
hashboards.append( hashboards.append(
HashBoard( HashBoard(
@@ -438,62 +433,62 @@ class BTMiner(BaseMiner):
return hashboards return hashboards
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]: async def _get_env_temp(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return api_summary["SUMMARY"][0]["Env Temp"] return rpc_summary["SUMMARY"][0]["Env Temp"]
except LookupError: except LookupError:
pass pass
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_summary: dict = None) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
wattage = api_summary["SUMMARY"][0]["Power"] wattage = rpc_summary["SUMMARY"][0]["Power"]
return wattage if not wattage == -1 else None return wattage if not wattage == -1 else None
except LookupError: except LookupError:
pass pass
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: async def _get_wattage_limit(self, rpc_summary: dict = None) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return api_summary["SUMMARY"][0]["Power Limit"] return rpc_summary["SUMMARY"][0]["Power Limit"]
except LookupError: except LookupError:
pass pass
async def _get_fans( 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]: ) -> List[Fan]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
fans = [Fan() for _ in range(self.expected_fans)] fans = [Fan() for _ in range(self.expected_fans)]
if api_summary is not None: if rpc_summary is not None:
try: try:
if self.expected_fans > 0: if self.expected_fans > 0:
fans = [ fans = [
Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(rpc_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 Out", 0)),
] ]
except LookupError: except LookupError:
pass pass
@@ -501,45 +496,45 @@ class BTMiner(BaseMiner):
return fans return fans
async def _get_fan_psu( 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]: ) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Power Fanspeed"]) return int(rpc_summary["SUMMARY"][0]["Power Fanspeed"])
except LookupError: except LookupError:
pass pass
if api_get_psu is None: if rpc_get_psu is None:
try: try:
api_get_psu = await self.api.get_psu() rpc_get_psu = await self.rpc.get_psu()
except APIError: except APIError:
pass pass
if api_get_psu is not None: if rpc_get_psu is not None:
try: try:
return int(api_get_psu["Msg"]["fan_speed"]) return int(rpc_get_psu["Msg"]["fan_speed"])
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
async def _get_errors( 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]: ) -> List[MinerErrorData]:
errors = [] 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: try:
api_get_error_code = await self.api.get_error_code() rpc_get_error_code = await self.rpc.get_error_code()
except APIError: except APIError:
pass pass
if api_get_error_code is not None: if rpc_get_error_code is not None:
try: 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): if isinstance(err, dict):
for code in err: for code in err:
errors.append(WhatsminerError(error_code=int(code))) errors.append(WhatsminerError(error_code=int(code)))
@@ -548,48 +543,48 @@ class BTMiner(BaseMiner):
except KeyError: except KeyError:
pass pass
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]): for i in range(rpc_summary["SUMMARY"][0]["Error Code Count"]):
err = api_summary["SUMMARY"][0].get(f"Error Code {i}") err = rpc_summary["SUMMARY"][0].get(f"Error Code {i}")
if err: if err:
errors.append(WhatsminerError(error_code=err)) errors.append(WhatsminerError(error_code=err))
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
return errors return errors
async def _get_expected_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
if expected_hashrate: if expected_hashrate:
return round(expected_hashrate / 1000, 2) return round(expected_hashrate / 1000, 2)
except LookupError: except LookupError:
pass pass
async def _get_fault_light(self, api_get_miner_info: dict = None) -> Optional[bool]: async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> Optional[bool]:
if api_get_miner_info is None: if rpc_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() rpc_get_miner_info = await self.rpc.get_miner_info()
except APIError: except APIError:
if not self.light: if not self.light:
self.light = False self.light = False
if api_get_miner_info is not None: if rpc_get_miner_info is not None:
try: try:
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto") self.light = not (rpc_get_miner_info["Msg"]["ledstat"] == "auto")
except KeyError: except KeyError:
pass pass
@@ -605,46 +600,46 @@ class BTMiner(BaseMiner):
): ):
if not hostname: if not hostname:
hostname = await self.get_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 ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
) )
async def set_dhcp(self, hostname: str = None): async def set_dhcp(self, hostname: str = None):
if hostname: if hostname:
await self.set_hostname(hostname) await self.set_hostname(hostname)
await self.api.net_config() await self.rpc.net_config()
async def set_hostname(self, hostname: str): 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]: async def _is_mining(self, rpc_status: dict = None) -> Optional[bool]:
if api_status is None: if rpc_status is None:
try: try:
api_status = await self.api.status() rpc_status = await self.rpc.status()
except APIError: except APIError:
pass pass
if api_status is not None: if rpc_status is not None:
try: try:
if api_status["Msg"].get("btmineroff"): if rpc_status["Msg"].get("btmineroff"):
try: try:
await self.api.devdetails() await self.rpc.devdetails()
except APIError: except APIError:
return False return False
return True return True
return True if api_status["Msg"]["mineroff"] == "false" else False return True if rpc_status["Msg"]["mineroff"] == "false" else False
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(rpc_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -18,44 +18,39 @@ from typing import Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.cgminer import CGMinerRPCAPI from pyasic.rpc.cgminer import CGMinerRPCAPI
CGMINER_DATA_LOC = DataLocations( CGMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -64,15 +59,15 @@ CGMINER_DATA_LOC = DataLocations(
class CGMiner(BaseMiner): class CGMiner(BaseMiner):
"""Base handler for CGMiner based miners""" """Base handler for CGMiner based miners"""
_api_cls = CGMinerRPCAPI _rpc_cls = CGMinerRPCAPI
api: CGMinerRPCAPI rpc: CGMinerRPCAPI
data_locations = CGMINER_DATA_LOC data_locations = CGMINER_DATA_LOC
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
try: try:
pools = await self.api.pools() pools = await self.rpc.pools()
except APIError: except APIError:
return self.config return self.config
@@ -83,60 +78,60 @@ class CGMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = rpc_version["VERSION"][0]["API"]
except LookupError: except LookupError:
pass pass
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
if api_version is None: if rpc_version is None:
try: try:
api_version = await self.api.version() rpc_version = await self.rpc.version()
except APIError: except APIError:
pass pass
if api_version is not None: if rpc_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"] self.fw_ver = rpc_version["VERSION"][0]["CGMiner"]
except LookupError: except LookupError:
pass pass
return self.fw_ver 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]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return round( 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): except (LookupError, ValueError, TypeError):
pass pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -21,13 +21,8 @@ from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
DataFunction,
DataLocations,
DataOptions,
WebAPICommand,
)
from pyasic.web.epic import ePICWebAPI from pyasic.web.epic import ePICWebAPI
EPIC_DATA_LOC = DataLocations( EPIC_DATA_LOC = DataLocations(
@@ -112,6 +107,31 @@ class ePIC(BaseMiner):
self.config = cfg self.config = cfg
return self.config 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: async def restart_backend(self) -> bool:
data = await self.web.restart_epic() data = await self.web.restart_epic()
if data: if data:

View File

@@ -15,12 +15,12 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List from typing import List
from pyasic.config import MinerConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
from pyasic.miners.base import ( from pyasic.miners.data import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
@@ -37,7 +37,7 @@ GOLDSHELL_DATA_LOC = DataLocations(
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
@@ -45,22 +45,22 @@ GOLDSHELL_DATA_LOC = DataLocations(
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[ [
RPCAPICommand("api_devs", "devs"), RPCAPICommand("rpc_devs", "devs"),
RPCAPICommand("api_devdetails", "devdetails"), RPCAPICommand("rpc_devdetails", "devdetails"),
], ],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -74,6 +74,8 @@ class GoldshellMiner(BFGMiner):
data_locations = GOLDSHELL_DATA_LOC data_locations = GOLDSHELL_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
try: try:
@@ -96,13 +98,18 @@ class GoldshellMiner(BFGMiner):
) )
self.config = config self.config = config
cfg = config.as_goldshell(user_suffix=user_suffix)
# send them back 1 at a time # send them back 1 at a time
for pool in config.as_goldshell(user_suffix=user_suffix)["pools"]: for pool in cfg["pools"]:
await self.web.newpool( await self.web.newpool(
url=pool["url"], user=pool["user"], password=pool["pass"] url=pool["url"], user=pool["user"], password=pool["pass"]
) )
settings = await self.web.setting()
for new_setting in cfg["settings"]:
settings[new_setting] = cfg["settings"][new_setting]
await self.web.set_setting(settings)
async def _get_mac(self, web_setting: dict = None) -> str: async def _get_mac(self, web_setting: dict = None) -> str:
if web_setting is None: if web_setting is None:
try: try:
@@ -130,11 +137,11 @@ class GoldshellMiner(BFGMiner):
pass pass
async def _get_hashboards( async def _get_hashboards(
self, api_devs: dict = None, api_devdetails: dict = None self, rpc_devs: dict = None, rpc_devdetails: dict = None
) -> List[HashBoard]: ) -> List[HashBoard]:
if api_devs is None: if rpc_devs is None:
try: try:
api_devs = await self.api.devs() rpc_devs = await self.rpc.devs()
except APIError: except APIError:
pass pass
@@ -143,9 +150,9 @@ class GoldshellMiner(BFGMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if api_devs is not None: if rpc_devs is not None:
if api_devs.get("DEVS"): if rpc_devs.get("DEVS"):
for board in api_devs["DEVS"]: for board in rpc_devs["DEVS"]:
if board.get("ID") is not None: if board.get("ID") is not None:
try: try:
b_id = board["ID"] b_id = board["ID"]
@@ -157,17 +164,17 @@ class GoldshellMiner(BFGMiner):
except KeyError: except KeyError:
pass pass
else: else:
logger.error(self, api_devs) logger.error(self, rpc_devs)
if api_devdetails is None: if rpc_devdetails is None:
try: try:
api_devdetails = await self.api.devdetails() rpc_devdetails = await self.rpc.devdetails()
except APIError: except APIError:
pass pass
if api_devdetails is not None: if rpc_devdetails is not None:
if api_devdetails.get("DEVS"): if rpc_devdetails.get("DEVS"):
for board in api_devdetails["DEVS"]: for board in rpc_devdetails["DEVS"]:
if board.get("ID") is not None: if board.get("ID") is not None:
try: try:
b_id = board["ID"] b_id = board["ID"]
@@ -175,6 +182,24 @@ class GoldshellMiner(BFGMiner):
except KeyError: except KeyError:
pass pass
else: else:
logger.error(self, api_devdetails) logger.error(self, rpc_devdetails)
return hashboards return hashboards
async def stop_mining(self) -> bool:
settings = await self.web.setting()
mode = MiningModeConfig.sleep()
cfg = mode.as_goldshell()
for new_setting in cfg["settings"]:
settings[new_setting] = cfg["settings"][new_setting]
await self.web.set_setting(settings)
return True
async def resume_mining(self) -> bool:
settings = await self.web.setting()
mode = MiningModeConfig.normal()
cfg = mode.as_goldshell()
for new_setting in cfg["settings"]:
settings[new_setting] = cfg["settings"][new_setting]
await self.web.set_setting(settings)
return True

View File

@@ -21,7 +21,7 @@ from pyasic.data.error_codes import MinerErrorData
from pyasic.data.error_codes.innosilicon import InnosiliconError from pyasic.data.error_codes.innosilicon import InnosiliconError
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner from pyasic.miners.backends import CGMiner
from pyasic.miners.base import ( from pyasic.miners.data import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
@@ -41,23 +41,23 @@ INNOSILICON_DATA_LOC = DataLocations(
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[ [
RPCAPICommand("api_summary", "summary"), RPCAPICommand("rpc_summary", "summary"),
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[ [
RPCAPICommand("api_stats", "stats"), RPCAPICommand("rpc_stats", "stats"),
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
@@ -65,7 +65,7 @@ INNOSILICON_DATA_LOC = DataLocations(
"_get_wattage", "_get_wattage",
[ [
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
RPCAPICommand("api_stats", "stats"), RPCAPICommand("rpc_stats", "stats"),
], ],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
@@ -88,7 +88,7 @@ INNOSILICON_DATA_LOC = DataLocations(
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -168,14 +168,14 @@ class Innosilicon(CGMiner):
pass pass
async def _get_hashrate( 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]: ) -> Optional[float]:
if web_get_all: if web_get_all:
web_get_all = web_get_all["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: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
@@ -193,14 +193,14 @@ class Innosilicon(CGMiner):
except KeyError: except KeyError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (KeyError, IndexError):
pass pass
async def _get_hashboards( 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]: ) -> List[HashBoard]:
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
@@ -210,9 +210,9 @@ class Innosilicon(CGMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
@@ -224,9 +224,9 @@ class Innosilicon(CGMiner):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if api_stats is not None: if rpc_stats is not None:
if api_stats.get("STATS"): if rpc_stats.get("STATS"):
for board in api_stats["STATS"]: for board in rpc_stats["STATS"]:
try: try:
idx = board["Chain ID"] idx = board["Chain ID"]
chips = board["Num active chips"] chips = board["Num active chips"]
@@ -258,7 +258,7 @@ class Innosilicon(CGMiner):
return hashboards return hashboards
async def _get_wattage( 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]: ) -> Optional[int]:
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
@@ -277,15 +277,15 @@ class Innosilicon(CGMiner):
except KeyError: except KeyError:
pass pass
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
if api_stats.get("STATS"): if rpc_stats.get("STATS"):
for board in api_stats["STATS"]: for board in rpc_stats["STATS"]:
try: try:
wattage = board["power"] wattage = board["power"]
except KeyError: except KeyError:

View File

@@ -18,43 +18,38 @@ from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import BaseMiner
BaseMiner, from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
from pyasic.rpc.luxminer import LUXMinerRPCAPI from pyasic.rpc.luxminer import LUXMinerRPCAPI
LUXMINER_DATA_LOC = DataLocations( LUXMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", "_get_mac",
[RPCAPICommand("api_config", "config")], [RPCAPICommand("rpc_config", "config")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[RPCAPICommand("api_power", "power")], [RPCAPICommand("rpc_power", "power")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_fans", "fans")], [RPCAPICommand("rpc_fans", "fans")],
), ),
str(DataOptions.UPTIME): DataFunction( 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): class LUXMiner(BaseMiner):
"""Handler for LuxOS miners""" """Handler for LuxOS miners"""
_api_cls = LUXMinerRPCAPI _rpc_cls = LUXMinerRPCAPI
api: LUXMinerRPCAPI rpc: LUXMinerRPCAPI
firmware = "LuxOS" firmware = "LuxOS"
@@ -72,14 +67,14 @@ class LUXMiner(BaseMiner):
async def _get_session(self) -> Optional[str]: async def _get_session(self) -> Optional[str]:
try: try:
data = await self.api.session() data = await self.rpc.session()
if not data["SESSION"][0]["SessionID"] == "": if not data["SESSION"][0]["SessionID"] == "":
return data["SESSION"][0]["SessionID"] return data["SESSION"][0]["SessionID"]
except APIError: except APIError:
pass pass
try: try:
data = await self.api.logon() data = await self.rpc.logon()
return data["SESSION"][0]["SessionID"] return data["SESSION"][0]["SessionID"]
except (LookupError, APIError): except (LookupError, APIError):
return return
@@ -88,7 +83,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.ledset(session_id, "red", "blink") await self.rpc.ledset(session_id, "red", "blink")
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -98,7 +93,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.ledset(session_id, "red", "off") await self.rpc.ledset(session_id, "red", "off")
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -111,7 +106,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.resetminer(session_id) await self.rpc.resetminer(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -121,7 +116,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.curtail(session_id) await self.rpc.curtail(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -131,7 +126,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.wakeup(session_id) await self.rpc.wakeup(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -140,7 +135,7 @@ class LUXMiner(BaseMiner):
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
await self.api.rebootdevice(session_id) await self.rpc.rebootdevice(session_id)
return True return True
except (APIError, LookupError): except (APIError, LookupError):
pass pass
@@ -153,48 +148,48 @@ class LUXMiner(BaseMiner):
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### 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 mac = None
if api_config is None: if rpc_config is None:
try: try:
api_config = await self.api.config() rpc_config = await self.rpc.config()
except APIError: except APIError:
return None return None
if api_config is not None: if rpc_config is not None:
try: try:
mac = api_config["CONFIG"][0]["MACAddr"] mac = rpc_config["CONFIG"][0]["MACAddr"]
except KeyError: except KeyError:
return None return None
return mac return mac
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: 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): except (LookupError, ValueError, TypeError):
pass pass
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = rpc_stats["STATS"]
if len(boards) > 1: if len(boards) > 1:
for board_num in range(1, 16, 5): for board_num in range(1, 16, 5):
for _b_num in range(5): for _b_num in range(5):
@@ -236,48 +231,48 @@ class LUXMiner(BaseMiner):
return hashboards return hashboards
async def _get_wattage(self, api_power: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]:
if api_power is None: if rpc_power is None:
try: try:
api_power = await self.api.power() rpc_power = await self.rpc.power()
except APIError: except APIError:
pass pass
if api_power is not None: if rpc_power is not None:
try: try:
return api_power["POWER"][0]["Watts"] return rpc_power["POWER"][0]["Watts"]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
async def _get_fans(self, api_fans: dict = None) -> List[Fan]: async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
if api_fans is None: if rpc_fans is None:
try: try:
api_fans = await self.api.fans() rpc_fans = await self.rpc.fans()
except APIError: except APIError:
pass pass
fans = [] fans = []
if api_fans is not None: if rpc_fans is not None:
for fan in range(self.expected_fans): for fan in range(self.expected_fans):
try: try:
fans.append(Fan(api_fans["FANS"][fan]["RPM"])) fans.append(Fan(rpc_fans["FANS"][fan]["RPM"]))
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
fans.append(Fan()) fans.append(Fan())
return fans 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]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
try: try:
rate_unit = api_stats["STATS"][1]["rate_unit"] rate_unit = rpc_stats["STATS"][1]["rate_unit"]
except KeyError: except KeyError:
rate_unit = "GH" rate_unit = "GH"
if rate_unit == "GH": if rate_unit == "GH":
@@ -289,15 +284,15 @@ class LUXMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
if api_stats is None: if rpc_stats is None:
try: try:
api_stats = await self.api.stats() rpc_stats = await self.rpc.stats()
except APIError: except APIError:
pass pass
if api_stats is not None: if rpc_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -1,18 +1,16 @@
# ------------------------------------------------------------------------------ # Copyright 2022 Upstream Data Inc
# Copyright 2022 Upstream Data Inc - #
# - # Licensed under the Apache License, Version 2.0 (the "License");
# Licensed under the Apache License, Version 2.0 (the "License"); - # you may not use this file except in compliance with the License.
# you may not use this file except in compliance with the License. - # You may obtain a copy of the License at
# You may obtain a copy of the License at - #
# - # http://www.apache.org/licenses/LICENSE-2.0
# http://www.apache.org/licenses/LICENSE-2.0 - #
# - # Unless required by applicable law or agreed to in writing, software
# Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS,
# distributed under the License is distributed on an "AS IS" BASIS, - # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - # See the License for the specific language governing permissions and
# See the License for the specific language governing permissions and - # limitations under the License.
# limitations under the License. -
# ------------------------------------------------------------------------------
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
@@ -32,7 +30,7 @@ class UnknownMiner(BaseMiner):
) -> None: ) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
self.api = UnknownRPCAPI(ip) self.rpc = UnknownRPCAPI(ip)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Unknown: {str(self.ip)}" return f"Unknown: {str(self.ip)}"

View File

@@ -19,7 +19,7 @@ from typing import Optional
from pyasic import MinerConfig from pyasic import MinerConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.base import ( from pyasic.miners.data import (
DataFunction, DataFunction,
DataLocations, DataLocations,
DataOptions, DataOptions,
@@ -36,7 +36,7 @@ VNISH_DATA_LOC = DataLocations(
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("api_version", "version")], [RPCAPICommand("rpc_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
@@ -48,15 +48,15 @@ VNISH_DATA_LOC = DataLocations(
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("rpc_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
@@ -68,11 +68,11 @@ VNISH_DATA_LOC = DataLocations(
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", "_get_uptime",
[RPCAPICommand("api_stats", "stats")], [RPCAPICommand("rpc_stats", "stats")],
), ),
} }
) )
@@ -172,18 +172,18 @@ class VNish(BMMiner):
except KeyError: except KeyError:
pass 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 # get hr from API
if api_summary is None: if rpc_summary is None:
try: try:
api_summary = await self.api.summary() rpc_summary = await self.rpc.summary()
except APIError: except APIError:
pass pass
if api_summary is not None: if rpc_summary is not None:
try: try:
return round( 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): except (LookupError, ValueError, TypeError):
pass pass
@@ -205,13 +205,14 @@ class VNish(BMMiner):
if web_summary is None: if web_summary is None:
web_summary = await self.web.summary() web_summary = await self.web.summary()
fw_ver = None
if web_summary is not None: if web_summary is not None:
try: try:
fw_ver = web_summary["miner"]["miner_type"] fw_ver = web_summary["miner"]["miner_type"]
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "") fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
return fw_ver return fw_ver
except KeyError: except LookupError:
pass return fw_ver
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
try: try:

View File

@@ -16,8 +16,6 @@
import asyncio import asyncio
import ipaddress import ipaddress
import warnings import warnings
from dataclasses import dataclass, field, make_dataclass
from enum import Enum
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
from pyasic.config import MinerConfig 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.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.data import DataLocations, DataOptions, RPCAPICommand, WebAPICommand
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
],
)
class MinerProtocol(Protocol): class MinerProtocol(Protocol):
_api_cls: Type = None _rpc_cls: Type = None
_web_cls: Type = None _web_cls: Type = None
_ssh_cls: Type = None _ssh_cls: Type = None
ip: str = None ip: str = None
api: _api_cls = None rpc: _rpc_cls = None
web: _web_cls = None web: _web_cls = None
ssh: _ssh_cls = None ssh: _ssh_cls = None
@@ -153,6 +73,10 @@ class MinerProtocol(Protocol):
model_data.append(f"({self.firmware})") model_data.append(f"({self.firmware})")
return " ".join(model_data) return " ".join(model_data)
@property
def api(self):
return self.rpc
async def check_light(self) -> bool: async def check_light(self) -> bool:
return await self.get_fault_light() return await self.get_fault_light()
@@ -568,8 +492,8 @@ class BaseMiner(MinerProtocol):
) )
# interfaces # interfaces
if self._api_cls is not None: if self._rpc_cls is not None:
self.api = self._api_cls(ip) self.rpc = self._rpc_cls(ip)
if self._web_cls is not None: if self._web_cls is not None:
self.web = self._web_cls(ip) self.web = self._web_cls(ip)
if self._ssh_cls is not None: 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 - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import enum import enum
import ipaddress import ipaddress
import json import json
import re import re
from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union from typing import Any, AsyncGenerator, Callable
import anyio import anyio
import httpx import httpx
@@ -26,23 +28,25 @@ import httpx
from pyasic import settings from pyasic import settings
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.antminer import * from pyasic.miners.antminer import *
from pyasic.miners.auradine import *
from pyasic.miners.avalonminer import * from pyasic.miners.avalonminer import *
from pyasic.miners.backends import ( from pyasic.miners.backends import (
Auradine,
AvalonMiner, AvalonMiner,
BMMiner, BMMiner,
BOSMiner, BOSMiner,
BTMiner, BTMiner,
GoldshellMiner, GoldshellMiner,
Hiveon, Hiveon,
Innosilicon,
LUXMiner, LUXMiner,
VNish, VNish,
ePIC, ePIC,
) )
from pyasic.miners.backends.innosilicon import Innosilicon from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner from pyasic.miners.base import AnyMiner
from pyasic.miners.goldshell import * from pyasic.miners.goldshell import *
from pyasic.miners.innosilicon import * from pyasic.miners.innosilicon import *
from pyasic.miners.unknown import UnknownMiner
from pyasic.miners.whatsminer import * from pyasic.miners.whatsminer import *
@@ -57,6 +61,7 @@ class MinerTypes(enum.Enum):
HIVEON = 7 HIVEON = 7
LUX_OS = 8 LUX_OS = 8
EPIC = 9 EPIC = 9
AURADINE = 10
MINER_CLASSES = { MINER_CLASSES = {
@@ -284,7 +289,9 @@ MINER_CLASSES = {
"M50S++VK30": BTMinerM50SPlusPlusVK30, "M50S++VK30": BTMinerM50SPlusPlusVK30,
"M53VH30": BTMinerM53VH30, "M53VH30": BTMinerM53VH30,
"M53SVH30": BTMinerM53SVH30, "M53SVH30": BTMinerM53SVH30,
"M53SVJ40": BTMinerM53SVJ40,
"M53S+VJ30": BTMinerM53SPlusVJ30, "M53S+VJ30": BTMinerM53SPlusVJ30,
"M53S++VK10": BTMinerM53SPlusPlusVK10,
"M56VH30": BTMinerM56VH30, "M56VH30": BTMinerM56VH30,
"M56SVH30": BTMinerM56SVH30, "M56SVH30": BTMinerM56SVH30,
"M56S+VJ30": BTMinerM56SPlusVJ30, "M56S+VJ30": BTMinerM56SPlusVJ30,
@@ -335,6 +342,8 @@ MINER_CLASSES = {
"GOLDSHELL HS5": GoldshellHS5, "GOLDSHELL HS5": GoldshellHS5,
"GOLDSHELL KD5": GoldshellKD5, "GOLDSHELL KD5": GoldshellKD5,
"GOLDSHELL KDMAX": GoldshellKDMax, "GOLDSHELL KDMAX": GoldshellKDMax,
"GOLDSHELL KDBOXII": GoldshellKDBoxII,
"GOLDSHELL KDBOXPRO": GoldshellKDBoxPro,
}, },
MinerTypes.BRAIINS_OS: { MinerTypes.BRAIINS_OS: {
None: BOSMiner, None: BOSMiner,
@@ -392,10 +401,20 @@ MINER_CLASSES = {
None: LUXMiner, None: LUXMiner,
"ANTMINER S9": LUXMinerS9, "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 res = None
for fut in asyncio.as_completed(tasks): for fut in asyncio.as_completed(tasks):
res = await fut res = await fut
@@ -411,15 +430,9 @@ async def concurrent_get_first_result(tasks: list, verification_func: Callable):
class MinerFactory: class MinerFactory:
def __init__(self):
self.cache = {}
def clear_cached_miners(self):
self.cache = {}
async def get_multiple_miners( async def get_multiple_miners(
self, ips: List[str], limit: int = 200 self, ips: list[str], limit: int = 200
) -> List[AnyMiner]: ) -> list[AnyMiner]:
results = [] results = []
async for miner in self.get_miner_generator(ips, limit): async for miner in self.get_miner_generator(ips, limit):
@@ -427,7 +440,9 @@ class MinerFactory:
return results 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 = [] tasks = []
semaphore = asyncio.Semaphore(limit) semaphore = asyncio.Semaphore(limit)
@@ -440,10 +455,8 @@ class MinerFactory:
if result is not None: if result is not None:
yield result yield result
async def get_miner(self, ip: str): async def get_miner(self, ip: str | ipaddress.ip_address) -> AnyMiner | None:
ip = str(ip) ip = str(ip)
if ip in self.cache:
return self.cache[ip]
miner_type = None miner_type = None
@@ -472,6 +485,7 @@ class MinerFactory:
MinerTypes.EPIC: self.get_miner_model_epic, MinerTypes.EPIC: self.get_miner_model_epic,
MinerTypes.HIVEON: self.get_miner_model_hiveon, MinerTypes.HIVEON: self.get_miner_model_hiveon,
MinerTypes.LUX_OS: self.get_miner_model_luxos, MinerTypes.LUX_OS: self.get_miner_model_luxos,
MinerTypes.AURADINE: self.get_miner_model_auradine,
} }
fn = miner_model_fns.get(miner_type) fn = miner_model_fns.get(miner_type)
@@ -491,11 +505,9 @@ class MinerFactory:
miner_model=miner_model, miner_model=miner_model,
) )
if miner is not None and not isinstance(miner, UnknownMiner):
self.cache[ip] = miner
return miner return miner
async def _get_miner_type(self, ip: str): async def _get_miner_type(self, ip: str) -> MinerTypes | None:
tasks = [ tasks = [
asyncio.create_task(self._get_miner_web(ip)), asyncio.create_task(self._get_miner_web(ip)),
asyncio.create_task(self._get_miner_socket(ip)), asyncio.create_task(self._get_miner_socket(ip)),
@@ -503,7 +515,7 @@ class MinerFactory:
return await concurrent_get_first_result(tasks, lambda x: x is not None) 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 = [] tasks = []
try: try:
urls = [f"http://{ip}/", f"https://{ip}/"] urls = [f"http://{ip}/", f"https://{ip}/"]
@@ -532,7 +544,7 @@ class MinerFactory:
@staticmethod @staticmethod
async def _web_ping( async def _web_ping(
session: httpx.AsyncClient, url: str session: httpx.AsyncClient, url: str
) -> Tuple[Optional[str], Optional[httpx.Response]]: ) -> tuple[str | None, httpx.Response | None]:
try: try:
resp = await session.get(url, follow_redirects=True) resp = await session.get(url, follow_redirects=True)
return resp.text, resp return resp.text, resp
@@ -546,7 +558,7 @@ class MinerFactory:
return None, None return None, None
@staticmethod @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( if web_resp.status_code == 401 and 'realm="antMiner' in web_resp.headers.get(
"www-authenticate", "" "www-authenticate", ""
): ):
@@ -571,8 +583,10 @@ class MinerFactory:
return MinerTypes.AVALONMINER return MinerTypes.AVALONMINER
if "DragonMint" in web_text: if "DragonMint" in web_text:
return MinerTypes.INNOSILICON 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 = [] tasks = []
try: try:
commands = ["version", "devdetails"] commands = ["version", "devdetails"]
@@ -596,7 +610,7 @@ class MinerFactory:
pass pass
@staticmethod @staticmethod
async def _socket_ping(ip: str, cmd: str) -> Optional[str]: async def _socket_ping(ip: str, cmd: str) -> str | None:
data = b"" data = b""
try: try:
reader, writer = await asyncio.wait_for( reader, writer = await asyncio.wait_for(
@@ -642,7 +656,7 @@ class MinerFactory:
return data.decode("utf-8") return data.decode("utf-8")
@staticmethod @staticmethod
def _parse_socket_type(data: str) -> MinerTypes: def _parse_socket_type(data: str) -> MinerTypes | None:
upper_data = data.upper() upper_data = data.upper()
if "BOSMINER" in upper_data or "BOSER" in upper_data: if "BOSMINER" in upper_data or "BOSER" in upper_data:
return MinerTypes.BRAIINS_OS return MinerTypes.BRAIINS_OS
@@ -656,21 +670,27 @@ class MinerFactory:
return MinerTypes.LUX_OS return MinerTypes.LUX_OS
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data: if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
return MinerTypes.ANTMINER return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data: if (
"INTCHAINS_QOMO" in upper_data
or "KDAMINER" in upper_data
or "BFGMINER" in upper_data
):
return MinerTypes.GOLDSHELL return MinerTypes.GOLDSHELL
if "AVALON" in upper_data: if "AVALON" in upper_data:
return MinerTypes.AVALONMINER return MinerTypes.AVALONMINER
if "GCMINER" in upper_data or "FLUXOS" in upper_data:
return MinerTypes.AURADINE
async def send_web_command( async def send_web_command(
self, self,
ip: Union[ipaddress.ip_address, str], ip: str,
location: str, location: str,
auth: Optional[httpx.DigestAuth] = None, auth: httpx.DigestAuth = None,
) -> Optional[dict]: ) -> dict | None:
async with httpx.AsyncClient(transport=settings.transport()) as session: async with httpx.AsyncClient(transport=settings.transport()) as session:
try: try:
data = await session.get( data = await session.get(
f"http://{str(ip)}{location}", f"http://{ip}{location}",
auth=auth, auth=auth,
timeout=settings.get("factory_get_timeout", 3), timeout=settings.get("factory_get_timeout", 3),
) )
@@ -689,12 +709,10 @@ class MinerFactory:
else: else:
return json_data return json_data
async def send_api_command( async def send_api_command(self, ip: str, command: str) -> dict | None:
self, ip: Union[ipaddress.ip_address, str], command: str
) -> Optional[dict]:
data = b"" data = b""
try: try:
reader, writer = await asyncio.open_connection(str(ip), 4028) reader, writer = await asyncio.open_connection(ip, 4028)
except (ConnectionError, OSError): except (ConnectionError, OSError):
return return
cmd = {"command": command} cmd = {"command": command}
@@ -732,7 +750,7 @@ class MinerFactory:
return data return data
@staticmethod @staticmethod
async def _fix_api_data(data: bytes): async def _fix_api_data(data: bytes) -> str:
if data.endswith(b"\x00"): if data.endswith(b"\x00"):
str_data = data.decode("utf-8")[:-1] str_data = data.decode("utf-8")[:-1]
else: else:
@@ -769,9 +787,9 @@ class MinerFactory:
@staticmethod @staticmethod
def _select_miner_from_classes( def _select_miner_from_classes(
ip: ipaddress.ip_address, ip: ipaddress.ip_address,
miner_model: Union[str, None], miner_model: str | None,
miner_type: Union[MinerTypes, None], miner_type: MinerTypes | None,
) -> AnyMiner: ) -> AnyMiner | None:
try: try:
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip) return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
except LookupError: except LookupError:
@@ -779,7 +797,7 @@ class MinerFactory:
return MINER_CLASSES[miner_type][None](ip) return MINER_CLASSES[miner_type][None](ip)
return UnknownMiner(str(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 = [ tasks = [
asyncio.create_task(self._get_model_antminer_web(ip)), asyncio.create_task(self._get_model_antminer_web(ip)),
asyncio.create_task(self._get_model_antminer_sock(ip)), asyncio.create_task(self._get_model_antminer_sock(ip)),
@@ -787,7 +805,7 @@ class MinerFactory:
return await concurrent_get_first_result(tasks, lambda x: x is not None) 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 # last resort, this is slow
auth = httpx.DigestAuth("root", "root") auth = httpx.DigestAuth("root", "root")
web_json_data = await self.send_web_command( web_json_data = await self.send_web_command(
@@ -801,7 +819,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "version")
try: try:
miner_model = sock_json_data["VERSION"][0]["Type"] miner_model = sock_json_data["VERSION"][0]["Type"]
@@ -826,7 +844,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") json_data = await self.send_web_command(ip, "/mcb/status")
try: try:
@@ -836,7 +854,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "devdetails")
try: try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "") miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
@@ -846,7 +864,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "version")
try: try:
miner_model = sock_json_data["VERSION"][0]["PROD"] miner_model = sock_json_data["VERSION"][0]["PROD"]
@@ -857,7 +875,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass pass
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]: async def get_miner_model_innosilicon(self, ip: str) -> str | None:
try: try:
async with httpx.AsyncClient(transport=settings.transport()) as session: async with httpx.AsyncClient(transport=settings.transport()) as session:
auth_req = await session.post( auth_req = await session.post(
@@ -877,7 +895,7 @@ class MinerFactory:
except (httpx.HTTPError, LookupError): except (httpx.HTTPError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "devdetails")
try: try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace( miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace(
@@ -901,7 +919,7 @@ class MinerFactory:
except (httpx.HTTPError, LookupError): except (httpx.HTTPError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "stats")
try: try:
miner_model = sock_json_data["STATS"][0]["Type"] miner_model = sock_json_data["STATS"][0]["Type"]
@@ -919,7 +937,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
try: try:
miner_model = sock_json_data["Model"] miner_model = sock_json_data["Model"]
@@ -927,7 +945,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "version")
try: try:
miner_type = sock_json_data["VERSION"][0]["Type"] miner_type = sock_json_data["VERSION"][0]["Type"]
@@ -936,7 +954,7 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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") sock_json_data = await self.send_api_command(ip, "version")
try: try:
miner_model = sock_json_data["VERSION"][0]["Type"] miner_model = sock_json_data["VERSION"][0]["Type"]
@@ -948,5 +966,17 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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() 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. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import GoldshellMiner from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import CK5 from pyasic.miners.models import CK5
class GoldshellCK5(GoldshellMiner, CK5): class GoldshellCK5(GoldshellMiner, CK5):

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import GoldshellMiner from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import HS5 from pyasic.miners.models import HS5
class GoldshellHS5(GoldshellMiner, HS5): class GoldshellHS5(GoldshellMiner, HS5):

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import GoldshellMiner from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KD5 from pyasic.miners.models import KD5
class GoldshellKD5(GoldshellMiner, KD5): class GoldshellKD5(GoldshellMiner, KD5):

View File

@@ -0,0 +1,25 @@
# ------------------------------------------------------------------------------
# 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 pyasic.miners.backends import GoldshellMiner
from pyasic.miners.models import KDBoxII, KDBoxPro
class GoldshellKDBoxII(GoldshellMiner, KDBoxII):
pass
class GoldshellKDBoxPro(GoldshellMiner, KDBoxPro):
pass

View File

@@ -0,0 +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. -
# ------------------------------------------------------------------------------
from .KDBox import GoldshellKDBoxII, GoldshellKDBoxPro

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import GoldshellMiner from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KDMax from pyasic.miners.models import KDMax
class GoldshellKDMax(GoldshellMiner, KDMax): class GoldshellKDMax(GoldshellMiner, KDMax):

View File

@@ -14,4 +14,5 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X5 import * from .X5 import *
from .XBox import *
from .XMax import * from .XMax import *

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends.innosilicon import Innosilicon from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.types import A10X from pyasic.miners.models import A10X
class InnosiliconA10X(Innosilicon, A10X): class InnosiliconA10X(Innosilicon, A10X):

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends.innosilicon import Innosilicon from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.types import T3HPlus from pyasic.miners.models import T3HPlus
class InnosiliconT3HPlus(Innosilicon, T3HPlus): class InnosiliconT3HPlus(Innosilicon, T3HPlus):

View File

@@ -16,10 +16,8 @@
import asyncio import asyncio
from pyasic.misc import Singleton
class MinerListenerProtocol(asyncio.Protocol):
class _MinerListener:
def __init__(self): def __init__(self):
self.responses = {} self.responses = {}
self.transport = None self.transport = None
@@ -44,11 +42,12 @@ class _MinerListener:
pass pass
class MinerListener(metaclass=Singleton): class MinerListener:
def __init__(self): def __init__(self, bind_addr: str = "0.0.0.0"):
self.found_miners = [] self.found_miners = []
self.new_miner = None self.new_miner = None
self.stop = False self.stop = False
self.bind_addr = bind_addr
async def listen(self): async def listen(self):
self.stop = False self.stop = False
@@ -56,10 +55,10 @@ class MinerListener(metaclass=Singleton):
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
transport_14235, _ = await loop.create_datagram_endpoint( 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( transport_8888, _ = await loop.create_datagram_endpoint(
_MinerListener, local_addr=("0.0.0.0", 8888) MinerListenerProtocol, local_addr=(self.bind_addr, 8888)
) )
while True: while True:
@@ -75,21 +74,3 @@ class MinerListener(metaclass=Singleton):
async def cancel(self): async def cancel(self):
self.stop = True 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): class GoldshellMake(BaseMiner):
make = "Goldshell" make = "Goldshell"
class AuradineMake(BaseMiner):
make = "Auradine"

View File

@@ -15,6 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .antminer import * from .antminer import *
from .auradine import *
from .avalonminer import * from .avalonminer import *
from .goldshell import * from .goldshell import *
from .innosilicon import * from .innosilicon import *

Some files were not shown because too many files have changed in this diff Show More