Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099ec35a8f | ||
|
|
113dfb9170 | ||
|
|
8d19e0ebbb | ||
|
|
ec064eba65 | ||
|
|
3451127761 | ||
|
|
b3be52ca77 | ||
|
|
b6ec6caa72 | ||
|
|
8e81e18622 | ||
|
|
6ea26e0e19 | ||
|
|
be446f94c1 | ||
|
|
e21e340f60 | ||
|
|
f63d8f4b91 |
12
README.md
12
README.md
@@ -24,16 +24,20 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
|
||||
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
||||
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
||||
```
|
||||
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
```
|
||||
|
||||
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
||||
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
||||
```
|
||||
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
```
|
||||
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
##### Installing `pyasic`
|
||||
|
||||
@@ -22,13 +22,21 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
|
||||
## Installation
|
||||
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
||||
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
||||
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
||||
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
||||
```
|
||||
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
```
|
||||
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
##### Installing `pyasic`
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
[`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.
|
||||
[`MinerFactory`][pyasic.miners.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.
|
||||
|
||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||
|
||||
[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
[`MinerFactory`][pyasic.miners.factory.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
|
||||
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()`.
|
||||
|
||||
@@ -32,5 +32,5 @@ Finally, there is functionality to get multiple miners without using `asyncio.ga
|
||||
heading_level: 4
|
||||
|
||||
[`AnyMiner`][pyasic.miners.base.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner],
|
||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# pyasic
|
||||
## Miner APIs
|
||||
Each miner has a unique API that is used to communicate with it.
|
||||
## Miner RPC APIs
|
||||
Each miner has a unique RPC API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.rpc`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
All RPC API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
|
||||
14
docs/web/antminer.md
Normal file
14
docs/web/antminer.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# pyasic
|
||||
## AntminerModernWebAPI
|
||||
::: pyasic.web.antminer.AntminerModernWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AntminerOldWebAPI
|
||||
::: pyasic.web.antminer.AntminerOldWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
27
docs/web/api.md
Normal file
27
docs/web/api.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# pyasic
|
||||
## Miner Web APIs
|
||||
Each miner has a unique Web API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.web`.
|
||||
|
||||
All API implementations inherit from [`BaseWebAPI`][pyasic.web.BaseWebAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseWebAPI`][pyasic.web.BaseWebAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
Use these instead -
|
||||
|
||||
#### [AntminerModerNWebAPI][pyasic.web.antminer.AntminerModernWebAPI]
|
||||
#### [AntminerOldWebAPI][pyasic.web.antminer.AntminerOldWebAPI]
|
||||
#### [AuradineWebAPI][pyasic.web.auradine.AuradineWebAPI]
|
||||
#### [ePICWebAPI][pyasic.web.epic.ePICWebAPI]
|
||||
#### [GoldshellWebAPI][pyasic.web.goldshell.GoldshellWebAPI]
|
||||
#### [InnosiliconWebAPI][pyasic.web.innosilicon.InnosiliconWebAPI]
|
||||
#### [MaraWebAPI][pyasic.web.marathon.MaraWebAPI]
|
||||
#### [VNishWebAPI][pyasic.web.vnish.VNishWebAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseWebAPI
|
||||
::: pyasic.web.BaseWebAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
7
docs/web/auradine.md
Normal file
7
docs/web/auradine.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## AuradineWebAPI
|
||||
::: pyasic.web.auradine.AuradineWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/epic.md
Normal file
7
docs/web/epic.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## ePICWebAPI
|
||||
::: pyasic.web.epic.ePICWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/goldshell.md
Normal file
7
docs/web/goldshell.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## GoldshellWebAPI
|
||||
::: pyasic.web.goldshell.GoldshellWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/innosilicon.md
Normal file
7
docs/web/innosilicon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## InnosiliconWebAPI
|
||||
::: pyasic.web.innosilicon.InnosiliconWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/marathon.md
Normal file
7
docs/web/marathon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## MaraWebAPI
|
||||
::: pyasic.web.marathon.MaraWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/vnish.md
Normal file
7
docs/web/vnish.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## VNishWebAPI
|
||||
::: pyasic.web.vnish.VNishWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
14
mkdocs.yml
14
mkdocs.yml
@@ -22,6 +22,15 @@ nav:
|
||||
- CGMiner: "rpc/cgminer.md"
|
||||
- LUXMiner: "rpc/luxminer.md"
|
||||
- Unknown: "rpc/unknown.md"
|
||||
- Web APIs:
|
||||
- Intro: "web/api.md"
|
||||
- Antminer: "web/antminer.md"
|
||||
- Auradine: "web/auradine.md"
|
||||
- ePIC: "web/epic.md"
|
||||
- Goldshell: "web/goldshell.md"
|
||||
- Innosilicon: "web/innosilicon.md"
|
||||
- Marathon: "web/marathon.md"
|
||||
- VNish: "web/vnish.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
@@ -50,10 +59,15 @@ nav:
|
||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Whatsminer M6X: "miners/whatsminer/M6X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell X5: "miners/goldshell/X5.md"
|
||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||
- Goldshell XBox: "miners/goldshell/XBox.md"
|
||||
- Auradine AD: "miners/auradine/AD.md"
|
||||
- Auradine AI: "miners/auradine/AI.md"
|
||||
- Auradine AT: "miners/auradine/AT.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
@@ -25,6 +25,8 @@ from pyasic.misc import merge_dicts
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
"""Represents the configuration for a miner including pool configuration,
|
||||
fan mode, temperature settings, mining mode, and power scaling."""
|
||||
pools: PoolConfig = field(default_factory=PoolConfig.default)
|
||||
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default)
|
||||
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
|
||||
@@ -34,9 +36,11 @@ class MinerConfig:
|
||||
)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Converts the MinerConfig object to a dictionary."""
|
||||
return asdict(self)
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Antminers."""
|
||||
return {
|
||||
**self.fan_mode.as_am_modern(),
|
||||
"freq-level": "100",
|
||||
@@ -47,6 +51,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers."""
|
||||
return {
|
||||
**self.fan_mode.as_wm(),
|
||||
**self.mining_mode.as_wm(),
|
||||
@@ -56,6 +61,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for old versions of Antminers."""
|
||||
return {
|
||||
**self.fan_mode.as_am_old(),
|
||||
**self.mining_mode.as_am_old(),
|
||||
@@ -65,6 +71,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Goldshell miners."""
|
||||
return {
|
||||
**self.fan_mode.as_goldshell(),
|
||||
**self.mining_mode.as_goldshell(),
|
||||
@@ -74,6 +81,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Avalonminers."""
|
||||
return {
|
||||
**self.fan_mode.as_avalon(),
|
||||
**self.mining_mode.as_avalon(),
|
||||
@@ -83,6 +91,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Innosilicon miners."""
|
||||
return {
|
||||
**self.fan_mode.as_inno(),
|
||||
**self.mining_mode.as_inno(),
|
||||
@@ -92,6 +101,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the bosminer.toml format."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**self.mining_mode.as_bosminer(),
|
||||
@@ -100,6 +110,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
""""Generates the configuration in the format suitable for BOSer."""
|
||||
return {
|
||||
**self.fan_mode.as_boser(),
|
||||
**self.temperature.as_boser(),
|
||||
@@ -109,6 +120,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for ePIC miners."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
|
||||
**self.mining_mode.as_epic(),
|
||||
@@ -117,6 +129,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Auradine miners."""
|
||||
return {
|
||||
**self.fan_mode.as_auradine(),
|
||||
**self.temperature.as_auradine(),
|
||||
@@ -127,6 +140,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from a dictionary."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_dict(dict_conf.get("pools")),
|
||||
mining_mode=MiningModeConfig.from_dict(dict_conf.get("mining_mode")),
|
||||
@@ -137,10 +151,12 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from API pool data."""
|
||||
return cls(pools=PoolConfig.from_api(api_pools))
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_am_modern(web_conf),
|
||||
mining_mode=MiningModeConfig.from_am_modern(web_conf),
|
||||
@@ -149,18 +165,22 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
|
||||
return cls(pools=PoolConfig.from_inno(web_pools))
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from the bosminer.toml file, same as the `as_bosminer` dumps a dict for writing to that file as toml."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_bosminer(toml_conf),
|
||||
mining_mode=MiningModeConfig.from_bosminer(toml_conf),
|
||||
@@ -171,6 +191,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from gRPC configuration for BOSer."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_boser(grpc_miner_conf),
|
||||
mining_mode=MiningModeConfig.from_boser(grpc_miner_conf),
|
||||
@@ -181,6 +202,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for ePIC miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_epic(web_conf),
|
||||
fan_mode=FanModeConfig.from_epic(web_conf),
|
||||
@@ -190,6 +212,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web settings for VNish miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_vnish(web_settings),
|
||||
fan_mode=FanModeConfig.from_vnish(web_settings),
|
||||
@@ -199,6 +222,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Auradine miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_api(web_conf["pools"]),
|
||||
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
|
||||
|
||||
@@ -44,11 +44,21 @@ class FanModeNormal(MinerConfigValue):
|
||||
cls_conf["minimum_speed"] = web_cooling_settings["fan_min_duty"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_fan_conf: dict):
|
||||
cls_conf = {}
|
||||
if toml_fan_conf.get("min_fans") is not None:
|
||||
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "auto"}}
|
||||
return {
|
||||
"temp_control": {"mode": "auto"},
|
||||
"fan_control": {"min_fans": self.minimum_fans},
|
||||
}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {
|
||||
@@ -123,7 +133,9 @@ class FanModeImmersion(MinerConfigValue):
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "disabled"}}
|
||||
return {
|
||||
"fan_control": {"min_fans": 0},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": 0}}
|
||||
@@ -178,20 +190,28 @@ class FanModeConfig(MinerConfigOption):
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
if toml_conf.get("temp_control") is None:
|
||||
return cls.default()
|
||||
if toml_conf["temp_control"].get("mode") is None:
|
||||
try:
|
||||
mode = toml_conf["temp_control"]["mode"]
|
||||
fan_config = toml_conf.get("fan_control", {})
|
||||
if mode == "auto":
|
||||
return cls.normal().from_bosminer(fan_config)
|
||||
elif mode == "manual":
|
||||
if toml_conf.get("fan_control"):
|
||||
return cls.manual().from_bosminer(fan_config)
|
||||
return cls.manual()
|
||||
elif mode == "disabled":
|
||||
return cls.immersion()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
min_fans = toml_conf["fan_control"]["min_fans"]
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
mode = toml_conf["temp_control"]["mode"]
|
||||
if mode == "auto":
|
||||
return cls.normal()
|
||||
elif mode == "manual":
|
||||
if toml_conf.get("fan_control"):
|
||||
return cls.manual().from_bosminer(toml_conf["fan_control"])
|
||||
return cls.manual()
|
||||
elif mode == "disabled":
|
||||
if min_fans == 0:
|
||||
return cls.immersion()
|
||||
return cls.normal(minimum_fans=min_fans)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict):
|
||||
|
||||
@@ -20,4 +20,5 @@ from .cgminer import *
|
||||
from .epic import *
|
||||
from .hiveon import *
|
||||
from .luxos import *
|
||||
from .marathon import *
|
||||
from .vnish import *
|
||||
|
||||
46
pyasic/miners/antminer/marathon/X19/S19.py
Normal file
46
pyasic/miners/antminer/marathon/X19/S19.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 MaraMiner
|
||||
from pyasic.miners.models import S19, S19XP, S19j, S19jNoPIC, S19jPro, S19KPro, S19Pro
|
||||
|
||||
|
||||
class MaraS19(MaraMiner, S19):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19Pro(MaraMiner, S19Pro):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19XP(MaraMiner, S19XP):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19j(MaraMiner, S19j):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19jNoPIC(MaraMiner, S19jNoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19jPro(MaraMiner, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19KPro(MaraMiner, S19KPro):
|
||||
pass
|
||||
9
pyasic/miners/antminer/marathon/X19/__init__.py
Normal file
9
pyasic/miners/antminer/marathon/X19/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .S19 import (
|
||||
MaraS19,
|
||||
MaraS19j,
|
||||
MaraS19jNoPIC,
|
||||
MaraS19jPro,
|
||||
MaraS19KPro,
|
||||
MaraS19Pro,
|
||||
MaraS19XP,
|
||||
)
|
||||
22
pyasic/miners/antminer/marathon/X21/S21.py
Normal file
22
pyasic/miners/antminer/marathon/X21/S21.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 MaraMiner
|
||||
from pyasic.miners.models import S21
|
||||
|
||||
|
||||
class MaraS21(MaraMiner, S21):
|
||||
pass
|
||||
22
pyasic/miners/antminer/marathon/X21/T21.py
Normal file
22
pyasic/miners/antminer/marathon/X21/T21.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 MaraMiner
|
||||
from pyasic.miners.models import T21
|
||||
|
||||
|
||||
class MaraT21(MaraMiner, T21):
|
||||
pass
|
||||
2
pyasic/miners/antminer/marathon/X21/__init__.py
Normal file
2
pyasic/miners/antminer/marathon/X21/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .S21 import MaraS21
|
||||
from .T21 import MaraT21
|
||||
2
pyasic/miners/antminer/marathon/__init__.py
Normal file
2
pyasic/miners/antminer/marathon/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .X19 import *
|
||||
from .X21 import *
|
||||
@@ -26,5 +26,6 @@ from .goldshell import GoldshellMiner
|
||||
from .hiveon import Hiveon
|
||||
from .innosilicon import Innosilicon
|
||||
from .luxminer import LUXMiner
|
||||
from .marathon import MaraMiner
|
||||
from .vnish import VNish
|
||||
from .whatsminer import M2X, M3X, M5X, M6X
|
||||
|
||||
87
pyasic/miners/backends/marathon.py
Normal file
87
pyasic/miners/backends/marathon.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from typing import Optional
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.web.marathon import MaraWebAPI
|
||||
|
||||
MARA_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("rpc_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("rpc_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MaraMiner(AntminerModern):
|
||||
_web_cls = MaraWebAPI
|
||||
web: MaraWebAPI
|
||||
|
||||
data_locations = MARA_DATA_LOC
|
||||
|
||||
firmware = "MaraFW"
|
||||
|
||||
async def _get_wattage(self, web_brief: dict = None) -> Optional[int]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return web_brief["power_consumption_estimated"]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -30,19 +30,7 @@ from pyasic.logger import logger
|
||||
from pyasic.miners.antminer import *
|
||||
from pyasic.miners.auradine import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
from pyasic.miners.backends import (
|
||||
Auradine,
|
||||
AvalonMiner,
|
||||
BMMiner,
|
||||
BOSMiner,
|
||||
BTMiner,
|
||||
GoldshellMiner,
|
||||
Hiveon,
|
||||
Innosilicon,
|
||||
LUXMiner,
|
||||
VNish,
|
||||
ePIC,
|
||||
)
|
||||
from pyasic.miners.backends import *
|
||||
from pyasic.miners.backends.unknown import UnknownMiner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.blockminer import *
|
||||
@@ -64,6 +52,7 @@ class MinerTypes(enum.Enum):
|
||||
LUX_OS = 8
|
||||
EPIC = 9
|
||||
AURADINE = 10
|
||||
MARATHON = 11
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -424,7 +413,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S21": LUXMinerS21,
|
||||
},
|
||||
MinerTypes.AURADINE: {
|
||||
None: type("GoldshellUnknown", (Auradine, AuradineMake), {}),
|
||||
None: type("AuradineUnknown", (Auradine, AuradineMake), {}),
|
||||
"AT1500": AuradineFluxAT1500,
|
||||
"AT2860": AuradineFluxAT2860,
|
||||
"AT2880": AuradineFluxAT2880,
|
||||
@@ -433,6 +422,18 @@ MINER_CLASSES = {
|
||||
"AD2500": AuradineFluxAD2500,
|
||||
"AD3500": AuradineFluxAD3500,
|
||||
},
|
||||
MinerTypes.MARATHON: {
|
||||
None: MaraMiner,
|
||||
"ANTMINER S19": MaraS19,
|
||||
"ANTMINER S19 PRO": MaraS19Pro,
|
||||
"ANTMINER S19J": MaraS19j,
|
||||
"ANTMINER S19J88NOPIC": MaraS19jNoPIC,
|
||||
"ANTMINER S19J PRO": MaraS19jPro,
|
||||
"ANTMINER S19 XP": MaraS19XP,
|
||||
"ANTMINER S19K PRO": MaraS19KPro,
|
||||
"ANTMINER S21": MaraS21,
|
||||
"ANTMINER T21": MaraT21,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -508,6 +509,7 @@ class MinerFactory:
|
||||
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||
MinerTypes.AURADINE: self.get_miner_model_auradine,
|
||||
MinerTypes.MARATHON: self.get_miner_model_marathon,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
@@ -689,6 +691,8 @@ class MinerFactory:
|
||||
return MinerTypes.HIVEON
|
||||
if "LUXMINER" in upper_data:
|
||||
return MinerTypes.LUX_OS
|
||||
if "KAONSU" in upper_data:
|
||||
return MinerTypes.MARATHON
|
||||
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
|
||||
return MinerTypes.ANTMINER
|
||||
if (
|
||||
@@ -1001,6 +1005,21 @@ class MinerFactory:
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_miner_model_marathon(self, ip: str) -> str | None:
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
web_json_data = await self.send_web_command(
|
||||
ip, "/kaonsu/v1/overview", auth=auth
|
||||
)
|
||||
|
||||
try:
|
||||
miner_model = web_json_data["model"]
|
||||
if miner_model == "":
|
||||
return None
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@ from pyasic.ssh.base import BaseSSH
|
||||
|
||||
|
||||
class AntminerModernSSH(BaseSSH):
|
||||
"""
|
||||
Initialize an AntminerModernSSH instance.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address of the Antminer device.
|
||||
"""
|
||||
def __init__(self, ip: str):
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_antminer_ssh_password", "root")
|
||||
|
||||
@@ -4,32 +4,92 @@ from pyasic.ssh.base import BaseSSH
|
||||
|
||||
class BOSMinerSSH(BaseSSH):
|
||||
def __init__(self, ip: str):
|
||||
"""
|
||||
Initialize a BOSMinerSSH instance.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address of the BOSMiner device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_bosminer_ssh_password", "root")
|
||||
|
||||
async def get_board_info(self):
|
||||
"""
|
||||
Retrieve information about the BOSMiner board.
|
||||
|
||||
Returns:
|
||||
str: Information about the BOSMiner board.
|
||||
"""
|
||||
return await self.send_command("bosminer model -d")
|
||||
|
||||
async def fault_light_on(self):
|
||||
"""
|
||||
Turn on the fault light of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after turning on the fault light.
|
||||
"""
|
||||
return await self.send_command("miner fault_light on")
|
||||
|
||||
async def fault_light_off(self):
|
||||
"""
|
||||
Turn off the fault light of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after turning off the fault light.
|
||||
"""
|
||||
return await self.send_command("miner fault_light off")
|
||||
|
||||
async def restart_bosminer(self):
|
||||
"""
|
||||
Restart the BOSMiner service on the device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after restarting the BOSMiner service.
|
||||
"""
|
||||
return await self.send_command("/etc/init.d/bosminer restart")
|
||||
|
||||
async def reboot(self):
|
||||
"""
|
||||
Reboot the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after initiating the reboot process.
|
||||
"""
|
||||
return await self.send_command("/sbin/reboot")
|
||||
|
||||
async def get_config_file(self):
|
||||
"""
|
||||
Retrieve the configuration file of BOSMiner.
|
||||
|
||||
Returns:
|
||||
str: Content of the BOSMiner configuration file.
|
||||
"""
|
||||
return await self.send_command("cat /etc/bosminer.toml")
|
||||
|
||||
async def get_network_config(self):
|
||||
"""
|
||||
Retrieve the network configuration of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Content of the network configuration file.
|
||||
"""
|
||||
return await self.send_command("cat /etc/config/network")
|
||||
|
||||
async def get_hostname(self):
|
||||
"""
|
||||
Retrieve the hostname of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Hostname of the BOSMiner device.
|
||||
"""
|
||||
return await self.send_command("cat /proc/sys/kernel/hostname")
|
||||
|
||||
async def get_led_status(self):
|
||||
"""
|
||||
Retrieve the status of the LED on the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Status of the LED.
|
||||
"""
|
||||
return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .base import BaseWebAPI
|
||||
|
||||
from .antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
from .auradine import AuradineWebAPI
|
||||
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
|
||||
@@ -27,6 +27,11 @@ from pyasic.web.base import BaseWebAPI
|
||||
|
||||
class AntminerModernWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initialize the modern Antminer API client with a specific IP address.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Antminer device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "root"
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
@@ -39,6 +44,18 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Antminer device using HTTP digest authentication.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The CGI command to send.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
privileged (bool): If set to True, requires elevated privileges.
|
||||
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device or an empty dictionary if an error occurs.
|
||||
"""
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
@@ -66,6 +83,16 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously.
|
||||
|
||||
Args:
|
||||
*commands (str): Multiple command strings to be executed.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of all commands executed.
|
||||
"""
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
tasks = [
|
||||
asyncio.create_task(self._handle_multicommand(client, command))
|
||||
@@ -83,6 +110,15 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
async def _handle_multicommand(
|
||||
self, client: httpx.AsyncClient, command: str
|
||||
) -> dict:
|
||||
"""Helper function for handling individual commands in a multicommand execution.
|
||||
|
||||
Args:
|
||||
client (httpx.AsyncClient): The HTTP client to use for the request.
|
||||
command (str): The command to be executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the response of the executed command.
|
||||
"""
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
|
||||
try:
|
||||
@@ -100,29 +136,75 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
return {command: {}}
|
||||
|
||||
async def get_miner_conf(self) -> dict:
|
||||
"""Retrieve the miner configuration from the Antminer device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_miner_conf")
|
||||
|
||||
async def set_miner_conf(self, conf: dict) -> dict:
|
||||
"""Set the configuration for the miner.
|
||||
|
||||
Args:
|
||||
conf (dict): A dictionary of configuration settings to apply to the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the configuration.
|
||||
"""
|
||||
return await self.send_command("set_miner_conf", **conf)
|
||||
|
||||
async def blink(self, blink: bool) -> dict:
|
||||
"""Control the blinking of the LED on the miner device.
|
||||
|
||||
Args:
|
||||
blink (bool): True to start blinking, False to stop.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after the command execution.
|
||||
"""
|
||||
if blink:
|
||||
return await self.send_command("blink", blink="true")
|
||||
return await self.send_command("blink", blink="false")
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device confirming the reboot command.
|
||||
"""
|
||||
return await self.send_command("reboot")
|
||||
|
||||
async def get_system_info(self) -> dict:
|
||||
"""Retrieve system information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing system information of the miner.
|
||||
"""
|
||||
return await self.send_command("get_system_info")
|
||||
|
||||
async def get_network_info(self) -> dict:
|
||||
"""Retrieve network configuration information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the network configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_network_info")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get a summary of the miner's status and performance.
|
||||
|
||||
Returns:
|
||||
dict: A summary of the miner's current operational status.
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def get_blink_status(self) -> dict:
|
||||
"""Check the status of the LED blinking on the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating whether the LED is currently blinking.
|
||||
"""
|
||||
return await self.send_command("get_blink_status")
|
||||
|
||||
async def set_network_conf(
|
||||
@@ -134,6 +216,19 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
hostname: str,
|
||||
protocol: int,
|
||||
) -> dict:
|
||||
"""Set the network configuration of the miner.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the device.
|
||||
dns (str): DNS server IP address.
|
||||
gateway (str): Gateway IP address.
|
||||
subnet_mask (str): Network subnet mask.
|
||||
hostname (str): Hostname of the device.
|
||||
protocol (int): Network protocol used.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the network configuration.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"set_network_conf",
|
||||
ipAddress=ip,
|
||||
@@ -147,6 +242,11 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
|
||||
class AntminerOldWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initialize the old Antminer API client with a specific IP address.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Antminer device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "root"
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
@@ -159,6 +259,18 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Antminer device using HTTP digest authentication.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The CGI command to send.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
privileged (bool): If set to True, requires elevated privileges.
|
||||
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device or an empty dictionary if an error occurs.
|
||||
"""
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
@@ -184,6 +296,16 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously.
|
||||
|
||||
Args:
|
||||
*commands (str): Multiple command strings to be executed.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of all commands executed.
|
||||
"""
|
||||
data = {k: None for k in commands}
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
@@ -203,30 +325,81 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
return data
|
||||
|
||||
async def get_system_info(self) -> dict:
|
||||
"""Retrieve system information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing system information of the miner.
|
||||
"""
|
||||
return await self.send_command("get_system_info")
|
||||
|
||||
async def blink(self, blink: bool) -> dict:
|
||||
"""Control the blinking of the LED on the miner device.
|
||||
|
||||
Args:
|
||||
blink (bool): True to start blinking, False to stop.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after the command execution.
|
||||
"""
|
||||
if blink:
|
||||
return await self.send_command("blink", action="startBlink")
|
||||
return await self.send_command("blink", action="stopBlink")
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device confirming the reboot command.
|
||||
"""
|
||||
return await self.send_command("reboot")
|
||||
|
||||
async def get_blink_status(self) -> dict:
|
||||
"""Check the status of the LED blinking on the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating whether the LED is currently blinking.
|
||||
"""
|
||||
return await self.send_command("blink", action="onPageLoaded")
|
||||
|
||||
async def get_miner_conf(self) -> dict:
|
||||
"""Retrieve the miner configuration from the Antminer device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_miner_conf")
|
||||
|
||||
async def set_miner_conf(self, conf: dict) -> dict:
|
||||
"""Set the configuration for the miner.
|
||||
|
||||
Args:
|
||||
conf (dict): A dictionary of configuration settings to apply to the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the configuration.
|
||||
"""
|
||||
return await self.send_command("set_miner_conf", **conf)
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Retrieve detailed statistical data of the mining operation.
|
||||
|
||||
Returns:
|
||||
dict: Detailed statistics of the miner's operation.
|
||||
"""
|
||||
return await self.send_command("miner_stats")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get a summary of the miner's status and performance.
|
||||
|
||||
Returns:
|
||||
dict: A summary of the miner's current operational status.
|
||||
"""
|
||||
return await self.send_command("miner_summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Retrieve current pool information associated with the miner.
|
||||
|
||||
Returns:
|
||||
dict: Information about the mining pools configured in the miner.
|
||||
"""
|
||||
return await self.send_command("miner_pools")
|
||||
|
||||
@@ -30,6 +30,11 @@ from pyasic.web.base import BaseWebAPI
|
||||
|
||||
class AuradineWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initializes the API client for interacting with Auradine mining devices.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Auradine miner.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_auradine_web_password", "admin")
|
||||
@@ -37,6 +42,11 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
self.token = None
|
||||
|
||||
async def auth(self) -> str | None:
|
||||
"""Authenticate and retrieve a web token from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
str | None: A token if authentication is successful, None otherwise.
|
||||
"""
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
try:
|
||||
auth = await client.post(
|
||||
@@ -65,6 +75,18 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Auradine miner, handling authentication and retries.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The specific command to execute.
|
||||
ignore_errors (bool): Whether to ignore HTTP errors.
|
||||
allow_warning (bool): Whether to proceed with warnings.
|
||||
privileged (bool): Whether the command requires privileged access.
|
||||
**parameters: Additional parameters for the command.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device.
|
||||
"""
|
||||
post = privileged or not parameters == {}
|
||||
if not parameters == {}:
|
||||
parameters["command"] = command
|
||||
@@ -102,6 +124,16 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously on the Auradine miner.
|
||||
|
||||
Args:
|
||||
*commands (str): Commands to execute.
|
||||
ignore_errors (bool): Whether to ignore errors during command execution.
|
||||
allow_warning (bool): Whether to proceed despite warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing responses for all commands executed.
|
||||
"""
|
||||
tasks = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
@@ -124,52 +156,157 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
return data
|
||||
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Perform a factory reset on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the reset operation.
|
||||
"""
|
||||
return await self.send_command("factory-reset", privileged=True)
|
||||
|
||||
async def get_fan(self) -> dict:
|
||||
"""Retrieve the current fan status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current fan status.
|
||||
"""
|
||||
return await self.send_command("fan")
|
||||
|
||||
async def set_fan(self, fan: int, speed_pct: int) -> dict:
|
||||
"""Set the speed of a specific fan on the Auradine miner.
|
||||
|
||||
Args:
|
||||
fan (int): The index of the fan to control.
|
||||
speed_pct (int): The speed percentage to set for the fan.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation.
|
||||
"""
|
||||
return await self.send_command("fan", index=fan, percentage=speed_pct)
|
||||
|
||||
async def firmware_upgrade(self, url: str = None, version: str = "latest") -> dict:
|
||||
"""Upgrade the firmware of the Auradine miner.
|
||||
|
||||
Args:
|
||||
url (str, optional): The URL to download the firmware from.
|
||||
version (str, optional): The version of the firmware to upgrade to, defaults to 'latest'.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the firmware upgrade.
|
||||
"""
|
||||
if url is not None:
|
||||
return await self.send_command("firmware-upgrade", url=url)
|
||||
return await self.send_command("firmware-upgrade", version=version)
|
||||
|
||||
async def get_frequency(self) -> dict:
|
||||
"""Retrieve the current frequency settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the frequency settings.
|
||||
"""
|
||||
return await self.send_command("frequency")
|
||||
|
||||
async def set_frequency(self, board: int, frequency: float) -> dict:
|
||||
"""Set the frequency for a specific board on the Auradine miner.
|
||||
|
||||
Args:
|
||||
board (int): The index of the board to configure.
|
||||
frequency (float): The frequency in MHz to set for the board.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the frequency.
|
||||
"""
|
||||
return await self.send_command("frequency", board=board, frequency=frequency)
|
||||
|
||||
async def ipreport(self) -> dict:
|
||||
"""Generate an IP report for the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the IP report details.
|
||||
"""
|
||||
return await self.send_command("ipreport")
|
||||
|
||||
async def get_led(self) -> dict:
|
||||
"""Retrieve the current LED status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current status of the LED settings.
|
||||
"""
|
||||
return await self.send_command("led")
|
||||
|
||||
async def set_led(self, code: int) -> dict:
|
||||
"""Set the LED code on the Auradine miner.
|
||||
|
||||
Args:
|
||||
code (int): The code that determines the LED behavior.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation.
|
||||
"""
|
||||
return await self.send_command("led", code=code)
|
||||
|
||||
async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str) -> dict:
|
||||
"""Set custom LED configurations including messages.
|
||||
|
||||
Args:
|
||||
code (int): The LED code to set.
|
||||
led_1 (int): The first LED indicator number.
|
||||
led_2 (int): The second LED indicator number.
|
||||
msg (str): The message to display or represent with LEDs.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the custom LED configuration.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"led", code=code, led1=led_1, led2=led_2, msg=msg
|
||||
)
|
||||
|
||||
async def get_mode(self) -> dict:
|
||||
"""Retrieve the current operational mode of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current mode settings.
|
||||
"""
|
||||
return await self.send_command("mode")
|
||||
|
||||
async def set_mode(self, **kwargs) -> dict:
|
||||
"""Set the operational mode of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Mode settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the mode setting operation.
|
||||
"""
|
||||
return await self.send_command("mode", **kwargs)
|
||||
|
||||
async def get_network(self) -> dict:
|
||||
"""Retrieve the network configuration settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the network configuration details.
|
||||
"""
|
||||
return await self.send_command("network")
|
||||
|
||||
async def set_network(self, **kwargs) -> dict:
|
||||
"""Set the network configuration of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Network settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the network configuration.
|
||||
"""
|
||||
return await self.send_command("network", **kwargs)
|
||||
|
||||
async def password(self, password: str) -> dict:
|
||||
"""Change the password used for accessing the Auradine miner.
|
||||
|
||||
Args:
|
||||
password (str): The new password to set.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the password change operation.
|
||||
"""
|
||||
res = await self.send_command(
|
||||
"password", user=self.username, old=self.pwd, new=password
|
||||
)
|
||||
@@ -177,43 +314,129 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
return res
|
||||
|
||||
async def get_psu(self) -> dict:
|
||||
"""Retrieve the status of the power supply unit (PSU) from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the PSU status.
|
||||
"""
|
||||
return await self.send_command("psu")
|
||||
|
||||
async def set_psu(self, voltage: float) -> dict:
|
||||
"""Set the voltage for the power supply unit of the Auradine miner.
|
||||
|
||||
Args:
|
||||
voltage (float): The voltage level to set for the PSU.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the PSU voltage.
|
||||
"""
|
||||
return await self.send_command("psu", voltage=voltage)
|
||||
|
||||
async def get_register(self) -> dict:
|
||||
"""Retrieve registration information from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the registration details.
|
||||
"""
|
||||
return await self.send_command("register")
|
||||
|
||||
async def set_register(self, company: str) -> dict:
|
||||
"""Set the registration information for the Auradine miner.
|
||||
|
||||
Args:
|
||||
company (str): The company name to register the miner under.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the registration operation.
|
||||
"""
|
||||
return await self.send_command("register", parameter=company)
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the reboot operation.
|
||||
"""
|
||||
return await self.send_command("restart", privileged=True)
|
||||
|
||||
async def restart_gcminer(self) -> dict:
|
||||
"""Restart the GCMiner application on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the GCMiner restart operation.
|
||||
"""
|
||||
return await self.send_command("restart", parameter="gcminer")
|
||||
|
||||
async def restart_api_server(self) -> dict:
|
||||
"""Restart the API server on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the API server restart operation.
|
||||
"""
|
||||
return await self.send_command("restart", parameter="api-server")
|
||||
|
||||
async def temperature(self) -> dict:
|
||||
"""Retrieve the current temperature readings from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing temperature data.
|
||||
"""
|
||||
return await self.send_command("temperature")
|
||||
|
||||
async def timedate(self, ntp: str, timezone: str) -> dict:
|
||||
"""Set the time and date settings for the Auradine miner.
|
||||
|
||||
Args:
|
||||
ntp (str): The NTP server to use for time synchronization.
|
||||
timezone (str): The timezone setting.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the time and date.
|
||||
"""
|
||||
return await self.send_command("timedate", ntp=ntp, timezone=timezone)
|
||||
|
||||
async def get_token(self) -> dict:
|
||||
"""Retrieve the current authentication token for the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the authentication token.
|
||||
"""
|
||||
return await self.send_command("token", user=self.username, password=self.pwd)
|
||||
|
||||
async def update_pools(self, pools: list[dict]) -> dict:
|
||||
"""Update the mining pools configuration on the Auradine miner.
|
||||
|
||||
Args:
|
||||
pools (list[dict]): A list of dictionaries, each representing a pool configuration.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the update operation.
|
||||
"""
|
||||
return await self.send_command("updatepools", pools=pools)
|
||||
|
||||
async def voltage(self) -> dict:
|
||||
"""Retrieve the voltage settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the voltage details.
|
||||
"""
|
||||
return await self.send_command("voltage")
|
||||
|
||||
async def get_ztp(self) -> dict:
|
||||
"""Retrieve the zero-touch provisioning status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the ZTP status.
|
||||
"""
|
||||
return await self.send_command("ztp")
|
||||
|
||||
async def set_ztp(self, enable: bool) -> dict:
|
||||
"""Enable or disable zero-touch provisioning (ZTP) on the Auradine miner.
|
||||
|
||||
Args:
|
||||
enable (bool): True to enable ZTP, False to disable.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the ZTP setting operation.
|
||||
"""
|
||||
return await self.send_command("ztp", parameter="on" if enable else "off")
|
||||
|
||||
131
pyasic/web/marathon.py
Normal file
131
pyasic/web/marathon.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.web.antminer import AntminerModernWebAPI
|
||||
|
||||
|
||||
class MaraWebAPI(AntminerModernWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
self.am_commands = [
|
||||
"get_miner_conf",
|
||||
"set_miner_conf",
|
||||
"blink",
|
||||
"reboot",
|
||||
"get_system_info",
|
||||
"get_network_info",
|
||||
"summary",
|
||||
"get_blink_status",
|
||||
"set_network_conf",
|
||||
]
|
||||
super().__init__(ip)
|
||||
|
||||
async def _send_mara_command(
|
||||
self,
|
||||
command: str | bytes,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
url = f"http://{self.ip}:{self.port}/kaonsu/v1/{command}"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(),
|
||||
) as client:
|
||||
if parameters:
|
||||
data = await client.post(
|
||||
url,
|
||||
auth=auth,
|
||||
timeout=settings.get("api_function_timeout", 3),
|
||||
json=parameters,
|
||||
)
|
||||
else:
|
||||
data = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def _send_am_command(
|
||||
self,
|
||||
command: str | bytes,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
):
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(),
|
||||
) as client:
|
||||
if parameters:
|
||||
data = await client.post(
|
||||
url,
|
||||
auth=auth,
|
||||
timeout=settings.get("api_function_timeout", 3),
|
||||
json=parameters,
|
||||
)
|
||||
else:
|
||||
data = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: str | bytes,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
if command in self.am_commands:
|
||||
return await self._send_am_command(
|
||||
command,
|
||||
ignore_errors=ignore_errors,
|
||||
allow_warning=allow_warning,
|
||||
privileged=privileged,
|
||||
**parameters,
|
||||
)
|
||||
return await self._send_mara_command(
|
||||
command,
|
||||
ignore_errors=ignore_errors,
|
||||
allow_warning=allow_warning,
|
||||
privileged=privileged,
|
||||
**parameters,
|
||||
)
|
||||
|
||||
async def brief(self):
|
||||
return await self.send_command("brief")
|
||||
|
||||
async def overview(self):
|
||||
return await self.send_command("overview")
|
||||
|
||||
async def connections(self):
|
||||
return await self.send_command("connections")
|
||||
|
||||
async def event_chart(self):
|
||||
return await self.send_command("event_chart")
|
||||
|
||||
async def hashboards(self):
|
||||
return await self.send_command("hashboards")
|
||||
|
||||
async def mara_pools(self):
|
||||
return await self._send_mara_command("pools")
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.54.16"
|
||||
version = "0.54.18"
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
|
||||
@@ -24,6 +24,7 @@ from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
from pyasic.rpc.gcminer import GCMinerRPCAPI
|
||||
from pyasic.rpc.luxminer import LUXMinerRPCAPI
|
||||
|
||||
|
||||
@@ -159,6 +160,12 @@ class TestCGMinerAPI(TestAPIBase):
|
||||
self.api_str = "CGMiner"
|
||||
|
||||
|
||||
class TestGCMinerRPCAPI(TestAPIBase):
|
||||
def setUpData(self):
|
||||
self.api = GCMinerRPCAPI(self.ip)
|
||||
self.api_str = "GCMiner"
|
||||
|
||||
|
||||
class TestLuxOSAPI(TestAPIBase):
|
||||
def setUpData(self):
|
||||
self.api = LUXMinerRPCAPI(self.ip)
|
||||
|
||||
Reference in New Issue
Block a user