Compare commits

..

3 Commits

Author SHA1 Message Date
Upstream Data
54f0292712 bug: try to fix scan timeout with asyncio.wait_for. 2024-05-13 08:28:12 -06:00
Upstream Data
46c56134f7 docs: update docs generation to fix marathon miners. 2024-04-29 09:14:08 -06:00
Upstream Data
1c1f7f1098 bug: move client into web ping to try to fix scanning failing. 2024-04-26 13:07:08 -06:00
665 changed files with 7545 additions and 31023 deletions

View File

@@ -13,10 +13,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- uses: actions/checkout@v2
- name: Publish GH release
uses: softprops/action-gh-release@v2.1.0
uses: softprops/action-gh-release@v0.1.14
- name: Build using poetry and publish to PyPi
uses: JRubics/poetry-publish@v2.0
uses: JRubics/poetry-publish@v1.11
with:
pypi_token: ${{ secrets.PYPI_API_KEY }}

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@ pyvenv.cfg
bin/
lib/
.idea/
.vs/

View File

@@ -1,33 +1,16 @@
ci:
skip:
- poetry-lock
- unittest
- generate-docs
repos:
- repo: https://github.com/python-poetry/poetry
rev: 2.1.2
hooks:
- id: poetry-check
- id: poetry-lock
- id: poetry-install
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: check-yaml
name: check-yaml for mkdocs.yml
files: ^mkdocs\.yml$
args: [--unsafe]
- id: check-yaml
name: check-yaml for other YAML files
exclude: ^mkdocs\.yml$
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 25.1.0
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 6.0.1
rev: 5.13.2
hooks:
- id: isort
name: isort (python)
@@ -38,12 +21,7 @@ repos:
name: unittest
entry: python -m unittest discover
language: system
types: [ python ]
'types': [python]
args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
pass_filenames: false
- id: generate-docs
name: generate-docs
entry: python docs/generate_miners.py
language: system
types: [ python ]
pass_filenames: false
stages: [commit]

View File

@@ -1,18 +1,20 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools: { python: "3.11" }
jobs:
pre_create_environment:
- asdf plugin add poetry
- asdf install poetry latest
- asdf global poetry latest
- poetry config virtualenvs.create false
post_install:
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --only docs
tools:
python: "3.9"
mkdocs:
configuration: mkdocs.yml
configuration: mkdocs.yml
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

View File

@@ -23,7 +23,7 @@ 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. Use version 2.0+
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
```
poetry install
@@ -42,7 +42,7 @@ It is recommended to install `pyasic` in a [virtual environment](https://realpyt
##### Installing `pyasic`
`python -m pip install pyasic` or `poetry install`
`python -m pip install .` or `poetry install`
##### Additional Developer Setup
```
@@ -50,13 +50,6 @@ poetry install --with dev
pre-commit install
```
##### Building Documentation Locally
```
poetry install --with docs
python docs/generate_miners.py
poetry run mkdocs serve
```
---
## Getting started
@@ -262,30 +255,24 @@ if __name__ == "__main__":
```python
from pyasic import settings
settings.update("default_antminer_web_password", "my_pwd")
settings.update("default_antminer_password", "my_pwd")
```
##### Default values:
```
"network_ping_retries": 1,
"network_ping_timeout": 3,
"network_scan_semaphore": None,
"network_scan_threads": 300,
"factory_get_retries": 1,
"factory_get_timeout": 3,
"get_data_retries": 1,
"api_function_timeout": 5,
"antminer_mining_mode_as_str": False,
"default_whatsminer_rpc_password": "admin",
"default_innosilicon_web_password": "admin",
"default_antminer_web_password": "root",
"default_bosminer_web_password": "root",
"default_vnish_web_password": "admin",
"default_goldshell_web_password": "123456789",
"default_auradine_web_password": "admin",
"default_epic_web_password": "letmein",
"default_hive_web_password": "admin",
"default_antminer_ssh_password": "miner",
"default_bosminer_ssh_password": "root",
"default_whatsminer_password": "admin",
"default_innosilicon_password": "admin",
"default_antminer_password": "root",
"default_bosminer_password": "root",
"default_vnish_password": "admin",
"default_goldshell_password": "123456789",
# ADVANCED
# Only use this if you know what you are doing

View File

@@ -2,7 +2,6 @@ import asyncio
import importlib
import os
import warnings
from pathlib import Path
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
@@ -50,21 +49,6 @@ def backend_str(backend: MinerTypes) -> str:
return "LuxOS Firmware Miners"
case MinerTypes.MARATHON:
return "Mara Firmware Miners"
case MinerTypes.BITAXE:
return "Stock Firmware BitAxe Miners"
case MinerTypes.LUCKYMINER:
return "Stock Firmware Lucky Miners"
case MinerTypes.ICERIVER:
return "Stock Firmware IceRiver Miners"
case MinerTypes.HAMMER:
return "Stock Firmware Hammer Miners"
case MinerTypes.VOLCMINER:
return "Stock Firmware Volcminers"
case MinerTypes.ELPHAPEX:
return "Stock Firmware Elphapex Miners"
case MinerTypes.MSKMINER:
return "MSKMiner Firmware Miners"
raise TypeError("Unknown miner backend, cannot generate docs")
def create_url_str(mtype: str):
@@ -79,17 +63,11 @@ def create_url_str(mtype: str):
HEADER_FORMAT = "# pyasic\n## {} Models\n\n"
MINER_HEADER_FORMAT = "## {}\n"
DATA_FORMAT = """
- [{}] Shutdowns
- [{}] Power Modes
- [{}] Setpoints
- [{}] Presets
::: {}
DATA_FORMAT = """::: {}
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
"""
SUPPORTED_TYPES_HEADER = """# pyasic
@@ -97,8 +75,6 @@ SUPPORTED_TYPES_HEADER = """# pyasic
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
Keep in mind that some functionality is only supported for specific miners or firmwares, please check the page for your miner to make sure the functionality you need is supported.
##### pyasic currently supports the following miners and subtypes:
<style>
details {
@@ -129,49 +105,39 @@ BACKEND_TYPE_CLOSER = """
</details>"""
m_data = {}
done = []
for m in MINER_CLASSES:
for t in sorted(MINER_CLASSES[m], key=lambda x: x or ""):
if t is not None and MINER_CLASSES[m][t] not in done:
for t in MINER_CLASSES[m]:
if t is not None:
miner = MINER_CLASSES[m][t]
if make(miner) not in m_data:
m_data[make(miner)] = {}
if model_type(miner) not in m_data[make(miner)]:
m_data[make(miner)][model_type(miner)] = []
m_data[make(miner)][model_type(miner)].append(miner)
done.append(miner)
def create_directory_structure(directory, data):
async def create_directory_structure(directory, data):
if not os.path.exists(directory):
os.makedirs(directory)
for key, value in data.items():
subdirectory = os.path.join(directory, key)
if isinstance(value, dict):
create_directory_structure(subdirectory, value)
await create_directory_structure(subdirectory, value)
elif isinstance(value, list):
file_path = os.path.join(subdirectory + ".md")
with open(file_path, "w") as file:
file.write(HEADER_FORMAT.format(key))
for item in value:
obj = item("1.1.1.1")
header = obj.model
header = await item("1.1.1.1").get_model()
file.write(MINER_HEADER_FORMAT.format(header))
file.write(
DATA_FORMAT.format(
"x" if obj.supports_shutdown else " ",
"x" if obj.supports_power_modes else " ",
"x" if obj.supports_autotuning else " ",
"x" if obj.supports_presets else " ",
path(item),
)
)
file.write(DATA_FORMAT.format(path(item)))
def create_supported_types(directory):
async def create_supported_types(directory):
with open(os.path.join(directory, "supported_types.md"), "w") as file:
file.write(SUPPORTED_TYPES_HEADER)
for mback in MINER_CLASSES:
@@ -188,7 +154,7 @@ def create_supported_types(directory):
for mtype in backend_types:
file.write(MINER_TYPE_HEADER.format(mtype))
for minstance in backend_types[mtype]:
model = minstance("1.1.1.1").model
model = await minstance("1.1.1.1").get_model()
file.write(
MINER_DETAILS.format(
make(minstance), mtype, create_url_str(model), model
@@ -198,7 +164,6 @@ def create_supported_types(directory):
file.write(BACKEND_TYPE_CLOSER)
if __name__ == "__main__":
root_directory = Path(__file__).parent.joinpath("miners")
create_directory_structure(root_directory, m_data)
create_supported_types(root_directory)
root_directory = os.path.join(os.getcwd(), "miners")
asyncio.run(create_directory_structure(root_directory, m_data))
asyncio.run(create_supported_types(root_directory))

View File

@@ -11,149 +11,145 @@
[![Read The Docs - Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/)
[![License - Apache 2.0](https://img.shields.io/github/license/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
---
## Intro
---
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
[Click here to view supported miner types](miners/supported_types.md)
## 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.
`pyasic` can be installed directly from pip, either with `pip install pyasic`, or a different command if using a tool like `pypoetry`.
## 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`
`python -m pip install .` or `poetry install`
---
## Getting started
---
Getting started with `pyasic` is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
### Scanning for miners
##### Scanning for miners
To scan for miners in `pyasic`, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
```python3
import asyncio# (1)!
from pyasic.network import MinerNetwork# (2)!
```python
import asyncio # asyncio for handling the async part
from pyasic.network import MinerNetwork # miner network handles the scanning
async def scan_miners():# (3)!
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
async def scan_miners(): # define async scan function to allow awaiting
# create a miner network
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
miners = await network.scan()# (5)!
# scan for miners asynchronously
# this will return the correct type of miners if they are supported with all functionality.
miners = await network.scan()
print(miners)
if __name__ == "__main__":
asyncio.run(scan_miners())# (6)!
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
```
1. `asyncio` for handling the async part.
2. `MinerNetwork` handles the scanning.
3. Define an async function to allow awaiting.
4. Create a miner network.
You can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
This uses the 192.168.1.0-255 network.
5. Scan for miners asynchronously.
This will return the correct type of miners (if they are supported) with all functionality.
6. Run the scan asynchronously with asyncio.run().
---
### 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.factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
```python
import asyncio# (1)!
from pyasic import get_miner# (2)!
import asyncio # asyncio for handling the async part
from pyasic import get_miner # handles miner creation
async def get_miners():# (3)!
miner_1 = await get_miner("192.168.1.75")# (4)!
async def get_miners(): # define async scan function to allow awaiting
# get the miner with the miner factory
# the miner factory is a singleton, and will always use the same object and cache
# this means you can always call it as MinerFactory().get_miner(), or just get_miner()
miner_1 = await get_miner("192.168.1.75")
miner_2 = await get_miner("192.168.1.76")
print(miner_1, miner_2)
# can also gather these, since they are async
# gathering them will get them both at the same time
# this makes it much faster to get a lot of miners at a time
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
miners = await asyncio.gather(*tasks)# (5)!
miners = await asyncio.gather(*tasks)
print(miners)
if __name__ == "__main__":
asyncio.run(get_miners())# (6)!
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
```
1. `asyncio` for handling the async part.
2. `get_miner` handles the miner type selection.
3. Define an async function to allow awaiting.
4. Get the miner.
5. Can also gather these, since they are async.
Gathering them will get them both at the same time.
This makes it much faster to get a lot of miners at a time.
6. Get the miners asynchronously with asyncio.run().
---
## Data gathering
---
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built-in function in each miner called `get_data()`.
This function will return an instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
### One miner
##### One miner
```python
import asyncio# (1)!
from pyasic import get_miner# (2)!
import asyncio
from pyasic import get_miner
async def gather_miner_data():# (3)!
miner = await get_miner("192.168.1.75")# (4)!
if miner is not None:# (5)!
miner_data = await miner.get_data()# (6)!
print(miner_data)# (7)!
async def gather_miner_data():
miner = await get_miner("192.168.1.75")
if miner is not None:
miner_data = await miner.get_data()
print(miner_data) # all data from the dataclass
print(miner_data.hashrate) # hashrate of the miner in TH/s
if __name__ == "__main__":
asyncio.run(gather_miner_data())# (9)!
asyncio.run(gather_miner_data())
```
1. `asyncio` for handling the async part.
2. `get_miner` handles the miner type selection.
3. Define an async function to allow awaiting.
4. Get the miner.
5. Make sure the miner exists.
If this result is `None`, the miner may be offline.
6. Get data from the miner.
7. All the data from the dataclass.
8. Hashrate of the miner, with unit information.
9. Get the miner data asynchronously with asyncio.run().
### Multiple miners
---
##### Multiple miners
You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.
```python
import asyncio# (1)!
from pyasic.network import MinerNetwork# (2)!
import asyncio # asyncio for handling the async part
from pyasic.network import MinerNetwork # miner network handles the scanning
async def gather_miner_data():# (3)!
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
miners = await network.scan()# (5)!
async def gather_miner_data(): # define async scan function to allow awaiting
network = MinerNetwork.from_subnet("192.168.1.50/24")
miners = await network.scan()
# we need to asyncio.gather() all the miners get_data() functions to make them run together
all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])
for miner_data in all_miner_data:
print(miner_data)# (7)!
print(miner_data) # print out all the data one by one
if __name__ == "__main__":
asyncio.run(gather_miner_data())# (8)!
asyncio.run(gather_miner_data())
```
1. `asyncio` for handling the async part.
2. `MinerNetwork` handles the scanning.
3. Define an async function to allow awaiting.
4. Create a miner network.
5. Scan for miners asynchronously.
6. Use `asyncio.gather()` with all the miners' `get_data()` functions to make them run together.
7. Print out the data one at a time.
8. Get the miner data asynchronously with asyncio.run().
---
## Miner control
---
`pyasic` exposes a standard interface for each miner using control functions.
Every miner class in `pyasic` must implement all the following control functions.
Every miner class in `pyasic` must implement all the control functions defined in [`MinerProtocol`][pyasic.miners.base.MinerProtocol].
These functions are
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
@@ -170,41 +166,35 @@ Every miner class in `pyasic` must implement all the following control functions
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
### Usage
##### Usage
```python
import asyncio# (1)!
from pyasic import get_miner# (2)!
import asyncio
from pyasic import get_miner
async def set_fault_light():# (3)!
miner = await get_miner("192.168.1.20")# (4)!
async def set_fault_light():
miner = await get_miner("192.168.1.20")
await miner.fault_light_on()# (5)!
# call control function
await miner.fault_light_on()
if __name__ == "__main__":
asyncio.run(set_fault_light())# (6)!
asyncio.run(set_fault_light())
```
1. `asyncio` for handling the async part.
2. `get_miner` handles the miner type selection.
3. Define an async function to allow awaiting.
4. Get the miner.
5. Call the miner control function.
6. Call the control function asynchronously with asyncio.run().
---
## Helper dataclasses
---
### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
##### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
`pyasic` implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
---
### [`MinerData`][pyasic.data.MinerData]
##### [`MinerData`][pyasic.data.MinerData]
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`][pyasic.miners.base.MinerProtocol.get_data] function, and is used to have a consistent dataset across all returns.
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns.
You can call [`MinerData.as_dict()`][pyasic.data.MinerData.as_dict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
@@ -223,13 +213,13 @@ average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_m
---
### [`MinerConfig`][pyasic.config.MinerConfig]
##### [`MinerConfig`][pyasic.config.MinerConfig]
[`MinerConfig`][pyasic.config.MinerConfig] is `pyasic`'s way to represent a configuration file from a miner.
It is designed to unionize the configuration of all supported miner types, and is the return from [`get_config()`][pyasic.miners.base.MinerProtocol.get_config].
It is designed to unionize the configuration of all supported miner types, and is the return from [`get_config()`](#get-config).
Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class.
In most cases these helper functions should not be used, as [`send_config()`][pyasic.miners.base.MinerProtocol.send_config] takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.
In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.
You can use the [`MinerConfig`][pyasic.config.MinerConfig] as follows:
```python
@@ -251,6 +241,7 @@ if __name__ == "__main__":
```
---
## Settings
---
`pyasic` has settings designed to make using large groups of miners easier. You can set the default password for all types of miners using the `pyasic.settings` module, used as follows:
@@ -258,30 +249,25 @@ if __name__ == "__main__":
```python
from pyasic import settings
settings.update("default_antminer_web_password", "my_pwd")
settings.update("default_antminer_password", "my_pwd")
```
### Default values:
##### Default values:
```
"network_ping_retries": 1,
"network_ping_timeout": 3,
"network_scan_semaphore": None,
"network_scan_threads": 300,
"factory_get_retries": 1,
"factory_get_timeout": 3,
"get_data_retries": 1,
"api_function_timeout": 5,
"antminer_mining_mode_as_str": False,
"default_whatsminer_rpc_password": "admin",
"default_innosilicon_web_password": "admin",
"default_antminer_web_password": "root",
"default_bosminer_web_password": "root",
"default_vnish_web_password": "admin",
"default_goldshell_web_password": "123456789",
"default_auradine_web_password": "admin",
"default_epic_web_password": "letmein",
"default_hive_web_password": "admin",
"default_antminer_ssh_password": "miner",
"default_bosminer_ssh_password": "root",
"default_whatsminer_password": "admin",
"default_innosilicon_password": "admin",
"default_antminer_password": "root",
"default_bosminer_password": "root",
"default_vnish_password": "admin",
"default_goldshell_password": "123456789",
# ADVANCED
# Only use this if you know what you are doing

View File

@@ -1,29 +1,10 @@
# pyasic
## X15 Models
## Z15 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Z15
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
handler: python
options:
show_root_heading: false
heading_level: 0
## Z15 Pro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X15.Z15.BMMinerZ15Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,211 +1,115 @@
# pyasic
## X17 Models
## S17 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## S17
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
handler: python
options:
show_root_heading: false
heading_level: 0
## S17 Pro (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## S17+ (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## S17+
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17e (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## S17 Pro
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## S17e
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
handler: python
options:
show_root_heading: false
heading_level: 0
## T17 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## T17
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
handler: python
options:
show_root_heading: false
heading_level: 0
## T17+ (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## T17+
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## T17e (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## T17e
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
handler: python
options:
show_root_heading: false
heading_level: 0
## S17 Pro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17+ (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17 Pro (BOS+)
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## S17e (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T17 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T17+ (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T17e (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17e
handler: python
options:
show_root_heading: false
heading_level: 0
## S17 Pro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17+ (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S17 Pro (VNish)
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4

File diff suppressed because it is too large Load Diff

View File

@@ -1,328 +1,59 @@
# pyasic
## X21 Models
## S21 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## S21
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Hydro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ Hydro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## T21
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
heading_level: 4
## S21
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Hydro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ Hydro (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X21.T21.BOSMinerT21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Hydro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## S21+ Hydro (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21PlusHydro
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X21.T21.VNishT21
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S21 (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
handler: python
options:
show_root_heading: false
heading_level: 0
## S21 Pro (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T21 (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S21 (LuxOS)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
handler: python
options:
show_root_heading: false
heading_level: 0
## T21 (LuxOS)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X21.T21.LUXMinerT21
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S21 (MaraFW)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.marathon.X21.S21.MaraS21
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T21 (MaraFW)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.marathon.X21.T21.MaraT21
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,81 +1,38 @@
# pyasic
## X3 Models
## D3 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## D3
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
handler: python
options:
show_root_heading: false
heading_level: 0
## HS3 (Stock)
- [ ] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## HS3
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
handler: python
options:
show_root_heading: false
heading_level: 0
## KA3 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X3.KA3.BMMinerKA3
handler: python
options:
show_root_heading: false
heading_level: 0
## KS3 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X3.KS3.BMMinerKS3
handler: python
options:
show_root_heading: false
heading_level: 0
## L3+ (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## L3+
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## L3+ (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X3.L3.VNishL3Plus
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## L3+ (VNish)
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,42 +1,10 @@
# pyasic
## X5 Models
## DR5 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## DR5
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
handler: python
options:
show_root_heading: false
heading_level: 0
## KS5 (Stock)
- [ ] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5
handler: python
options:
show_root_heading: false
heading_level: 0
## KS5 (Stock)
- [ ] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,55 +1,17 @@
# pyasic
## X7 Models
## D7 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X7.D7.BMMinerD7
handler: python
options:
show_root_heading: false
heading_level: 0
## K7 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X7.K7.BMMinerK7
handler: python
options:
show_root_heading: false
heading_level: 0
## L7 (Stock)
- [ ] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## L7
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## L7 (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X7.L7.VNishL7
::: pyasic.miners.antminer.vnish.X7.L7.VnishL7
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,146 +1,59 @@
# pyasic
## X9 Models
## D9 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X9.D9.BMMinerD9
handler: python
options:
show_root_heading: false
heading_level: 0
## E9Pro (Stock)
- [ ] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## E9Pro
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## L9 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bmminer.X9.L9.BMMinerL9
handler: python
options:
show_root_heading: false
heading_level: 0
## S9 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## S9
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
handler: python
options:
show_root_heading: false
heading_level: 0
## S9i (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## S9i
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
handler: python
options:
show_root_heading: false
heading_level: 0
## S9j (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## S9j
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
handler: python
options:
show_root_heading: false
heading_level: 0
## T9 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## T9
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S9 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
handler: python
options:
show_root_heading: false
heading_level: 0
## L9 (VNish)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.vnish.X9.L9.VNishL9
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## T9 (Hive)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## S9 (LuxOS)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [x] Presets
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,42 +1,24 @@
# pyasic
## AD Models
## AT1500 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
## AT1500
::: pyasic.miners.auradine.flux.AD.AT1.AuradineFluxAT1500
handler: python
options:
show_root_heading: false
heading_level: 0
## AT2860 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
heading_level: 4
## AT2860
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2860
handler: python
options:
show_root_heading: false
heading_level: 0
## AT2880 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
heading_level: 4
## AT2880
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2880
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +1,17 @@
# pyasic
## AI Models
## AI2500 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
## AI2500
::: pyasic.miners.auradine.flux.AI.AI2.AuradineFluxAI2500
handler: python
options:
show_root_heading: false
heading_level: 0
## AI3680 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
heading_level: 4
## AI3680
::: pyasic.miners.auradine.flux.AI.AI3.AuradineFluxAI3680
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +1,17 @@
# pyasic
## AT Models
## AD2500 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
## AD2500
::: pyasic.miners.auradine.flux.AT.AD2.AuradineFluxAD2500
handler: python
options:
show_root_heading: false
heading_level: 0
## AD3500 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Presets
heading_level: 4
## AD3500
::: pyasic.miners.auradine.flux.AT.AD3.AuradineFluxAD3500
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,42 +1,24 @@
# pyasic
## A10X Models
## Avalon 1026 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 1026
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 1047 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 1047
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 1066 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 1066
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +1,10 @@
# pyasic
## A11X Models
## Avalon 1126 Pro (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.A11X.A1126.CGMinerAvalon1126Pro
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 1166 Pro (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 1166 Pro
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,16 +1,10 @@
# pyasic
## A12X Models
## Avalon 1246 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 1246
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,16 +0,0 @@
# pyasic
## A15X Models
## Avalon 1566 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.A15X.A1566.CGMinerAvalon1566
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,42 +1,24 @@
# pyasic
## A7X Models
## Avalon 721 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 721
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 741 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 741
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 761 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 761
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,42 +1,24 @@
# pyasic
## A8X Models
## Avalon 821 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 821
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 841 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 841
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon 851 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## Avalon 851
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,16 +1,10 @@
# pyasic
## A9X Models
## Avalon 921 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## Avalon 921
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +0,0 @@
# pyasic
## nano Models
## Avalon Nano 3s (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3s
handler: python
options:
show_root_heading: false
heading_level: 0
## Avalon Nano 3 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,14 +1,7 @@
# pyasic
## Modern Hiveon Backend
## Hiveon Backend
::: pyasic.miners.backends.hiveon.HiveonModern
handler: python
options:
show_root_heading: false
heading_level: 4
## Old Hiveon Backend
::: pyasic.miners.backends.hiveon.HiveonOld
::: pyasic.miners.backends.hiveon.Hiveon
handler: python
options:
show_root_heading: false

View File

@@ -10,3 +10,8 @@ You may not instantiate this class on its own, only subclass from it.
handler: python
options:
heading_level: 4
::: pyasic.miners.base.MinerProtocol
handler: python
options:
heading_level: 4

View File

@@ -1,55 +0,0 @@
# pyasic
## BM Models
## Ultra (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.bitaxe.espminer.BM.BM1366.BitAxeUltra
handler: python
options:
show_root_heading: false
heading_level: 0
## Supra (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.bitaxe.espminer.BM.BM1368.BitAxeSupra
handler: python
options:
show_root_heading: false
heading_level: 0
## Gamma (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.bitaxe.espminer.BM.BM1370.BitAxeGamma
handler: python
options:
show_root_heading: false
heading_level: 0
## Max (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.bitaxe.espminer.BM.BM1397.BitAxeMax
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,42 +0,0 @@
# pyasic
## blockminer Models
## BlockMiner 520i (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner520i
handler: python
options:
show_root_heading: false
heading_level: 0
## BlockMiner 720i (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner720i
handler: python
options:
show_root_heading: false
heading_level: 0
## BlockMiner eLITE 1.0 (ePIC)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMinerELITE1
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,29 +0,0 @@
# pyasic
## BMM Models
## BMM100 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM100
handler: python
options:
show_root_heading: false
heading_level: 0
## BMM101 (BOS+)
- [x] Shutdowns
- [ ] Power Modes
- [x] Setpoints
- [ ] Presets
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM101
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,42 +0,0 @@
# pyasic
## DGX Models
## DG1 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1
handler: python
options:
show_root_heading: false
heading_level: 0
## DG1+ (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Plus
handler: python
options:
show_root_heading: false
heading_level: 0
## DG1Home (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Home
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,10 +1,91 @@
## Control functionality
All control functionality is outlined by the [`MinerProtocol`][pyasic.miners.base.MinerProtocol] class.
## Miner Protocol
::: pyasic.miners.base.MinerProtocol
### Check Light
::: pyasic.miners.base.MinerProtocol.check_light
handler: python
options:
heading_level: 4
### Fault Light Off
::: pyasic.miners.base.MinerProtocol.fault_light_off
handler: python
options:
heading_level: 4
### Fault Light On
::: pyasic.miners.base.MinerProtocol.fault_light_on
handler: python
options:
heading_level: 4
### Get Config
::: pyasic.miners.base.MinerProtocol.get_config
handler: python
options:
heading_level: 4
### Get Data
::: pyasic.miners.base.MinerProtocol.get_data
handler: python
options:
heading_level: 4
### Get Errors
::: pyasic.miners.base.MinerProtocol.get_errors
handler: python
options:
heading_level: 4
### Get Hostname
::: pyasic.miners.base.MinerProtocol.get_hostname
handler: python
options:
heading_level: 4
### Get Model
::: pyasic.miners.base.MinerProtocol.get_model
handler: python
options:
heading_level: 4
### Reboot
::: pyasic.miners.base.MinerProtocol.reboot
handler: python
options:
heading_level: 4
### Restart Backend
::: pyasic.miners.base.MinerProtocol.restart_backend
handler: python
options:
heading_level: 4
### Stop Mining
::: pyasic.miners.base.MinerProtocol.stop_mining
handler: python
options:
heading_level: 4
### Resume Mining
::: pyasic.miners.base.MinerProtocol.resume_mining
handler: python
options:
heading_level: 4
### Is Mining
::: pyasic.miners.base.MinerProtocol.is_mining
handler: python
options:
heading_level: 4
### Send Config
::: pyasic.miners.base.MinerProtocol.send_config
handler: python
options:
heading_level: 4
### Set Power Limit
::: pyasic.miners.base.MinerProtocol.set_power_limit
handler: python
options:
heading_level: 4

View File

@@ -1,16 +0,0 @@
# pyasic
## Byte Models
## Byte (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.goldshell.bfgminer.Byte.Byte.GoldshellByte
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,42 +1,24 @@
# pyasic
## X5 Models
## CK5 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## CK5
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
handler: python
options:
show_root_heading: false
heading_level: 0
## HS5 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## HS5
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
handler: python
options:
show_root_heading: false
heading_level: 0
## KD5 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## KD5
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +1,17 @@
# pyasic
## XBox Models
## KD Box II (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## KD Box II
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxII
handler: python
options:
show_root_heading: false
heading_level: 0
## KD Box Pro (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
heading_level: 4
## KD Box Pro
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxPro
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,16 +1,10 @@
# pyasic
## XMax Models
## KD Max (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## KD Max
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.GoldshellKDMax
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,16 +0,0 @@
# pyasic
## DX Models
## D10 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.hammer.blackminer.DX.D10.HammerD10
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,120 +0,0 @@
# pyasic
## KSX Models
## KS0 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS0.IceRiverKS0
handler: python
options:
show_root_heading: false
heading_level: 0
## KS1 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS1.IceRiverKS1
handler: python
options:
show_root_heading: false
heading_level: 0
## KS2 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
handler: python
options:
show_root_heading: false
heading_level: 0
## KS3 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3
handler: python
options:
show_root_heading: false
heading_level: 0
## KS3L (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3L
handler: python
options:
show_root_heading: false
heading_level: 0
## KS3M (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3M
handler: python
options:
show_root_heading: false
heading_level: 0
## KS5 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5
handler: python
options:
show_root_heading: false
heading_level: 0
## KS5L (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5L
handler: python
options:
show_root_heading: false
heading_level: 0
## KS5M (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5M
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,16 +1,10 @@
# pyasic
## A10X Models
## A10X (Stock)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## A10X
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +0,0 @@
# pyasic
## A11X Models
## A11 (Stock)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.innosilicon.cgminer.A11X.A11.InnosiliconA11
handler: python
options:
show_root_heading: false
heading_level: 0
## A11MX (Stock)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.innosilicon.cgminer.A11X.A11M.InnosiliconA11MX
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,16 +1,10 @@
# pyasic
## T3X Models
## T3H+ (Stock)
- [x] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
## T3H+
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

View File

@@ -1,29 +0,0 @@
# pyasic
## LV Models
## LV07 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.luckyminer.espminer.LV.LV07.LuckyMinerLV07
handler: python
options:
show_root_heading: false
heading_level: 0
## LV08 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.luckyminer.espminer.LV.LV08.LuckyMinerLV08
handler: python
options:
show_root_heading: false
heading_level: 0

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
# pyasic
## DX Models
## D1 (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.volcminer.blackminer.DX.D1.VolcMinerD1
handler: python
options:
show_root_heading: false
heading_level: 0

View File

@@ -1,172 +1,94 @@
# pyasic
## M2X Models
## M20P V10 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
handler: python
options:
show_root_heading: false
heading_level: 0
## M20P V30 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
handler: python
options:
show_root_heading: false
heading_level: 0
## M20S+ V30 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 0
## M20S V10 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
handler: python
options:
show_root_heading: false
heading_level: 0
## M20S V20 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
handler: python
options:
show_root_heading: false
heading_level: 0
## M20S V30 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
handler: python
options:
show_root_heading: false
heading_level: 0
## M20 V10 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## M20 V10
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M21S+ V20 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
## M20S V10
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M21S V20 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
## M20S V20
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M21S V60 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
## M20S V30
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M21S V70 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
## M20P V10
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M21 V10 (Stock)
## M20P V30
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
handler: python
options:
show_root_heading: false
heading_level: 4
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## M20S+ V30
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
## M21 V10
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4
## M29 V10 (Stock)
## M21S V20
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
handler: python
options:
show_root_heading: false
heading_level: 4
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
## M21S V60
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
handler: python
options:
show_root_heading: false
heading_level: 4
## M21S V70
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
handler: python
options:
show_root_heading: false
heading_level: 4
## M21S+ V20
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
handler: python
options:
show_root_heading: false
heading_level: 4
## M29 V10
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
handler: python
options:
show_root_heading: false
heading_level: 0
heading_level: 4

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
# pyasic
## M7X Models
## M70 VM30 (Stock)
- [x] Shutdowns
- [x] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M7X.M70.BTMinerM70VM30
handler: python
options:
show_root_heading: false
heading_level: 0

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
jinja2<3.1.3
mkdocs
mkdocstrings[python]

View File

@@ -13,17 +13,13 @@ Settings options:
- `get_data_retries`
- `api_function_timeout`
- `antminer_mining_mode_as_str`
- `default_whatsminer_rpc_password`
- `default_innosilicon_web_password`
- `default_antminer_web_password`
- `default_bosminer_web_password`
- `default_vnish_web_password`
- `default_goldshell_web_password`
- `default_auradine_web_password`
- `default_epic_web_password`
- `default_hive_web_password`
- `default_antminer_ssh_password`
- `default_bosminer_ssh_password`
- `default_whatsminer_password`
- `default_innosilicon_password`
- `default_antminer_password`
- `default_bosminer_password`
- `default_vnish_password`
- `default_goldshell_password`
- `socket_linger_time`
### get

View File

@@ -1,36 +1,5 @@
site_name: pyasic
repo_url: https://github.com/UpstreamData/pyasic
site_url: !ENV SITE_URL
theme:
name: material
features:
- content.code.copy
- content.code.annotate
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/weather-night
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/weather-sunny
name: Switch to auto mode
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
nav:
- Introduction: "index.md"
- Miners:
@@ -81,8 +50,6 @@ nav:
- Antminer X17: "miners/antminer/X17.md"
- Antminer X19: "miners/antminer/X19.md"
- Antminer X21: "miners/antminer/X21.md"
- Braiins Mini Miners: "miners/braiins/BMM.md"
- Avalon Nano: "miners/avalonminer/nano.md"
- Avalon 7X: "miners/avalonminer/A7X.md"
- Avalon 8X: "miners/avalonminer/A8X.md"
- Avalon 9X: "miners/avalonminer/A9X.md"
@@ -93,22 +60,14 @@ nav:
- Whatsminer M3X: "miners/whatsminer/M3X.md"
- Whatsminer M5X: "miners/whatsminer/M5X.md"
- Whatsminer M6X: "miners/whatsminer/M6X.md"
- Whatsminer M7X: "miners/whatsminer/M7X.md"
- Innosilicon T3X: "miners/innosilicon/T3X.md"
- Innosilicon A10X: "miners/innosilicon/A10X.md"
- Innosilicon A11X: "miners/innosilicon/A11X.md"
- Goldshell Byte: "miners/goldshell/Byte.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"
- Blockminer: "miners/blockminer/blockminer.md"
- BitAxe BM: "miners/bitaxe/BM.md"
- Hammer DX: "miners/hammer/DX.md"
- Iceriver KSX: "miners/iceriver/KSX.md"
- Volcminer DX: "miners/volcminer/DX.md"
- Base Miner: "miners/base_miner.md"
- Settings:
- Settings: "settings/settings.md"

1717
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,6 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import importlib.metadata
from pyasic import settings
from pyasic.config import MinerConfig
from pyasic.data import MinerData
@@ -24,5 +22,3 @@ from pyasic.network import MinerNetwork
from pyasic.rpc import *
from pyasic.ssh import *
from pyasic.web import *
__version__ = importlib.metadata.version("pyasic")

View File

@@ -13,40 +13,33 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from dataclasses import asdict, dataclass, field
from pydantic import BaseModel, Field
from pyasic.config.fans import FanMode, FanModeConfig, FanModeNormal
from pyasic.config.mining import MiningMode, MiningModeConfig
from pyasic.config.mining.scaling import ScalingConfig
from pyasic.config.fans import FanModeConfig
from pyasic.config.mining import MiningModeConfig
from pyasic.config.pools import PoolConfig
from pyasic.config.power_scaling import PowerScalingConfig
from pyasic.config.temperature import TemperatureConfig
from pyasic.misc import merge_dicts
class MinerConfig(BaseModel):
"""Represents the configuration for a miner including pool configuration,
@dataclass
class MinerConfig:
"""Represents the configuration for a miner including pool configuration,
fan mode, temperature settings, mining mode, and power scaling."""
class Config:
arbitrary_types_allowed = True
pools: PoolConfig = Field(default_factory=PoolConfig.default)
fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
def __getitem__(self, item):
try:
return getattr(self, item)
except AttributeError:
raise KeyError
pools: PoolConfig = field(default_factory=PoolConfig.default)
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default)
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
mining_mode: MiningModeConfig = field(default_factory=MiningModeConfig.default)
power_scaling: PowerScalingConfig = field(
default_factory=PowerScalingConfig.default
)
def as_dict(self) -> dict:
"""Converts the MinerConfig object to a dictionary."""
return self.model_dump()
return asdict(self)
def as_am_modern(self, user_suffix: str | None = None) -> dict:
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(),
@@ -54,154 +47,97 @@ class MinerConfig(BaseModel):
**self.mining_mode.as_am_modern(),
**self.pools.as_am_modern(user_suffix=user_suffix),
**self.temperature.as_am_modern(),
**self.power_scaling.as_am_modern(),
}
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
"""Generates the configuration in the format suitable for modern Hiveon."""
return {
**self.fan_mode.as_hiveon_modern(),
"freq-level": "100",
**self.mining_mode.as_hiveon_modern(),
**self.pools.as_hiveon_modern(user_suffix=user_suffix),
**self.temperature.as_hiveon_modern(),
}
def as_elphapex(self, user_suffix: str | None = None) -> dict:
"""Generates the configuration in the format suitable for modern Elphapex."""
return {
**self.fan_mode.as_elphapex(),
"fc-freq-level": "100",
**self.mining_mode.as_elphapex(),
**self.pools.as_elphapex(user_suffix=user_suffix),
**self.temperature.as_elphapex(),
}
def as_wm(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_wm(user_suffix=user_suffix),
**self.temperature.as_wm(),
**self.power_scaling.as_wm(),
}
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
"""Generates the configuration in the format suitable for Whatsminers running BTMiner V3."""
return {
"set.miner.pools": self.pools.as_btminer_v3()
** self.mining_mode.as_btminer_v3()
}
def as_am_old(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_am_old(user_suffix=user_suffix),
**self.temperature.as_am_old(),
**self.power_scaling.as_am_old(),
}
def as_goldshell(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_goldshell(user_suffix=user_suffix),
**self.temperature.as_goldshell(),
**self.power_scaling.as_goldshell(),
}
def as_avalon(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_avalon(user_suffix=user_suffix),
**self.temperature.as_avalon(),
**self.power_scaling.as_avalon(),
}
def as_inno(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_inno(user_suffix=user_suffix),
**self.temperature.as_inno(),
**self.power_scaling.as_inno(),
}
def as_bosminer(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_bosminer(user_suffix=user_suffix),
**self.power_scaling.as_bosminer(),
}
def as_boser(self, user_suffix: str | None = None) -> dict:
"""Generates the configuration in the format suitable for BOSer."""
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(),
**self.mining_mode.as_boser(),
**self.pools.as_boser(user_suffix=user_suffix),
**self.power_scaling.as_boser(),
}
def as_epic(self, user_suffix: str | None = None) -> dict:
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(),
**self.pools.as_epic(user_suffix=user_suffix),
**self.power_scaling.as_epic(),
}
def as_auradine(self, user_suffix: str | None = None) -> dict:
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(),
**self.mining_mode.as_auradine(),
**self.pools.as_auradine(user_suffix=user_suffix),
**self.power_scaling.as_auradine(),
}
def as_mara(self, user_suffix: str | None = None) -> dict:
return {
**self.fan_mode.as_mara(),
**self.temperature.as_mara(),
**self.mining_mode.as_mara(),
**self.pools.as_mara(user_suffix=user_suffix),
}
def as_espminer(self, user_suffix: str | None = None) -> dict:
return {
**self.fan_mode.as_espminer(),
**self.temperature.as_espminer(),
**self.mining_mode.as_espminer(),
**self.pools.as_espminer(user_suffix=user_suffix),
}
def as_luxos(self, user_suffix: str | None = None) -> dict:
return {
**self.fan_mode.as_luxos(),
**self.temperature.as_luxos(),
**self.mining_mode.as_luxos(),
**self.pools.as_luxos(user_suffix=user_suffix),
}
def as_vnish(self, user_suffix: str | None = None) -> dict:
main_cfg = {
"miner": {
**self.fan_mode.as_vnish(),
**self.temperature.as_vnish(),
**self.mining_mode.as_vnish(),
**self.pools.as_vnish(user_suffix=user_suffix),
}
}
if isinstance(self.fan_mode, FanModeNormal):
main_cfg["miner"]["cooling"]["mode"]["param"] = self.temperature.target
return main_cfg
def as_hammer(self, *args, **kwargs) -> dict:
return self.as_am_modern(*args, **kwargs)
@classmethod
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from a dictionary."""
@@ -210,6 +146,7 @@ class MinerConfig(BaseModel):
mining_mode=MiningModeConfig.from_dict(dict_conf.get("mining_mode")),
fan_mode=FanModeConfig.from_dict(dict_conf.get("fan_mode")),
temperature=TemperatureConfig.from_dict(dict_conf.get("temperature")),
power_scaling=PowerScalingConfig.from_dict(dict_conf.get("power_scaling")),
)
@classmethod
@@ -226,24 +163,6 @@ class MinerConfig(BaseModel):
fan_mode=FanModeConfig.from_am_modern(web_conf),
)
@classmethod
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Hiveon."""
return cls(
pools=PoolConfig.from_hiveon_modern(web_conf),
mining_mode=MiningModeConfig.from_hiveon_modern(web_conf),
fan_mode=FanModeConfig.from_hiveon_modern(web_conf),
)
@classmethod
def from_elphapex(cls, web_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
return cls(
pools=PoolConfig.from_elphapex(web_conf),
mining_mode=MiningModeConfig.from_elphapex(web_conf),
fan_mode=FanModeConfig.from_elphapex(web_conf),
)
@classmethod
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
@@ -254,11 +173,6 @@ class MinerConfig(BaseModel):
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
return cls(pools=PoolConfig.from_am_modern(web_conf))
@classmethod
def from_goldshell_byte(cls, web_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Goldshell Byte miners."""
return cls(pools=PoolConfig.from_goldshell_byte(web_conf))
@classmethod
def from_inno(cls, web_pools: list) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
@@ -272,6 +186,7 @@ class MinerConfig(BaseModel):
mining_mode=MiningModeConfig.from_bosminer(toml_conf),
fan_mode=FanModeConfig.from_bosminer(toml_conf),
temperature=TemperatureConfig.from_bosminer(toml_conf),
power_scaling=PowerScalingConfig.from_bosminer(toml_conf),
)
@classmethod
@@ -282,6 +197,7 @@ class MinerConfig(BaseModel):
mining_mode=MiningModeConfig.from_boser(grpc_miner_conf),
fan_mode=FanModeConfig.from_boser(grpc_miner_conf),
temperature=TemperatureConfig.from_boser(grpc_miner_conf),
power_scaling=PowerScalingConfig.from_boser(grpc_miner_conf),
)
@classmethod
@@ -295,17 +211,13 @@ class MinerConfig(BaseModel):
)
@classmethod
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
) -> "MinerConfig":
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),
temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(
web_settings, web_presets, web_perf_summary
),
mining_mode=MiningModeConfig.from_vnish(web_settings),
)
@classmethod
@@ -316,60 +228,3 @@ class MinerConfig(BaseModel):
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
)
@classmethod
def from_mara(cls, web_miner_config: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_mara(web_miner_config),
fan_mode=FanModeConfig.from_mara(web_miner_config),
mining_mode=MiningModeConfig.from_mara(web_miner_config),
)
@classmethod
def from_espminer(cls, web_system_info: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_espminer(web_system_info),
fan_mode=FanModeConfig.from_espminer(web_system_info),
)
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_iceriver(web_userpanel),
)
@classmethod
def from_luxos(
cls,
rpc_tempctrl: dict,
rpc_fans: dict,
rpc_pools: dict,
rpc_groups: dict,
rpc_config: dict,
rpc_profiles: dict,
) -> "MinerConfig":
return cls(
temperature=TemperatureConfig.from_luxos(rpc_tempctrl=rpc_tempctrl),
fan_mode=FanModeConfig.from_luxos(
rpc_tempctrl=rpc_tempctrl, rpc_fans=rpc_fans
),
pools=PoolConfig.from_luxos(rpc_pools=rpc_pools, rpc_groups=rpc_groups),
mining_mode=MiningModeConfig.from_luxos(
rpc_config=rpc_config, rpc_profiles=rpc_profiles
),
)
@classmethod
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
return cls.from_am_modern(*args, **kwargs)
@classmethod
def from_btminer_v3(
cls, rpc_pools: dict, rpc_settings: dict, rpc_device_info: dict
) -> "MinerConfig":
return cls(
pools=PoolConfig.from_btminer_v3(rpc_pools=rpc_pools["msg"]),
mining_mode=MiningModeConfig.from_btminer_v3(
rpc_device_info=rpc_device_info, rpc_settings=rpc_settings
),
)

View File

@@ -15,10 +15,9 @@
# ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum
from pydantic import BaseModel
class MinerConfigOption(Enum):
@classmethod
@@ -28,9 +27,6 @@ class MinerConfigOption(Enum):
def as_am_modern(self) -> dict:
return self.value.as_am_modern()
def as_hiveon_modern(self) -> dict:
return self.value.as_hiveon_modern()
def as_am_old(self) -> dict:
return self.value.as_am_old()
@@ -50,7 +46,7 @@ class MinerConfigOption(Enum):
return self.value.as_bosminer()
def as_boser(self) -> dict:
return self.value.as_boser
return self.value.as_boser()
def as_epic(self) -> dict:
return self.value.as_epic()
@@ -61,18 +57,6 @@ class MinerConfigOption(Enum):
def as_auradine(self) -> dict:
return self.value.as_auradine()
def as_mara(self) -> dict:
return self.value.as_mara()
def as_espminer(self) -> dict:
return self.value.as_espminer()
def as_luxos(self) -> dict:
return self.value.as_luxos()
def as_elphapex(self) -> dict:
return self.value.as_elphapex()
def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs)
@@ -80,36 +64,25 @@ class MinerConfigOption(Enum):
def default(cls):
pass
def __getitem__(self, item):
try:
return getattr(self, item)
except AttributeError:
raise KeyError
class MinerConfigValue(BaseModel):
@dataclass
class MinerConfigValue:
@classmethod
def from_dict(cls, dict_conf: dict | None):
return cls()
def as_dict(self) -> dict:
return self.model_dump()
return asdict(self)
def as_am_modern(self) -> dict:
return {}
def as_hiveon_modern(self) -> dict:
return {}
def as_am_old(self) -> dict:
return {}
def as_wm(self) -> dict:
return {}
def as_btminer_v3(self) -> dict:
return {}
def as_inno(self) -> dict:
return {}
@@ -133,21 +106,3 @@ class MinerConfigValue(BaseModel):
def as_auradine(self) -> dict:
return {}
def as_mara(self) -> dict:
return {}
def as_espminer(self) -> dict:
return {}
def as_luxos(self) -> dict:
return {}
def as_elphapex(self) -> dict:
return {}
def __getitem__(self, item):
try:
return getattr(self, item)
except AttributeError:
raise KeyError

View File

@@ -15,15 +15,14 @@
# ------------------------------------------------------------------------------
from __future__ import annotations
from typing import TypeVar, Union
from pydantic import Field
from dataclasses import dataclass, field
from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass
class FanModeNormal(MinerConfigValue):
mode: str = Field(init=False, default="normal")
mode: str = field(init=False, default="normal")
minimum_fans: int = 1
minimum_speed: int = 0
@@ -55,12 +54,6 @@ class FanModeNormal(MinerConfigValue):
def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
def as_hiveon_modern(self) -> dict:
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
def as_elphapex(self) -> dict:
return {"fc-fan-ctrl": False, "fc-fan-pwn": "100"}
def as_bosminer(self) -> dict:
return {
"temp_control": {"mode": "auto"},
@@ -78,36 +71,10 @@ class FanModeNormal(MinerConfigValue):
}
}
def as_mara(self) -> dict:
return {
"general-config": {"environment-profile": "AirCooling"},
"advance-config": {
"override-fan-control": False,
"fan-fixed-percent": 0,
},
}
def as_espminer(self) -> dict:
return {"autoFanspeed": 1}
def as_luxos(self) -> dict:
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
def as_vnish(self) -> dict:
return {
"cooling": {
"fan_min_count": self.minimum_fans,
"fan_min_duty": self.minimum_speed,
"mode": {
"name": "auto",
"param": None, # Target temp, must be set later...
},
}
}
@dataclass
class FanModeManual(MinerConfigValue):
mode: str = Field(init=False, default="manual")
mode: str = field(init=False, default="manual")
speed: int = 100
minimum_fans: int = 1
@@ -141,12 +108,6 @@ class FanModeManual(MinerConfigValue):
def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
def as_hiveon_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
def as_elphapex(self) -> dict:
return {"fc-fan-ctrl": True, "fc-fan-pwm": str(self.speed)}
def as_bosminer(self) -> dict:
return {
"temp_control": {"mode": "manual"},
@@ -159,36 +120,10 @@ class FanModeManual(MinerConfigValue):
def as_epic(self) -> dict:
return {"fans": {"Manual": {"speed": self.speed}}}
def as_mara(self) -> dict:
return {
"general-config": {"environment-profile": "AirCooling"},
"advance-config": {
"override-fan-control": True,
"fan-fixed-percent": self.speed,
},
}
def as_espminer(self) -> dict:
return {"autoFanspeed": 0, "fanspeed": self.speed}
def as_luxos(self) -> dict:
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
def as_vnish(self) -> dict:
return {
"cooling": {
"fan_min_count": self.minimum_fans,
"fan_min_duty": self.speed,
"mode": {
"name": "manual",
"param": self.speed, # Speed value
},
}
}
@dataclass
class FanModeImmersion(MinerConfigValue):
mode: str = Field(init=False, default="immersion")
mode: str = field(init=False, default="immersion")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
@@ -197,12 +132,6 @@ class FanModeImmersion(MinerConfigValue):
def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
def as_hiveon_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
def as_elphapex(self) -> dict:
return {"fc-fan-ctrl": True, "fc-fan-pwm": "0"}
def as_bosminer(self) -> dict:
return {
"fan_control": {"min_fans": 0},
@@ -211,15 +140,6 @@ class FanModeImmersion(MinerConfigValue):
def as_auradine(self) -> dict:
return {"fan": {"percentage": 0}}
def as_mara(self) -> dict:
return {"general-config": {"environment-profile": "OilImmersionCooling"}}
def as_luxos(self) -> dict:
return {"fanset": {"speed": 0, "min_fans": 0}}
def as_vnish(self) -> dict:
return {"cooling": {"mode": {"name": "immers"}}}
class FanModeConfig(MinerConfigOption):
normal = FanModeNormal
@@ -257,34 +177,6 @@ class FanModeConfig(MinerConfigOption):
else:
return cls.default()
@classmethod
def from_hiveon_modern(cls, web_conf: dict):
if web_conf.get("bitmain-fan-ctrl") is not None:
fan_manual = web_conf["bitmain-fan-ctrl"]
if fan_manual:
speed = int(web_conf["bitmain-fan-pwm"])
if speed == 0:
return cls.immersion()
return cls.manual(speed=speed)
else:
return cls.normal()
else:
return cls.default()
@classmethod
def from_elphapex(cls, web_conf: dict):
if web_conf.get("fc-fan-ctrl") is not None:
fan_manual = web_conf["fc-fan-ctrl"]
if fan_manual:
speed = int(web_conf["fc-fan-pwm"])
if speed == 0:
return cls.immersion()
return cls.manual(speed=speed)
else:
return cls.normal()
else:
return cls.default()
@classmethod
def from_epic(cls, web_conf: dict):
try:
@@ -345,7 +237,7 @@ class FanModeConfig(MinerConfigOption):
keys = temperature_conf.keys()
if "auto" in keys:
if "minimumRequiredFans" in keys:
return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
return cls.normal(temperature_conf["minimumRequiredFans"])
return cls.normal()
if "manual" in keys:
conf = {}
@@ -354,12 +246,6 @@ class FanModeConfig(MinerConfigOption):
if "minimumRequiredFans" in keys:
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
return cls.manual(**conf)
if "disabled" in keys:
conf = {}
if "fanSpeedRatio" in temperature_conf["disabled"].keys():
conf["speed"] = int(temperature_conf["disabled"]["fanSpeedRatio"])
return cls.manual(**conf)
return cls.default()
@classmethod
def from_auradine(cls, web_fan: dict):
@@ -369,53 +255,4 @@ class FanModeConfig(MinerConfigOption):
fan_1_target = fan_data["Target"]
return cls.manual(speed=round((fan_1_target / fan_1_max) * 100))
except LookupError:
pass
return cls.default()
@classmethod
def from_mara(cls, web_config: dict):
try:
mode = web_config["general-config"]["environment-profile"]
if mode == "AirCooling":
if web_config["advance-config"]["override-fan-control"]:
return cls.manual(
speed=web_config["advance-config"]["fan-fixed-percent"]
)
return cls.normal()
return cls.immersion()
except LookupError:
pass
return cls.default()
@classmethod
def from_espminer(cls, web_system_info: dict):
if web_system_info["autofanspeed"] == 1:
return cls.normal()
else:
return cls.manual(speed=web_system_info["fanspeed"])
@classmethod
def from_luxos(cls, rpc_fans: dict, rpc_tempctrl: dict):
try:
mode = rpc_tempctrl["TEMPCTRL"][0]["Mode"]
if mode == "Manual":
speed = rpc_fans["FANS"][0]["Speed"]
min_fans = rpc_fans["FANCTRL"][0]["MinFans"]
if min_fans == 0 and speed == 0:
return cls.immersion()
return cls.manual(
speed=speed,
minimum_fans=min_fans,
)
return cls.normal(
minimum_fans=rpc_fans["FANCTRL"][0]["MinFans"],
)
except LookupError:
pass
return cls.default()
FanMode = TypeVar(
"FanMode",
bound=Union[FanModeNormal, FanModeManual, FanModeImmersion],
)
return cls.default()

470
pyasic/config/mining.py Normal file
View File

@@ -0,0 +1,470 @@
# ------------------------------------------------------------------------------
# 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 __future__ import annotations
from dataclasses import dataclass, field
from pyasic import settings
from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
HashrateTargetMode,
PerformanceMode,
Power,
PowerTargetMode,
SaveAction,
SetPerformanceModeRequest,
TeraHashrate,
TunerPerformanceMode,
)
@dataclass
class MiningModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": self.mode}}
def as_epic(self) -> dict:
return {"ptune": {"enabled": False}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 0}}
@dataclass
class MiningModeSleep(MinerConfigValue):
mode: str = field(init=False, default="sleep")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "1"}
return {"miner-mode": 1}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"sleep": "on"}}
def as_epic(self) -> dict:
return {"ptune": {"algo": "Sleep", "target": 0}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 3}}
@dataclass
class MiningModeLPM(MinerConfigValue):
mode: str = field(init=False, default="low")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "3"}
return {"miner-mode": 3}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": "eco"}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 1}}
@dataclass
class MiningModeHPM(MinerConfigValue):
mode: str = field(init=False, default="high")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": "turbo"}}
@dataclass
class StandardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="standard")
def as_epic(self) -> str:
return VOptAlgo().as_epic()
@dataclass
class VOptAlgo(MinerConfigValue):
mode: str = field(init=False, default="voltage_optimizer")
def as_epic(self) -> str:
return "VoltageOptimizer"
class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="chip_tune")
def as_epic(self) -> str:
return "ChipTune"
@dataclass
class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo
chip_tune = ChipTuneAlgo
@classmethod
def default(cls):
return cls.standard()
@classmethod
def from_dict(cls, dict_conf: dict | None):
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
cls_attr = getattr(cls, mode)
if cls_attr is not None:
return cls_attr().from_dict(dict_conf)
@dataclass
class MiningModePowerTune(MinerConfigValue):
mode: str = field(init=False, default="power_tuning")
power: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
cls_conf = {}
if dict_conf.get("power"):
cls_conf["power"] = dict_conf["power"]
if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
return cls(**cls_conf)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_wm(self) -> dict:
if self.power is not None:
return {"mode": self.mode, self.mode: {"wattage": self.power}}
return {}
def as_bosminer(self) -> dict:
conf = {"enabled": True, "mode": "power_target"}
if self.power is not None:
conf["power_target"] = self.power
return {"autotuning": conf}
def as_boser(self) -> dict:
return {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
power_target=PowerTargetMode(
power_target=Power(watt=self.power)
)
)
),
),
}
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
@dataclass
class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
cls_conf = {}
if dict_conf.get("hashrate"):
cls_conf["hashrate"] = dict_conf["hashrate"]
if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
return cls(**cls_conf)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_bosminer(self) -> dict:
conf = {"enabled": True, "mode": "hashrate_target"}
if self.hashrate is not None:
conf["hashrate_target"] = self.hashrate
return {"autotuning": conf}
def as_boser(self) -> dict:
return {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
hashrate_target=HashrateTargetMode(
hashrate_target=TeraHashrate(
terahash_per_second=self.hashrate
)
)
)
),
)
}
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
def as_epic(self) -> dict:
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
@dataclass
class ManualBoardSettings(MinerConfigValue):
freq: float
volt: float
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
@dataclass
class MiningModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual")
global_freq: float
global_volt: float
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
return cls(
global_freq=dict_conf["global_freq"],
global_volt=dict_conf["global_volt"],
boards={i: ManualBoardSettings.from_dict(dict_conf[i]) for i in dict_conf},
)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
@classmethod
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
# will raise KeyError if it cant find the settings, values cannot be empty
voltage = web_overclock_settings["globals"]["volt"]
freq = web_overclock_settings["globals"]["freq"]
boards = {
idx: ManualBoardSettings(
freq=board["freq"],
volt=voltage if not board["freq"] == 0 else 0,
)
for idx, board in enumerate(web_overclock_settings["chains"])
}
return cls(global_freq=freq, global_volt=voltage, boards=boards)
class MiningModeConfig(MinerConfigOption):
normal = MiningModeNormal
low = MiningModeLPM
high = MiningModeHPM
sleep = MiningModeSleep
power_tuning = MiningModePowerTune
hashrate_tuning = MiningModeHashrateTune
manual = MiningModeManual
@classmethod
def default(cls):
return cls.normal()
@classmethod
def from_dict(cls, dict_conf: dict | None):
if dict_conf is None:
return cls.default()
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
cls_attr = getattr(cls, mode)
if cls_attr is not None:
return cls_attr().from_dict(dict_conf)
@classmethod
def from_am_modern(cls, web_conf: dict):
if web_conf.get("bitmain-work-mode") is not None:
work_mode = web_conf["bitmain-work-mode"]
if work_mode == "":
return cls.default()
if int(work_mode) == 0:
return cls.normal()
elif int(work_mode) == 1:
return cls.sleep()
elif int(work_mode) == 3:
return cls.low()
return cls.default()
@classmethod
def from_epic(cls, web_conf: dict):
try:
tuner_running = web_conf["PerpetualTune"]["Running"]
if tuner_running:
algo_info = web_conf["PerpetualTune"]["Algorithm"]
if algo_info.get("VoltageOptimizer") is not None:
return cls.hashrate_tuning(
hashrate=algo_info["VoltageOptimizer"]["Target"],
algo=TunerAlgo.voltage_optimizer,
)
else:
return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"]["Target"],
algo=TunerAlgo.chip_tune,
)
else:
return cls.normal()
except KeyError:
return cls.default()
@classmethod
def from_bosminer(cls, toml_conf: dict):
if toml_conf.get("autotuning") is None:
return cls.default()
autotuning_conf = toml_conf["autotuning"]
if autotuning_conf.get("enabled") is None:
return cls.default()
if not autotuning_conf["enabled"]:
return cls.default()
if autotuning_conf.get("psu_power_limit") is not None:
# old autotuning conf
return cls.power_tuning(autotuning_conf["psu_power_limit"])
if autotuning_conf.get("mode") is not None:
# new autotuning conf
mode = autotuning_conf["mode"]
if mode == "power_target":
if autotuning_conf.get("power_target") is not None:
return cls.power_tuning(autotuning_conf["power_target"])
return cls.power_tuning()
if mode == "hashrate_target":
if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning(autotuning_conf["hashrate_target"])
return cls.hashrate_tuning()
@classmethod
def from_vnish(cls, web_settings: dict):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
return cls.default()
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return cls.power_tuning(int(mode_settings["preset"]))
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
tuner_conf = grpc_miner_conf["tuner"]
if not tuner_conf.get("enabled", False):
return cls.default()
except LookupError:
return cls.default()
if tuner_conf.get("tunerMode") is not None:
if tuner_conf["tunerMode"] == 1:
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(tuner_conf["powerTarget"]["watt"])
return cls.power_tuning()
if tuner_conf["tunerMode"] == 2:
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
)
return cls.hashrate_tuning()
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(tuner_conf["powerTarget"]["watt"])
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
)
@classmethod
def from_auradine(cls, web_mode: dict):
try:
mode_data = web_mode["Mode"][0]
if mode_data.get("Sleep") == "on":
return cls.sleep()
if mode_data.get("Mode") == "normal":
return cls.normal()
if mode_data.get("Mode") == "eco":
return cls.low()
if mode_data.get("Mode") == "turbo":
return cls.high()
if mode_data.get("Ths") is not None:
return cls.hashrate_tuning(mode_data["Ths"])
if mode_data.get("Power") is not None:
return cls.power_tuning(mode_data["Power"])
except LookupError:
return cls.default()

View File

@@ -1,873 +0,0 @@
# ------------------------------------------------------------------------------
# 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 __future__ import annotations
from dataclasses import field
from typing import TypeVar, Union
from pyasic import settings
from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
DpsHashrateTarget,
DpsPowerTarget,
DpsTarget,
HashrateTargetMode,
PerformanceMode,
Power,
PowerTargetMode,
SaveAction,
SetDpsRequest,
SetPerformanceModeRequest,
TeraHashrate,
TunerPerformanceMode,
)
from .algo import TunerAlgo, TunerAlgoType
from .presets import MiningPreset
from .scaling import ScalingConfig
class MiningModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "start", "set.miner.power_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": {"level": 0}}
def as_mara(self) -> dict:
return {
"mode": {
"work-mode-selector": "Stock",
}
}
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": False}}
def as_bosminer(self) -> dict:
return {"autotuning": {"enabled": True}}
class MiningModeSleep(MinerConfigValue):
mode: str = field(init=False, default="sleep")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "1"}
return {"miner-mode": 1}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "1"}
return {"miner-mode": 1}
def as_elphapex(self) -> dict:
return {"miner-mode": 1}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "stop"}
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": {"level": 3}}
def as_mara(self) -> dict:
return {
"mode": {
"work-mode-selector": "Sleep",
}
}
class MiningModeLPM(MinerConfigValue):
mode: str = field(init=False, default="low")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "3"}
return {"miner-mode": 3}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "3"}
return {"miner-mode": 3}
def as_elphapex(self) -> dict:
return {"miner-mode": 3}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "start", "set.miner.power_mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": "eco"}}
def as_goldshell(self) -> dict:
return {"settings": {"level": 1}}
class MiningModeHPM(MinerConfigValue):
mode: str = field(init=False, default="high")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
return cls()
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "start", "set.miner.power_mode": self.mode}
def as_auradine(self) -> dict:
return {"mode": {"mode": "turbo"}}
class MiningModePowerTune(MinerConfigValue):
class Config:
arbitrary_types_allowed = True
mode: str = field(init=False, default="power_tuning")
power: int | None = None
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig | None = None
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
cls_conf = {}
if dict_conf.get("power"):
cls_conf["power"] = dict_conf["power"]
if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
if dict_conf.get("scaling"):
cls_conf["scaling"] = ScalingConfig.from_dict(dict_conf["scaling"])
return cls(**cls_conf)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_wm(self) -> dict:
if self.power is not None:
return {"mode": self.mode, self.mode: {"wattage": self.power}}
return {}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "start", "set.miner.power_limit": self.power}
def as_bosminer(self) -> dict:
tuning_cfg = {"enabled": True, "mode": "power_target"}
if self.power is not None:
tuning_cfg["power_target"] = self.power
cfg = {"autotuning": tuning_cfg}
if self.scaling is not None:
scaling_cfg = {"enabled": True}
if self.scaling.step is not None:
scaling_cfg["power_step"] = self.scaling.step
if self.scaling.minimum is not None:
scaling_cfg["min_power_target"] = self.scaling.minimum
if self.scaling.shutdown is not None:
scaling_cfg = {**scaling_cfg, **self.scaling.shutdown.as_bosminer()}
cfg["performance_scaling"] = scaling_cfg
return cfg
def as_boser(self) -> dict:
cfg = {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
power_target=PowerTargetMode(
power_target=Power(watt=self.power)
)
)
),
),
}
if self.scaling is not None:
sd_cfg = {}
if self.scaling.shutdown is not None:
sd_cfg = self.scaling.shutdown.as_boser()
power_target_kwargs = {}
if self.scaling.step is not None:
power_target_kwargs["power_step"] = Power(self.scaling.step)
if self.scaling.minimum is not None:
power_target_kwargs["min_power_target"] = Power(self.scaling.minimum)
cfg["set_dps"] = SetDpsRequest(
save_action=SaveAction.SAVE_AND_APPLY,
enable=True,
**sd_cfg,
target=DpsTarget(power_target=DpsPowerTarget(**power_target_kwargs)),
)
return cfg
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
def as_mara(self) -> dict:
return {
"mode": {
"work-mode-selector": "Auto",
"concorde": {
"mode-select": "PowerTarget",
"power-target": self.power,
},
}
}
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": True}}
class MiningModeHashrateTune(MinerConfigValue):
class Config:
arbitrary_types_allowed = True
mode: str = field(init=False, default="hashrate_tuning")
hashrate: int | None = None
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig | None = None
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
cls_conf = {}
if dict_conf.get("hashrate"):
cls_conf["hashrate"] = dict_conf["hashrate"]
if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
if dict_conf.get("scaling"):
cls_conf["scaling"] = ScalingConfig.from_dict(dict_conf["scaling"])
return cls(**cls_conf)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_bosminer(self) -> dict:
conf = {"enabled": True, "mode": "hashrate_target"}
if self.hashrate is not None:
conf["hashrate_target"] = self.hashrate
return {"autotuning": conf}
def as_boser(self) -> dict:
cfg = {
"set_performance_mode": SetPerformanceModeRequest(
save_action=SaveAction.SAVE_AND_APPLY,
mode=PerformanceMode(
tuner_mode=TunerPerformanceMode(
hashrate_target=HashrateTargetMode(
hashrate_target=TeraHashrate(
terahash_per_second=self.hashrate
)
)
)
),
)
}
if self.scaling is not None:
sd_cfg = {}
if self.scaling.shutdown is not None:
sd_cfg = self.scaling.shutdown.as_boser()
hashrate_target_kwargs = {}
if self.scaling.step is not None:
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
self.scaling.step
)
if self.scaling.minimum is not None:
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
self.scaling.minimum
)
cfg["set_dps"] = SetDpsRequest(
save_action=SaveAction.SAVE_AND_APPLY,
enable=True,
**sd_cfg,
target=DpsTarget(
hashrate_target=DpsHashrateTarget(**hashrate_target_kwargs)
),
)
return cfg
def as_auradine(self) -> dict:
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
def as_epic(self) -> dict:
mode = {
"ptune": {
"algo": self.algo.as_epic(),
"target": self.hashrate,
}
}
if self.scaling is not None:
if self.scaling.minimum is not None:
mode["ptune"]["min_throttle"] = self.scaling.minimum
if self.scaling.step is not None:
mode["ptune"]["throttle_step"] = self.scaling.step
return mode
def as_mara(self) -> dict:
return {
"mode": {
"work-mode-selector": "Auto",
"concorde": {
"mode-select": "Hashrate",
"hash-target": self.hashrate,
},
}
}
def as_luxos(self) -> dict:
return {"autotunerset": {"enabled": True}}
class MiningModePreset(MinerConfigValue):
mode: str = field(init=False, default="preset")
active_preset: MiningPreset
available_presets: list[MiningPreset] = field(default_factory=list)
def as_vnish(self) -> dict:
return {"overclock": {**self.active_preset.as_vnish()}}
@classmethod
def from_vnish(
cls,
web_overclock_settings: dict,
web_presets: list[dict],
web_perf_summary: dict,
) -> "MiningModePreset":
active_preset = web_perf_summary.get("current_preset")
if active_preset is None:
for preset in web_presets:
if preset["name"] == web_overclock_settings["preset"]:
active_preset = preset
return cls(
active_preset=MiningPreset.from_vnish(active_preset),
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
)
@classmethod
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> "MiningModePreset":
active_preset = cls.get_active_preset_from_luxos(rpc_config, rpc_profiles)
return cls(
active_preset=active_preset,
available_presets=[
MiningPreset.from_luxos(p) for p in rpc_profiles["PROFILES"]
],
)
@classmethod
def get_active_preset_from_luxos(
cls, rpc_config: dict, rpc_profiles: dict
) -> MiningPreset:
active_preset = None
active_profile = rpc_config["CONFIG"][0]["Profile"]
for profile in rpc_profiles["PROFILES"]:
if profile["Profile Name"] == active_profile:
active_preset = profile
return MiningPreset.from_luxos(active_preset)
class ManualBoardSettings(MinerConfigValue):
freq: float
volt: float
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_hiveon_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_vnish(self) -> dict:
return {"freq": self.freq}
class MiningModeManual(MinerConfigValue):
mode: str = field(init=False, default="manual")
global_freq: float
global_volt: float
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
return cls(
global_freq=dict_conf["global_freq"],
global_volt=dict_conf["global_volt"],
boards={i: ManualBoardSettings.from_dict(dict_conf[i]) for i in dict_conf},
)
def as_am_modern(self) -> dict:
if settings.get("antminer_mining_mode_as_str", False):
return {"miner-mode": "0"}
return {"miner-mode": 0}
def as_elphapex(self) -> dict:
return {"miner-mode": 0}
def as_vnish(self) -> dict:
chains = [b.as_vnish() for b in self.boards.values() if b.freq != 0]
return {
"overclock": {
"chains": chains if chains != [] else None,
"globals": {
"freq": int(self.global_freq),
"volt": int(self.global_volt),
},
}
}
@classmethod
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
# will raise KeyError if it cant find the settings, values cannot be empty
voltage = web_overclock_settings["globals"]["volt"]
freq = web_overclock_settings["globals"]["freq"]
boards = {
idx: ManualBoardSettings(
freq=board["freq"],
volt=voltage if not board["freq"] == 0 else 0,
)
for idx, board in enumerate(web_overclock_settings["chains"])
}
return cls(global_freq=freq, global_volt=voltage, boards=boards)
@classmethod
def from_epic(cls, epic_conf: dict) -> "MiningModeManual":
voltage = 0
freq = 0
if epic_conf.get("HwConfig") is not None:
freq = epic_conf["HwConfig"]["Boards Target Clock"][0]["Data"]
if epic_conf.get("Power Supply Stats") is not None:
voltage = epic_conf["Power Supply Stats"]["Target Voltage"]
boards = {}
if epic_conf.get("HBs") is not None:
boards = {
board["Index"]: ManualBoardSettings(
freq=board["Core Clock Avg"], volt=board["Input Voltage"]
)
for board in epic_conf["HBs"]
}
return cls(global_freq=freq, global_volt=voltage, boards=boards)
def as_mara(self) -> dict:
return {
"mode": {
"work-mode-selector": "Fixed",
"fixed": {
"frequency": str(self.global_freq),
"voltage": self.global_volt,
},
}
}
class MiningModeConfig(MinerConfigOption):
normal = MiningModeNormal
low = MiningModeLPM
high = MiningModeHPM
sleep = MiningModeSleep
power_tuning = MiningModePowerTune
hashrate_tuning = MiningModeHashrateTune
preset = MiningModePreset
manual = MiningModeManual
@classmethod
def default(cls):
return cls.normal()
@classmethod
def from_dict(cls, dict_conf: dict | None):
if dict_conf is None:
return cls.default()
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
cls_attr = getattr(cls, mode)
if cls_attr is not None:
return cls_attr().from_dict(dict_conf)
@classmethod
def from_am_modern(cls, web_conf: dict):
if web_conf.get("bitmain-work-mode") is not None:
work_mode = web_conf["bitmain-work-mode"]
if work_mode == "":
return cls.default()
if int(work_mode) == 0:
return cls.normal()
elif int(work_mode) == 1:
return cls.sleep()
elif int(work_mode) == 3:
return cls.low()
return cls.default()
@classmethod
def from_hiveon_modern(cls, web_conf: dict):
if web_conf.get("bitmain-work-mode") is not None:
work_mode = web_conf["bitmain-work-mode"]
if work_mode == "":
return cls.default()
if int(work_mode) == 0:
return cls.normal()
elif int(work_mode) == 1:
return cls.sleep()
elif int(work_mode) == 3:
return cls.low()
return cls.default()
@classmethod
def from_elphapex(cls, web_conf: dict):
if web_conf.get("fc-work-mode") is not None:
work_mode = web_conf["fc-work-mode"]
if work_mode == "":
return cls.default()
if int(work_mode) == 0:
return cls.normal()
elif int(work_mode) == 1:
return cls.sleep()
elif int(work_mode) == 3:
return cls.low()
return cls.default()
@classmethod
def from_epic(cls, web_conf: dict):
try:
tuner_running = web_conf["PerpetualTune"]["Running"]
if tuner_running:
algo_info = web_conf["PerpetualTune"]["Algorithm"]
if algo_info.get("VoltageOptimizer") is not None:
scaling_cfg = None
if "Throttle Step" in algo_info["VoltageOptimizer"]:
scaling_cfg = ScalingConfig(
minimum=algo_info["VoltageOptimizer"].get(
"Min Throttle Target"
),
step=algo_info["VoltageOptimizer"].get("Throttle Step"),
)
return cls.hashrate_tuning(
hashrate=algo_info["VoltageOptimizer"].get("Target"),
algo=TunerAlgo.voltage_optimizer(),
scaling=scaling_cfg,
)
elif algo_info.get("BoardTune") is not None:
scaling_cfg = None
if "Throttle Step" in algo_info["BoardTune"]:
scaling_cfg = ScalingConfig(
minimum=algo_info["BoardTune"].get("Min Throttle Target"),
step=algo_info["BoardTune"].get("Throttle Step"),
)
return cls.hashrate_tuning(
hashrate=algo_info["BoardTune"].get("Target"),
algo=TunerAlgo.board_tune(),
scaling=scaling_cfg,
)
else:
return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"].get("Target"),
algo=TunerAlgo.chip_tune(),
)
else:
return MiningModeManual.from_epic(web_conf)
except KeyError:
return cls.default()
@classmethod
def from_bosminer(cls, toml_conf: dict):
if toml_conf.get("autotuning") is None:
return cls.default()
autotuning_conf = toml_conf["autotuning"]
if autotuning_conf.get("enabled") is None:
return cls.default()
if not autotuning_conf["enabled"]:
return cls.default()
if autotuning_conf.get("psu_power_limit") is not None:
# old autotuning conf
return cls.power_tuning(
power=autotuning_conf["psu_power_limit"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
)
if autotuning_conf.get("mode") is not None:
# new autotuning conf
mode = autotuning_conf["mode"]
if mode == "power_target":
if autotuning_conf.get("power_target") is not None:
return cls.power_tuning(
power=autotuning_conf["power_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
)
return cls.power_tuning(
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
)
if mode == "hashrate_target":
if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning(
hashrate=autotuning_conf["hashrate_target"],
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
)
return cls.hashrate_tuning(
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
)
return cls.default()
@classmethod
def from_vnish(
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
return cls.default()
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return MiningModePreset.from_vnish(
mode_settings, web_presets, web_perf_summary
)
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
tuner_conf = grpc_miner_conf["tuner"]
if not tuner_conf.get("enabled", False):
return cls.default()
except LookupError:
return cls.default()
if tuner_conf.get("tunerMode") is not None:
if tuner_conf["tunerMode"] == 1:
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(
power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
)
return cls.power_tuning(
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power")
)
if tuner_conf["tunerMode"] == 2:
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser(
grpc_miner_conf, mode="hashrate"
),
)
return cls.hashrate_tuning(
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
)
if tuner_conf.get("powerTarget") is not None:
return cls.power_tuning(
power=tuner_conf["powerTarget"]["watt"],
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
)
if tuner_conf.get("hashrateTarget") is not None:
return cls.hashrate_tuning(
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
)
return cls.default()
@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(hashrate=mode_data["Ths"])
if mode_data.get("Power") is not None:
return cls.power_tuning(power=mode_data["Power"])
except LookupError:
return cls.default()
@classmethod
def from_btminer_v3(cls, rpc_device_info: dict, rpc_settings: dict):
try:
is_mining = rpc_device_info["msg"]["miner"]["working"] == "true"
if not is_mining:
return cls.sleep()
power_limit = rpc_settings["msg"]["power-limit"]
if not power_limit == 0:
return cls.power_tuning(power=power_limit)
power_mode = rpc_settings["msg"]["power-mode"]
if power_mode == "normal":
return cls.normal()
if power_mode == "high":
return cls.high()
if power_mode == "low":
return cls.low()
except LookupError:
return cls.default()
@classmethod
def from_mara(cls, web_config: dict):
try:
mode = web_config["mode"]["work-mode-selector"]
if mode == "Fixed":
fixed_conf = web_config["mode"]["fixed"]
return cls.manual(
global_freq=int(fixed_conf["frequency"]),
global_volt=fixed_conf["voltage"],
)
elif mode == "Stock":
return cls.normal()
elif mode == "Sleep":
return cls.sleep()
elif mode == "Auto":
auto_conf = web_config["mode"]["concorde"]
auto_mode = auto_conf["mode-select"]
if auto_mode == "Hashrate":
return cls.hashrate_tuning(hashrate=auto_conf["hash-target"])
elif auto_mode == "PowerTarget":
return cls.power_tuning(power=auto_conf["power-target"])
except LookupError:
pass
return cls.default()
@classmethod
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict):
preset_info = MiningModePreset.from_luxos(rpc_config, rpc_profiles)
return cls.preset(
active_preset=preset_info.active_preset,
available_presets=preset_info.available_presets,
)
MiningMode = TypeVar(
"MiningMode",
bound=Union[
MiningModeNormal,
MiningModeHPM,
MiningModeLPM,
MiningModeSleep,
MiningModeManual,
MiningModePowerTune,
MiningModeHashrateTune,
MiningModePreset,
],
)

View File

@@ -1,66 +0,0 @@
from __future__ import annotations
from dataclasses import field
from typing import TypeVar, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue
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="voltage_optimizer")
def as_epic(self) -> str:
return "VoltageOptimizer"
class BoardTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="board_tune")
def as_epic(self) -> str:
return "BoardTune"
class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="chip_tune")
def as_epic(self) -> str:
return "ChipTune"
class TunerAlgo(MinerConfigOption):
standard = StandardTuneAlgo
voltage_optimizer = VOptAlgo
board_tune = BoardTuneAlgo
chip_tune = ChipTuneAlgo
@classmethod
def default(cls) -> TunerAlgoType:
return cls.standard()
@classmethod
def from_dict(cls, dict_conf: dict | None) -> TunerAlgoType:
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
cls_attr = getattr(cls, mode)
if cls_attr is not None:
return cls_attr().from_dict(dict_conf)
TunerAlgoType = TypeVar(
"TunerAlgoType",
bound=Union[
StandardTuneAlgo,
VOptAlgo,
BoardTuneAlgo,
ChipTuneAlgo,
],
)

View File

@@ -1,54 +0,0 @@
from pyasic.config.base import MinerConfigValue
class MiningPreset(MinerConfigValue):
name: str | None = None
power: int | None = None
hashrate: int | None = None
tuned: bool | None = None
modded_psu: bool | None = None
frequency: int | None = None
voltage: float | None = None
def as_vnish(self) -> dict:
if self.name is not None:
return {"preset": self.name}
return {}
@classmethod
def from_vnish(cls, web_preset: dict):
name = web_preset["name"]
hr_power_split = web_preset["pretty"].split("~")
if len(hr_power_split) == 1:
power = None
hashrate = None
else:
power = hr_power_split[0].replace("watt", "").strip()
hashrate = (
hr_power_split[1]
.replace("TH", "")
.replace("GH", "")
.replace("MH", "")
.replace(" LC", "")
.strip()
)
tuned = web_preset["status"] == "tuned"
modded_psu = web_preset["modded_psu_required"]
return cls(
name=name,
power=power,
hashrate=hashrate,
tuned=tuned,
modded_psu=modded_psu,
)
@classmethod
def from_luxos(cls, profile: dict):
return cls(
name=profile["Profile Name"],
power=profile["Watts"],
hashrate=round(profile["Hashrate"]),
tuned=profile["IsTuned"],
frequency=profile["Frequency"],
voltage=profile["Voltage"],
)

View File

@@ -1,129 +0,0 @@
# ------------------------------------------------------------------------------
# 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 __future__ import annotations
from pyasic.config.base import MinerConfigValue
class ScalingShutdown(MinerConfigValue):
enabled: bool = False
duration: int | None = None
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "ScalingShutdown":
return cls(
enabled=dict_conf.get("enabled", False), duration=dict_conf.get("duration")
)
@classmethod
def from_bosminer(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdown_enabled")
if sd_enabled is not None:
return cls(
enabled=sd_enabled, duration=power_scaling_conf.get("shutdown_duration")
)
return None
@classmethod
def from_boser(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdownEnabled")
if sd_enabled is not None:
try:
return cls(
enabled=sd_enabled,
duration=power_scaling_conf["shutdownDuration"]["hours"],
)
except KeyError:
return cls(enabled=sd_enabled)
return None
def as_bosminer(self) -> dict:
cfg = {"shutdown_enabled": self.enabled}
if self.duration is not None:
cfg["shutdown_duration"] = self.duration
return cfg
def as_boser(self) -> dict:
return {"enable_shutdown": self.enabled, "shutdown_duration": self.duration}
class ScalingConfig(MinerConfigValue):
step: int | None = None
minimum: int | None = None
shutdown: ScalingShutdown | None = None
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "ScalingConfig":
cls_conf = {
"step": dict_conf.get("step"),
"minimum": dict_conf.get("minimum"),
}
shutdown = dict_conf.get("shutdown")
if shutdown is not None:
cls_conf["shutdown"] = ScalingShutdown.from_dict(shutdown)
return cls(**cls_conf)
@classmethod
def from_bosminer(cls, toml_conf: dict, mode: str | None = None):
if mode == "power":
return cls._from_bosminer_power(toml_conf)
if mode == "hashrate":
# not implemented yet
pass
@classmethod
def _from_bosminer_power(cls, toml_conf: dict):
power_scaling = toml_conf.get("power_scaling")
if power_scaling is None:
power_scaling = toml_conf.get("performance_scaling")
if power_scaling is not None:
enabled = power_scaling.get("enabled")
if not enabled:
return None
power_step = power_scaling.get("power_step")
min_power = power_scaling.get("min_psu_power_limit")
if min_power is None:
min_power = power_scaling.get("min_power_target")
sd_mode = ScalingShutdown.from_bosminer(power_scaling)
return cls(step=power_step, minimum=min_power, shutdown=sd_mode)
@classmethod
def from_boser(cls, grpc_miner_conf: dict, mode: str | None = None):
if mode == "power":
return cls._from_boser_power(grpc_miner_conf)
if mode == "hashrate":
# not implemented yet
pass
@classmethod
def _from_boser_power(cls, grpc_miner_conf: dict):
try:
dps_conf = grpc_miner_conf["dps"]
if not dps_conf.get("enabled", False):
return None
except LookupError:
return None
conf = {"shutdown": ScalingShutdown.from_boser(dps_conf)}
if dps_conf.get("minPowerTarget") is not None:
conf["minimum"] = dps_conf["minPowerTarget"]["watt"]
if dps_conf.get("powerStep") is not None:
conf["step"] = dps_conf["powerStep"]["watt"]
return cls(**conf)

View File

@@ -17,133 +17,106 @@ from __future__ import annotations
import random
import string
from dataclasses import dataclass, field
from typing import List
from pydantic import Field
from pyasic.config.base import MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
PoolConfiguration,
PoolGroupConfiguration,
Quota,
SaveAction,
SetPoolGroupsRequest,
)
@dataclass
class Pool(MinerConfigValue):
url: str
user: str
password: str
def as_am_modern(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_am_modern(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_hiveon_modern(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_elphapex(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_wm(self, idx: int = 1, user_suffix: str | None = None) -> dict:
def as_wm(self, idx: int = 1, user_suffix: str = None) -> dict:
if user_suffix is not None:
return {
f"pool_{idx}": self.url,
f"worker_{idx}": f"{self.user}{user_suffix}",
f"passwd_{idx}": self.password,
}
return {
f"pool_{idx}": self.url,
f"worker_{idx}": f"{self.user}{user_suffix or ''}",
f"worker_{idx}": self.user,
f"passwd_{idx}": self.password,
}
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
return {
f"pool": self.url,
f"worker": f"{self.user}{user_suffix or ''}",
f"passwd": self.password,
}
def as_am_old(self, idx: int = 1, user_suffix: str | None = None) -> dict:
def as_am_old(self, idx: int = 1, user_suffix: str = None) -> dict:
if user_suffix is not None:
return {
f"_ant_pool{idx}url": self.url,
f"_ant_pool{idx}user": f"{self.user}{user_suffix}",
f"_ant_pool{idx}pw": self.password,
}
return {
f"_ant_pool{idx}url": self.url,
f"_ant_pool{idx}user": f"{self.user}{user_suffix or ''}",
f"_ant_pool{idx}user": self.user,
f"_ant_pool{idx}pw": self.password,
}
def as_goldshell(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_goldshell(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_avalon(self, user_suffix: str | None = None) -> str:
return ",".join([self.url, f"{self.user}{user_suffix or ''}", self.password])
def as_avalon(self, user_suffix: str = None) -> str:
if user_suffix is not None:
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
return ",".join([self.url, self.user, self.password])
def as_inno(self, idx: int = 1, user_suffix: str | None = None) -> dict:
def as_inno(self, idx: int = 1, user_suffix: str = None) -> dict:
if user_suffix is not None:
return {
f"Pool{idx}": self.url,
f"UserName{idx}": f"{self.user}{user_suffix}",
f"Password{idx}": self.password,
}
return {
f"Pool{idx}": self.url,
f"UserName{idx}": f"{self.user}{user_suffix or ''}",
f"UserName{idx}": self.user,
f"Password{idx}": self.password,
}
def as_bosminer(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"password": self.password,
}
def as_bosminer(self, user_suffix: str = None) -> dict:
if user_suffix is not None:
return {
"url": self.url,
"user": f"{self.user}{user_suffix}",
"password": self.password,
}
return {"url": self.url, "user": self.user, "password": self.password}
def as_auradine(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": 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 = None):
return {
"pool": self.url,
"login": f"{self.user}{user_suffix or ''}",
"password": self.password,
}
def as_mara(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_espminer(self, user_suffix: str | None = None) -> dict:
return {
"stratumURL": self.url,
"stratumUser": f"{self.user}{user_suffix or ''}",
"stratumPassword": self.password,
}
def as_boser(self, user_suffix: str | None = None) -> PoolConfiguration:
return PoolConfiguration(
url=self.url,
user=f"{self.user}{user_suffix or ''}",
password=self.password,
enabled=True,
)
def as_vnish(self, user_suffix: str | None = None) -> dict:
return {
"url": self.url,
"user": f"{self.user}{user_suffix or ''}",
"pass": self.password,
}
def as_epic(self, user_suffix: str = None):
if user_suffix is not None:
return {
"pool": self.url,
"login": f"{self.user}{user_suffix}",
"password": self.password,
}
return {"pool": self.url, "login": self.user, "password": self.password}
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "Pool":
@@ -155,10 +128,6 @@ class Pool(MinerConfigValue):
def from_api(cls, api_pool: dict) -> "Pool":
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
@classmethod
def from_btminer_v3(cls, api_pool: dict) -> "Pool":
return cls(url=api_pool["url"], user=api_pool["account"], password="x")
@classmethod
def from_epic(cls, api_pool: dict) -> "Pool":
return cls(
@@ -171,18 +140,6 @@ class Pool(MinerConfigValue):
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
)
@classmethod
def from_hiveon_modern(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
)
@classmethod
def from_elphapex(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
)
# TODO: check if this is accurate, user/username, pass/password
@classmethod
def from_goldshell(cls, web_pool: dict) -> "Pool":
@@ -201,13 +158,13 @@ class Pool(MinerConfigValue):
return cls(
url=toml_pool_conf["url"],
user=toml_pool_conf["user"],
password=toml_pool_conf.get("password", ""),
password=toml_pool_conf["password"],
)
@classmethod
def from_vnish(cls, web_pool: dict) -> "Pool":
return cls(
url="stratum+tcp://" + web_pool["url"],
url=web_pool["url"],
user=web_pool["user"],
password=web_pool["pass"],
)
@@ -220,40 +177,12 @@ class Pool(MinerConfigValue):
password=grpc_pool["password"],
)
@classmethod
def from_mara(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["url"],
user=web_pool["user"],
password=web_pool["pass"],
)
@classmethod
def from_espminer(cls, web_system_info: dict) -> "Pool":
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
return cls(
url=url,
user=web_system_info["stratumUser"],
password=web_system_info.get("stratumPassword", ""),
)
@classmethod
def from_luxos(cls, rpc_pools: dict) -> "Pool":
return cls.from_api(rpc_pools)
@classmethod
def from_iceriver(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["addr"],
user=web_pool["user"],
password=web_pool["pass"],
)
@dataclass
class PoolGroup(MinerConfigValue):
pools: list[Pool] = Field(default_factory=list)
pools: list[Pool] = field(default_factory=list)
quota: int = 1
name: str | None = None
name: str = None
def __post_init__(self):
if self.name is None:
@@ -261,40 +190,18 @@ class PoolGroup(MinerConfigValue):
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
) # generate random pool group name in case it isn't set
def as_am_modern(self, user_suffix: str | None = None) -> list:
def as_am_modern(self, user_suffix: str = None) -> list:
pools = []
idx = 0
while idx < 3:
if len(self.pools) > idx:
pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix))
else:
pools.append(Pool(url="", user="", password="").as_am_modern())
pools.append(Pool("", "", "").as_am_modern())
idx += 1
return pools
def as_hiveon_modern(self, user_suffix: str | None = None) -> list:
pools = []
idx = 0
while idx < 3:
if len(self.pools) > idx:
pools.append(self.pools[idx].as_hiveon_modern(user_suffix=user_suffix))
else:
pools.append(Pool(url="", user="", password="").as_hiveon_modern())
idx += 1
return pools
def as_elphapex(self, user_suffix: str | None = None) -> list:
pools = []
idx = 0
while idx < 3:
if len(self.pools) > idx:
pools.append(self.pools[idx].as_elphapex(user_suffix=user_suffix))
else:
pools.append(Pool(url="", user="", password="").as_elphapex())
idx += 1
return pools
def as_wm(self, user_suffix: str | None = None) -> dict:
def as_wm(self, user_suffix: str = None) -> dict:
pools = {}
idx = 0
while idx < 3:
@@ -303,14 +210,11 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
)
else:
pools.update(**Pool(url="", user="", password="").as_wm(idx=idx + 1))
pools.update(**Pool("", "", "").as_wm(idx=idx + 1))
idx += 1
return pools
def as_btminer_v3(self, user_suffix: str | None = None) -> list:
return [pool.as_btminer_v3(user_suffix) for pool in self.pools[:3]]
def as_am_old(self, user_suffix: str | None = None) -> dict:
def as_am_old(self, user_suffix: str = None) -> dict:
pools = {}
idx = 0
while idx < 3:
@@ -319,21 +223,19 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
)
else:
pools.update(
**Pool(url="", user="", password="").as_am_old(idx=idx + 1)
)
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1))
idx += 1
return pools
def as_goldshell(self, user_suffix: str | None = None) -> list:
def as_goldshell(self, user_suffix: str = None) -> list:
return [pool.as_goldshell(user_suffix) for pool in self.pools]
def as_avalon(self, user_suffix: str | None = None) -> str:
def as_avalon(self, user_suffix: str = None) -> str:
if len(self.pools) > 0:
return self.pools[0].as_avalon(user_suffix=user_suffix)
return Pool(url="", user="", password="").as_avalon()
return Pool("", "", "").as_avalon()
def as_inno(self, user_suffix: str | None = None) -> dict:
def as_inno(self, user_suffix: str = None) -> dict:
pools = {}
idx = 0
while idx < 3:
@@ -342,11 +244,11 @@ class PoolGroup(MinerConfigValue):
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
)
else:
pools.update(**Pool(url="", user="", password="").as_inno(idx=idx + 1))
pools.update(**Pool("", "", "").as_inno(idx=idx + 1))
idx += 1
return pools
def as_bosminer(self, user_suffix: str | None = None) -> dict:
def as_bosminer(self, user_suffix: str = None) -> dict:
if len(self.pools) > 0:
conf = {
"name": self.name,
@@ -359,28 +261,12 @@ class PoolGroup(MinerConfigValue):
return conf
return {"name": "Group", "pool": []}
def as_auradine(self, user_suffix: str | None = None) -> list:
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 = None) -> list:
def as_epic(self, user_suffix: str = None) -> dict:
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
def as_mara(self, user_suffix: str | None = None) -> list:
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
def as_espminer(self, user_suffix: str | None = None) -> dict:
return self.pools[0].as_espminer(user_suffix=user_suffix)
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
return PoolGroupConfiguration(
name=self.name,
quota=Quota(value=self.quota),
pools=[p.as_boser() for p in self.pools],
)
def as_vnish(self, user_suffix: str | None = None) -> dict:
return {"pools": [p.as_vnish(user_suffix=user_suffix) for p in self.pools]}
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
cls_conf = {}
@@ -399,13 +285,6 @@ class PoolGroup(MinerConfigValue):
pools.append(Pool.from_api(pool))
return cls(pools=pools)
@classmethod
def from_btminer_v3(cls, api_pool_list: list) -> "PoolGroup":
pools = []
for pool in api_pool_list:
pools.append(Pool.from_btminer_v3(pool))
return cls(pools=pools)
@classmethod
def from_epic(cls, api_pool_list: list) -> "PoolGroup":
pools = []
@@ -420,43 +299,27 @@ class PoolGroup(MinerConfigValue):
pools.append(Pool.from_am_modern(pool))
return cls(pools=pools)
@classmethod
def from_hiveon_modern(cls, web_pool_list: list) -> "PoolGroup":
pools = []
for pool in web_pool_list:
pools.append(Pool.from_hiveon_modern(pool))
return cls(pools=pools)
@classmethod
def from_elphapex(cls, web_pool_list: list) -> "PoolGroup":
pools = []
for pool in web_pool_list:
pools.append(Pool.from_elphapex(pool))
return cls(pools=pools)
@classmethod
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
return cls([Pool.from_goldshell(p) for p in web_pools])
@classmethod
def from_inno(cls, web_pools: list) -> "PoolGroup":
return cls(pools=[Pool.from_inno(p) for p in web_pools])
return cls([Pool.from_inno(p) for p in web_pools])
@classmethod
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
if toml_group_conf.get("pool") is not None:
return cls(
name=toml_group_conf["name"],
quota=toml_group_conf.get("quota", 1),
quota=toml_group_conf.get("quota"),
pools=[Pool.from_bosminer(p) for p in toml_group_conf["pool"]],
)
return cls()
@classmethod
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
return cls(
pools=[Pool.from_vnish(p) for p in web_settings_pools if p["url"] != ""]
)
return cls([Pool.from_vnish(p) for p in web_settings_pools])
@classmethod
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
@@ -473,26 +336,10 @@ class PoolGroup(MinerConfigValue):
except LookupError:
return cls()
@classmethod
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
@classmethod
def from_espminer(cls, web_system_info: dict) -> "PoolGroup":
return cls(pools=[Pool.from_espminer(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
return cls(
pools=[
Pool.from_iceriver(web_pool)
for web_pool in web_userpanel["data"]["pools"]
]
)
@dataclass
class PoolConfig(MinerConfigValue):
groups: List[PoolGroup] = Field(default_factory=list)
groups: List[PoolGroup] = field(default_factory=list)
@classmethod
def default(cls) -> "PoolConfig":
@@ -514,67 +361,47 @@ class PoolConfig(MinerConfigValue):
group_pools.append(pool)
return cls(groups=[PoolGroup(pools=group_pools)])
def as_am_modern(self, user_suffix: str | None = None) -> dict:
def as_am_modern(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_am_modern()}
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_hiveon_modern(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_hiveon_modern()}
def as_elphapex(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_elphapex()}
def as_wm(self, user_suffix: str | None = None) -> dict:
def as_wm(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_wm()}
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_btminer_v3(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_btminer_v3()}
def as_am_old(self, user_suffix: str | None = None) -> dict:
def as_am_old(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return self.groups[0].as_am_old(user_suffix=user_suffix)
return PoolGroup().as_am_old()
def as_goldshell(self, user_suffix: str | None = None) -> dict:
def as_goldshell(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_goldshell(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_goldshell()}
def as_avalon(self, user_suffix: str | None = None) -> dict:
def as_avalon(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_avalon()}
def as_inno(self, user_suffix: str | None = None) -> dict:
def as_inno(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return self.groups[0].as_inno(user_suffix=user_suffix)
return PoolGroup().as_inno()
def as_bosminer(self, user_suffix: str | None = None) -> dict:
def as_bosminer(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {
"group": [g.as_bosminer(user_suffix=user_suffix) for g in self.groups]
}
return {"group": [PoolGroup().as_bosminer()]}
def as_boser(self, user_suffix: str | None = None) -> dict:
return {
"set_pool_groups": SetPoolGroupsRequest(
save_action=SaveAction.SAVE_AND_APPLY,
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
)
}
def as_boser(self, user_suffix: str = None) -> dict:
return {}
def as_auradine(self, user_suffix: str | None = None) -> dict:
def as_auradine(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {
"updatepools": {
@@ -583,7 +410,7 @@ class PoolConfig(MinerConfigValue):
}
return {"updatepools": {"pools": PoolGroup().as_auradine()}}
def as_epic(self, user_suffix: str | None = None) -> dict:
def as_epic(self, user_suffix: str = None) -> dict:
if len(self.groups) > 0:
return {
"pools": {
@@ -600,20 +427,6 @@ class PoolConfig(MinerConfigValue):
}
}
def as_mara(self, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0:
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
return {"pools": []}
def as_espminer(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_espminer(user_suffix=user_suffix)
def as_luxos(self, user_suffix: str | None = None) -> dict:
return {}
def as_vnish(self, user_suffix: str | None = None) -> dict:
return self.groups[0].as_vnish(user_suffix=user_suffix)
@classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig":
try:
@@ -622,76 +435,38 @@ class PoolConfig(MinerConfigValue):
return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
return cls(groups=[PoolGroup.from_api(pool_data)])
@classmethod
def from_btminer_v3(cls, rpc_pools: dict) -> "PoolConfig":
try:
pool_data = rpc_pools["pools"]
except KeyError:
return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["id"]))
return cls(groups=[PoolGroup.from_btminer_v3(pool_data)])
return cls([PoolGroup.from_api(pool_data)])
@classmethod
def from_epic(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["StratumConfigs"]
return cls(groups=[PoolGroup.from_epic(pool_data)])
return cls([PoolGroup.from_epic(pool_data)])
@classmethod
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
try:
pool_data = web_conf["pools"]
except KeyError:
return cls(groups=[])
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
@classmethod
def from_hiveon_modern(cls, web_conf: dict) -> "PoolConfig":
try:
pool_data = web_conf["pools"]
except KeyError:
return cls(groups=[])
return cls(groups=[PoolGroup.from_hiveon_modern(pool_data)])
@classmethod
def from_elphapex(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["pools"]
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
return cls([PoolGroup.from_am_modern(pool_data)])
@classmethod
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
@classmethod
def from_goldshell_byte(cls, web_pools: list) -> "PoolConfig":
return cls(
groups=[
PoolGroup.from_goldshell(g["pools"])
for g in web_pools
if len(g["pools"]) > 0
]
)
return cls([PoolGroup.from_goldshell(web_pools)])
@classmethod
def from_inno(cls, web_pools: list) -> "PoolConfig":
return cls(groups=[PoolGroup.from_inno(web_pools)])
return cls([PoolGroup.from_inno(web_pools)])
@classmethod
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
if toml_conf.get("group") is None:
return cls()
return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
@classmethod
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
try:
return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])])
except LookupError:
return cls()
@@ -706,32 +481,3 @@ class PoolConfig(MinerConfigValue):
)
except LookupError:
return cls()
@classmethod
def from_mara(cls, web_config: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
@classmethod
def from_espminer(cls, web_system_info: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_espminer(web_system_info)])
@classmethod
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_iceriver(web_userpanel)])
@classmethod
def from_luxos(cls, rpc_groups: dict, rpc_pools: dict) -> "PoolConfig":
return cls(
groups=[
PoolGroup(
pools=[
Pool.from_luxos(pool)
for pool in rpc_pools["POOLS"]
if pool["GROUP"] == group["GROUP"]
],
name=group["Name"],
quota=group["Quota"],
)
for group in rpc_groups["GROUPS"]
]
)

View File

@@ -0,0 +1,221 @@
# ------------------------------------------------------------------------------
# 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 __future__ import annotations
from dataclasses import dataclass, field
from pyasic.config.base import MinerConfigOption, MinerConfigValue
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
DpsPowerTarget,
DpsTarget,
Power,
SetDpsRequest,
)
@dataclass
class PowerScalingShutdownEnabled(MinerConfigValue):
mode: str = field(init=False, default="enabled")
duration: int = None
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownEnabled":
return cls(duration=dict_conf.get("duration"))
def as_bosminer(self) -> dict:
cfg = {"shutdown_enabled": True}
if self.duration is not None:
cfg["shutdown_duration"] = self.duration
return cfg
def as_boser(self) -> dict:
return {"enable_shutdown": True, "shutdown_duration": self.duration}
@dataclass
class PowerScalingShutdownDisabled(MinerConfigValue):
mode: str = field(init=False, default="disabled")
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownDisabled":
return cls()
def as_bosminer(self) -> dict:
return {"shutdown_enabled": False}
def as_boser(self) -> dict:
return {"enable_shutdown ": False}
class PowerScalingShutdown(MinerConfigOption):
enabled = PowerScalingShutdownEnabled
disabled = PowerScalingShutdownDisabled
@classmethod
def from_dict(cls, dict_conf: dict | None):
if dict_conf is None:
return cls.default()
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
clsattr = getattr(cls, mode)
if clsattr is not None:
return clsattr().from_dict(dict_conf)
@classmethod
def from_bosminer(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdown_enabled")
if sd_enabled is not None:
if sd_enabled:
return cls.enabled(power_scaling_conf.get("shutdown_duration"))
else:
return cls.disabled()
return None
@classmethod
def from_boser(cls, power_scaling_conf: dict):
sd_enabled = power_scaling_conf.get("shutdownEnabled")
if sd_enabled is not None:
if sd_enabled:
try:
return cls.enabled(power_scaling_conf["shutdownDuration"]["hours"])
except KeyError:
return cls.enabled()
else:
return cls.disabled()
return None
@dataclass
class PowerScalingEnabled(MinerConfigValue):
mode: str = field(init=False, default="enabled")
power_step: int = None
minimum_power: int = None
shutdown_enabled: PowerScalingShutdownEnabled | PowerScalingShutdownDisabled = None
@classmethod
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
power_step = power_scaling_conf.get("power_step")
min_power = power_scaling_conf.get("min_psu_power_limit")
if min_power is None:
min_power = power_scaling_conf.get("min_power_target")
sd_mode = PowerScalingShutdown.from_bosminer(power_scaling_conf)
return cls(
power_step=power_step, minimum_power=min_power, shutdown_enabled=sd_mode
)
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingEnabled":
cls_conf = {
"power_step": dict_conf.get("power_step"),
"minimum_power": dict_conf.get("minimum_power"),
}
shutdown_enabled = dict_conf.get("shutdown_enabled")
if shutdown_enabled is not None:
cls_conf["shutdown_enabled"] = PowerScalingShutdown.from_dict(
shutdown_enabled
)
return cls(**cls_conf)
def as_bosminer(self) -> dict:
cfg = {"enabled": True}
if self.power_step is not None:
cfg["power_step"] = self.power_step
if self.minimum_power is not None:
cfg["min_power_target"] = self.minimum_power
if self.shutdown_enabled is not None:
cfg = {**cfg, **self.shutdown_enabled.as_bosminer()}
return {"performance_scaling": cfg}
def as_boser(self) -> dict:
return {
"set_dps": SetDpsRequest(
enable=True,
**self.shutdown_enabled.as_boser(),
target=DpsTarget(
power_target=DpsPowerTarget(
power_step=Power(self.power_step),
min_power_target=Power(self.minimum_power),
)
),
),
}
@dataclass
class PowerScalingDisabled(MinerConfigValue):
mode: str = field(init=False, default="disabled")
class PowerScalingConfig(MinerConfigOption):
enabled = PowerScalingEnabled
disabled = PowerScalingDisabled
@classmethod
def default(cls):
return cls.disabled()
@classmethod
def from_dict(cls, dict_conf: dict | None):
if dict_conf is None:
return cls.default()
mode = dict_conf.get("mode")
if mode is None:
return cls.default()
clsattr = getattr(cls, mode)
if clsattr is not None:
return clsattr().from_dict(dict_conf)
@classmethod
def from_bosminer(cls, toml_conf: dict):
power_scaling = toml_conf.get("power_scaling")
if power_scaling is None:
power_scaling = toml_conf.get("performance_scaling")
if power_scaling is not None:
enabled = power_scaling.get("enabled")
if enabled is not None:
if enabled:
return cls.enabled().from_bosminer(power_scaling)
else:
return cls.disabled()
return cls.default()
@classmethod
def from_boser(cls, grpc_miner_conf: dict):
try:
dps_conf = grpc_miner_conf["dps"]
if not dps_conf.get("enabled", False):
return cls.disabled()
except LookupError:
return cls.default()
conf = {"shutdown_enabled": PowerScalingShutdown.from_boser(dps_conf)}
if dps_conf.get("minPowerTarget") is not None:
conf["minimum_power"] = dps_conf["minPowerTarget"]["watt"]
if dps_conf.get("powerStep") is not None:
conf["power_step"] = dps_conf["powerStep"]["watt"]
return cls.enabled(**conf)

View File

@@ -15,13 +15,16 @@
# ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass
from pyasic.config.base import MinerConfigValue
@dataclass
class TemperatureConfig(MinerConfigValue):
target: int | None = None
hot: int | None = None
danger: int | None = None
target: int = None
hot: int = None
danger: int = None
@classmethod
def default(cls):
@@ -35,8 +38,6 @@ class TemperatureConfig(MinerConfigValue):
temp_cfg["hot_temp"] = self.hot
if self.danger is not None:
temp_cfg["dangerous_temp"] = self.danger
if len(temp_cfg) == 0:
return {}
return {"temp_control": temp_cfg}
def as_epic(self) -> dict:
@@ -46,17 +47,9 @@ class TemperatureConfig(MinerConfigValue):
else:
temps_config["fans"]["Auto"]["Target Temperature"] = 60
if self.danger is not None:
temps_config["temps"]["critical"] = self.danger
if self.hot is not None:
temps_config["temps"]["shutdown"] = self.hot
temps_config["temps"]["shutdown"] = self.danger
return temps_config
def as_luxos(self) -> dict:
return {"tempctrlset": [self.target or "", self.hot or "", self.danger or ""]}
def as_vnish(self) -> dict:
return {"misc": {"restart_temp": self.danger}}
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
return cls(
@@ -74,38 +67,26 @@ class TemperatureConfig(MinerConfigValue):
hot=temp_control.get("hot_temp"),
danger=temp_control.get("dangerous_temp"),
)
return cls()
@classmethod
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
try:
dangerous_temp = web_conf["Misc"]["Critical Temp"]
dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError:
dangerous_temp = None
try:
hot_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError:
hot_temp = None
# Need to do this in two blocks to avoid KeyError if one is missing
try:
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
except KeyError:
target_temp = None
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
return cls(target=target_temp, danger=dangerous_temp)
@classmethod
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
try:
dangerous_temp = web_settings["misc"]["restart_temp"]
except KeyError:
dangerous_temp = None
try:
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
return cls(
target=web_settings["miner"]["cooling"]["mode"]["param"],
danger=dangerous_temp,
)
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
except KeyError:
pass
return cls()
@@ -140,16 +121,3 @@ class TemperatureConfig(MinerConfigValue):
return cls(**conf)
return cls.default()
@classmethod
def from_luxos(cls, rpc_tempctrl: dict) -> "TemperatureConfig":
try:
tempctrl_config = rpc_tempctrl["TEMPCTRL"][0]
return cls(
target=tempctrl_config.get("Target"),
hot=tempctrl_config.get("Hot"),
danger=tempctrl_config.get("Dangerous"),
)
except LookupError:
pass
return cls.default()

View File

@@ -13,26 +13,24 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import copy
import time
from datetime import datetime, timezone
from typing import Any
from pydantic import BaseModel, Field, computed_field
import copy
import json
import time
from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone
from typing import Any, List, Union
from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune
from pyasic.data.pools import PoolMetrics, Scheme
from pyasic.device.algorithm.hashrate import AlgoHashRateType
from .boards import HashBoard
from .device import DeviceInfo
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
from .error_codes.base import BaseMinerError
from .fans import Fan
class MinerData(BaseModel):
@dataclass
class MinerData:
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
Attributes:
@@ -40,25 +38,20 @@ class MinerData(BaseModel):
datetime: The time and date this data was generated.
uptime: The uptime of the miner in seconds.
mac: The MAC address of the miner as a str.
device_info: Info about the device, such as model, make, and firmware.
model: The model of the miner as a str.
make: The make of the miner as a str.
firmware: The firmware on the miner as a str.
algo: The mining algorithm of the miner as a str.
api_ver: The current api version on the miner as a str.
fw_ver: The current firmware version on the miner as a str.
hostname: The network hostname of the miner as a str.
hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically.
_hashrate: Backup for hashrate found via API instead of hashboards.
expected_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
sticker_hashrate: The factory sticker hashrate of the miner as a float.
hashboards: A list of [`HashBoard`][pyasic.data.HashBoard]s on the miner with their statistics.
temperature_avg: The average temperature across the boards. Calculated automatically.
env_temp: The environment temps as a float.
wattage: Current power draw of the miner as an int.
voltage: Current output voltage of the PSU as an float.
wattage_limit: Power limit of the miner as an int.
fans: A list of fans on the miner with their speeds.
expected_fans: The number of fans expected on a miner.
fan_psu: The speed of the PSU on the fan if the miner collects it.
total_chips: The total number of chips on all boards. Calculated automatically.
expected_chips: The expected number of chips in the miner as an int.
@@ -70,70 +63,59 @@ class MinerData(BaseModel):
errors: A list of errors on the miner.
fault_light: Whether the fault light is on as a boolean.
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
efficiency_fract: Same as efficiency, but is not rounded to integer. Calculated automatically.
is_mining: Whether the miner is mining.
pools: A list of PoolMetrics instances, each representing metrics for a pool.
"""
# general
ip: str
raw_datetime: datetime = Field(
exclude=True, default_factory=datetime.now(timezone.utc).astimezone, repr=False
)
# about
device_info: DeviceInfo | None = None
mac: str | None = None
api_ver: str | None = None
fw_ver: str | None = None
hostname: str | None = None
# hashrate
raw_hashrate: AlgoHashRateType = Field(exclude=True, default=None, repr=False)
# sticker
sticker_hashrate: AlgoHashRateType | None = None
# expected
expected_hashrate: AlgoHashRateType | None = None
expected_hashboards: int | None = None
expected_chips: int | None = None
expected_fans: int | None = None
# temperature
env_temp: float | None = None
# power
wattage: int | None = None
voltage: float | None = None
raw_wattage_limit: int | None = Field(exclude=True, default=None, repr=False)
# fans
fans: list[Fan] = Field(default_factory=list)
fan_psu: int | None = None
# boards
hashboards: list[HashBoard] = Field(default_factory=list)
# config
config: MinerConfig | None = None
fault_light: bool | None = None
# errors
errors: list[BaseMinerError] = Field(default_factory=list)
# mining state
datetime: datetime = None
uptime: int = None
mac: str = None
model: str = None
make: str = None
api_ver: str = None
fw_ver: str = None
hostname: str = None
hashrate: float = field(init=False)
_hashrate: float = field(repr=False, default=None)
expected_hashrate: float = None
hashboards: List[HashBoard] = field(default_factory=list)
expected_hashboards: int = None
temperature_avg: int = field(init=False)
env_temp: float = None
wattage: int = None
wattage_limit: int = field(init=False)
_wattage_limit: int = field(repr=False, default=None)
fans: List[Fan] = field(default_factory=list)
fan_psu: int = None
total_chips: int = field(init=False)
expected_chips: int = None
percent_expected_chips: float = field(init=False)
percent_expected_hashrate: float = field(init=False)
percent_expected_wattage: float = field(init=False)
nominal: bool = field(init=False)
config: MinerConfig = None
errors: List[
Union[
WhatsminerError,
BraiinsOSError,
X19Error,
InnosiliconError,
]
] = field(default_factory=list)
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
is_mining: bool = True
uptime: int | None = None
# pools
pools: list[PoolMetrics] = Field(default_factory=list)
@classmethod
def fields(cls) -> set:
all_fields = set(cls.model_fields.keys())
all_fields.update(set(cls.model_computed_fields.keys()))
return all_fields
def fields(cls):
return [f.name for f in fields(cls) if not f.name.startswith("_")]
@staticmethod
def dict_factory(x):
return {k: v for (k, v) in x if not k.startswith("_")}
def __post_init__(self):
self.datetime = datetime.now(timezone.utc).astimezone()
def get(self, __key: str, default: Any = None):
try:
@@ -161,19 +143,19 @@ class MinerData(BaseModel):
def __floordiv__(self, other):
cp = copy.deepcopy(self)
for key in self.fields():
for key in self:
item = getattr(self, key)
if isinstance(item, int):
setattr(cp, key, item // other)
if isinstance(item, float):
setattr(cp, key, item / other)
setattr(cp, key, round(item / other, 2))
return cp
def __add__(self, other):
if not isinstance(other, MinerData):
raise TypeError("Cannot add MinerData to non MinerData type.")
cp = copy.deepcopy(self)
for key in self.fields():
for key in self:
item = getattr(self, key)
other_item = getattr(other, key)
if item is None:
@@ -193,37 +175,34 @@ class MinerData(BaseModel):
setattr(cp, key, item & other_item)
return cp
@computed_field # type: ignore[misc]
@property
def hashrate(self) -> AlgoHashRateType | None:
def hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) > 0:
hr_data = []
for item in self.hashboards:
if item.hashrate is not None:
hr_data.append(item.hashrate)
if len(hr_data) > 0:
return sum(hr_data, start=self.device_info.algo.hashrate(rate=0))
return self.raw_hashrate
return round(sum(hr_data), 2)
return self._hashrate
@hashrate.setter
def hashrate(self, val):
self.raw_hashrate = val
self._hashrate = val
@computed_field # type: ignore[misc]
@property
def wattage_limit(self) -> int | None:
def wattage_limit(self): # noqa - Skip PyCharm inspection
if self.config is not None:
if isinstance(self.config.mining_mode, MiningModePowerTune):
return self.config.mining_mode.power
return self.raw_wattage_limit
return self._wattage_limit
@wattage_limit.setter
def wattage_limit(self, val: int):
self.raw_wattage_limit = val
self._wattage_limit = val
@computed_field # type: ignore[misc]
@property
def total_chips(self) -> int | None:
def total_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) > 0:
chip_data = []
for item in self.hashboards:
@@ -233,45 +212,58 @@ class MinerData(BaseModel):
return sum(chip_data)
return None
@computed_field # type: ignore[misc]
@total_chips.setter
def total_chips(self, val):
pass
@property
def nominal(self) -> bool | None:
def nominal(self): # noqa - Skip PyCharm inspection
if self.total_chips is None or self.expected_chips is None:
return None
return self.expected_chips == self.total_chips
@computed_field # type: ignore[misc]
@nominal.setter
def nominal(self, val):
pass
@property
def percent_expected_chips(self) -> int | None:
def percent_expected_chips(self): # noqa - Skip PyCharm inspection
if self.total_chips is None or self.expected_chips is None:
return None
if self.total_chips == 0 or self.expected_chips == 0:
return 0
return round((self.total_chips / self.expected_chips) * 100)
@computed_field # type: ignore[misc]
@percent_expected_chips.setter
def percent_expected_chips(self, val):
pass
@property
def percent_expected_hashrate(self) -> int | None:
def percent_expected_hashrate(self): # noqa - Skip PyCharm inspection
if self.hashrate is None or self.expected_hashrate is None:
return None
try:
return round((self.hashrate / self.expected_hashrate) * 100)
except ZeroDivisionError:
if self.hashrate == 0 or self.expected_hashrate == 0:
return 0
return round((self.hashrate / self.expected_hashrate) * 100)
@percent_expected_hashrate.setter
def percent_expected_hashrate(self, val):
pass
@computed_field # type: ignore[misc]
@property
def percent_expected_wattage(self) -> int | None:
def percent_expected_wattage(self): # noqa - Skip PyCharm inspection
if self.wattage_limit is None or self.wattage is None:
return None
try:
return round((self.wattage / self.wattage_limit) * 100)
except ZeroDivisionError:
if self.wattage_limit == 0 or self.wattage == 0:
return 0
return round((self.wattage / self.wattage_limit) * 100)
@percent_expected_wattage.setter
def percent_expected_wattage(self, val):
pass
@computed_field # type: ignore[misc]
@property
def temperature_avg(self) -> int | None:
def temperature_avg(self): # noqa - Skip PyCharm inspection
total_temp = 0
temp_count = 0
for hb in self.hashboards:
@@ -282,67 +274,24 @@ class MinerData(BaseModel):
return None
return round(total_temp / temp_count)
@computed_field # type: ignore[misc]
@property
def efficiency(self) -> int | None:
efficiency = self._efficiency(0)
if efficiency is None:
return None
else:
return int(efficiency)
@temperature_avg.setter
def temperature_avg(self, val):
pass
@computed_field # type: ignore[misc]
@property
def efficiency_fract(self) -> float | None:
return self._efficiency(2)
def _efficiency(self, ndigits: int) -> float | None:
def efficiency(self): # noqa - Skip PyCharm inspection
if self.hashrate is None or self.wattage is None:
return None
try:
return round(self.wattage / float(self.hashrate), ndigits)
except ZeroDivisionError:
return 0.0
if self.hashrate == 0 or self.wattage == 0:
return 0
return round(self.wattage / self.hashrate)
@computed_field # type: ignore[misc]
@property
def datetime(self) -> str:
return self.raw_datetime.isoformat()
@computed_field # type: ignore[misc]
@property
def timestamp(self) -> int:
return int(time.mktime(self.raw_datetime.timetuple()))
@computed_field # type: ignore[misc]
@property
def make(self) -> str | None:
if self.device_info.make is not None:
return str(self.device_info.make)
@computed_field # type: ignore[misc]
@property
def model(self) -> str | None:
if self.device_info.model is not None:
return str(self.device_info.model)
@computed_field # type: ignore[misc]
@property
def firmware(self) -> str | None:
if self.device_info.firmware is not None:
return str(self.device_info.firmware)
@computed_field # type: ignore[misc]
@property
def algo(self) -> str | None:
if self.device_info.algo is not None:
return str(self.device_info.algo)
def keys(self) -> list:
return list(self.model_fields.keys())
@efficiency.setter
def efficiency(self, val):
pass
def asdict(self) -> dict:
return self.model_dump()
return asdict(self, dict_factory=self.dict_factory)
def as_dict(self) -> dict:
"""Get this dataclass as a dictionary.
@@ -358,7 +307,9 @@ class MinerData(BaseModel):
Returns:
A JSON version of this class.
"""
return self.model_dump_json()
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data)
def as_csv(self) -> str:
"""Get this dataclass as CSV.
@@ -367,6 +318,7 @@ class MinerData(BaseModel):
A CSV version of this class with no headers.
"""
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
errs = []
for error in data["errors"]:
errs.append(error["error_message"])
@@ -374,9 +326,7 @@ class MinerData(BaseModel):
data_list = [str(data[item]) for item in data]
return ",".join(data_list)
def as_influxdb(
self, measurement_name: str = "miner_data", level_delimiter: str = "."
) -> str:
def as_influxdb(self, measurement_name: str = "miner_data") -> str:
"""Get this dataclass as [influxdb line protocol](https://docs.influxdata.com/influxdb/v2.4/reference/syntax/line-protocol/).
Parameters:
@@ -385,127 +335,54 @@ class MinerData(BaseModel):
Returns:
A influxdb line protocol version of this class.
"""
def serialize_int(key: str, value: int) -> str:
return f"{key}={value}"
def serialize_float(key: str, value: float) -> str:
return f"{key}={value}"
def serialize_str(key: str, value: str) -> str:
return f'{key}="{value}"'
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
return f"{key}={round(float(value), 2)}"
def serialize_list(key: str, value: list[Any]) -> str | None:
if len(value) == 0:
return None
list_field_data = []
for idx, list_field_val in enumerate(value):
item_serialization_func = serialization_map.get(
type(list_field_val), lambda _k, _v: None
)
item_serialized = item_serialization_func(
f"{key}{level_delimiter}{idx}", list_field_val
)
if item_serialized is not None:
list_field_data.append(item_serialized)
continue
for dt in serialization_map_instance:
if item_serialized is None:
if isinstance(list_field_val, dt):
item_serialized = serialization_map_instance[dt](
f"{key}{level_delimiter}{idx}", list_field_val
)
if item_serialized is not None:
list_field_data.append(item_serialized)
return ",".join(list_field_data)
def serialize_miner_error(key: str, value: BaseMinerError):
return value.as_influxdb(key, level_delimiter=level_delimiter)
def serialize_fan(key: str, value: Fan) -> str:
return f"{key}{level_delimiter}speed={value.speed}"
def serialize_hashboard(key: str, value: HashBoard) -> str:
return value.as_influxdb(key, level_delimiter=level_delimiter)
def serialize_bool(key: str, value: bool):
return f"{key}={str(value).lower()}"
def serialize_pool_metrics(key: str, value: PoolMetrics):
return value.as_influxdb(key, level_delimiter=level_delimiter)
include = [
"uptime",
"expected_hashrate",
"hashrate",
"hashboards",
"temperature_avg",
"env_temp",
"wattage",
"wattage_limit",
"voltage",
"fans",
"expected_fans",
"fan_psu",
"total_chips",
"expected_chips",
"efficiency",
"fault_light",
"is_mining",
"errors",
"pools",
]
serialization_map_instance = {
AlgoHashRateType: serialize_algo_hash_rate,
BaseMinerError: serialize_miner_error,
}
serialization_map = {
int: serialize_int,
float: serialize_float,
str: serialize_str,
bool: serialize_bool,
list: serialize_list,
Fan: serialize_fan,
HashBoard: serialize_hashboard,
PoolMetrics: serialize_pool_metrics,
}
tag_data = [
measurement_name,
f"ip={str(self.ip)}",
f"mac={str(self.mac)}",
f"make={str(self.make)}",
f"model={str(self.model)}",
f"firmware={str(self.firmware)}",
f"algo={str(self.algo)}",
]
tag_data = [measurement_name]
field_data = []
for field in include:
field_val = getattr(self, field)
serialization_func = serialization_map.get(
type(field_val), lambda _k, _v: None
)
serialized = serialization_func(field, field_val)
if serialized is not None:
field_data.append(serialized)
tags = ["ip", "mac", "model", "hostname"]
for attribute in self:
if attribute in tags:
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
tag_data.append(f"{attribute}={escaped_data}")
continue
for datatype in serialization_map_instance:
if serialized is None:
if isinstance(field_val, datatype):
serialized = serialization_map_instance[datatype](
field, field_val
)
if serialized is not None:
field_data.append(serialized)
elif str(attribute).startswith("_"):
continue
elif isinstance(self[attribute], str):
field_data.append(f'{attribute}="{self[attribute]}"')
continue
elif isinstance(self[attribute], bool):
field_data.append(f"{attribute}={str(self[attribute]).lower()}")
continue
elif isinstance(self[attribute], int):
field_data.append(f"{attribute}={self[attribute]}")
continue
elif isinstance(self[attribute], float):
field_data.append(f"{attribute}={self[attribute]}")
continue
elif attribute == "errors":
for idx, item in enumerate(self[attribute]):
field_data.append(f'error_{idx+1}="{item.error_message}"')
elif attribute == "hashboards":
for idx, item in enumerate(self[attribute]):
field_data.append(
f"hashboard_{idx+1}_hashrate={item.get('hashrate', 0.0)}"
)
field_data.append(
f"hashboard_{idx+1}_temperature={item.get('temp', 0)}"
)
field_data.append(
f"hashboard_{idx+1}_chip_temperature={item.get('chip_temp', 0)}"
)
field_data.append(f"hashboard_{idx+1}_chips={item.get('chips', 0)}")
field_data.append(
f"hashboard_{idx+1}_expected_chips={item.get('expected_chips', 0)}"
)
elif attribute == "fans":
for idx, item in enumerate(self[attribute]):
if item.speed is not None:
field_data.append(f"fan_{idx+1}={item.speed}")
tags_str = ",".join(tag_data).replace(" ", "\\ ")
field_str = ",".join(field_data).replace(" ", "\\ ")
timestamp = str(self.timestamp * 10**9)
tags_str = ",".join(tag_data)
field_str = ",".join(field_data)
timestamp = str(int(time.mktime(self.datetime.timetuple()) * 1e9))
return " ".join([tags_str, field_str, timestamp])

View File

@@ -13,53 +13,34 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from pydantic import BaseModel
from pyasic.device.algorithm.hashrate import AlgoHashRateType
class HashBoard(BaseModel):
@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.
inlet_temp: Inlet temperature for hydro asics as an int
outlet_temp: Outlet temperature for hydro asics as an int
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.
tuned: Whether the board is tuned as a bool.
active: Whether the board is currently tuning as a bool.
voltage: Current input voltage of the board as a float.
"""
slot: int = 0
hashrate: AlgoHashRateType | None = None
inlet_temp: float | None = None
outlet_temp: float | None = None
temp: float | None = None
chip_temp: float | None = None
chips: int | None = None
expected_chips: int | None = None
serial_number: str | None = None
hashrate: float = None
temp: int = None
chip_temp: int = None
chips: int = None
expected_chips: int = None
serial_number: str = None
missing: bool = True
tuned: bool | None = None
active: bool | None = None
voltage: float | None = None
@classmethod
def fields(cls) -> set:
all_fields = set(cls.model_fields.keys())
all_fields.update(set(cls.model_computed_fields.keys()))
return all_fields
def get(self, __key: str, default: Any = None):
try:
@@ -75,62 +56,3 @@ class HashBoard(BaseModel):
return getattr(self, item)
except AttributeError:
raise KeyError(f"{item}")
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
def serialize_int(key: str, value: int) -> str:
return f"{key}={value}"
def serialize_float(key: str, value: float) -> str:
return f"{key}={value}"
def serialize_str(key: str, value: str) -> str:
return f'{key}="{value}"'
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
return f"{key}={round(float(value), 2)}"
def serialize_bool(key: str, value: bool):
return f"{key}={str(value).lower()}"
serialization_map_instance = {
AlgoHashRateType: serialize_algo_hash_rate,
}
serialization_map = {
int: serialize_int,
float: serialize_float,
str: serialize_str,
bool: serialize_bool,
}
include = [
"hashrate",
"temp",
"chip_temp",
"chips",
"expected_chips",
"tuned",
"active",
"voltage",
]
field_data = []
for field in include:
field_val = getattr(self, field)
serialization_func = serialization_map.get(
type(field_val), lambda _k, _v: None
)
serialized = serialization_func(
f"{key_root}{level_delimiter}{field}", field_val
)
if serialized is not None:
field_data.append(serialized)
continue
for datatype in serialization_map_instance:
if serialized is None:
if isinstance(field_val, datatype):
serialized = serialization_map_instance[datatype](
f"{key_root}{level_delimiter}{field}", field_val
)
if serialized is not None:
field_data.append(serialized)
return ",".join(field_data)

View File

@@ -1,31 +0,0 @@
from pydantic import BaseModel, ConfigDict, field_serializer
from pyasic.device.algorithm import MinerAlgoType
from pyasic.device.firmware import MinerFirmware
from pyasic.device.makes import MinerMake
from pyasic.device.models import MinerModelType
class DeviceInfo(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
make: MinerMake | None = None
model: MinerModelType | None = None
firmware: MinerFirmware | None = None
algo: type[MinerAlgoType] | None = None
@field_serializer("make")
def serialize_make(self, make: MinerMake, _info):
return str(make)
@field_serializer("model")
def serialize_model(self, model: MinerModelType, _info):
return str(model)
@field_serializer("firmware")
def serialize_firmware(self, firmware: MinerFirmware, _info):
return str(firmware)
@field_serializer("algo")
def serialize_algo(self, algo: MinerAlgoType, _info):
return str(algo)

View File

@@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, fields
class X19Error(BaseMinerError):
@dataclass
class X19Error:
"""A Dataclass to handle error codes of X19 miners.
Attributes:
@@ -26,3 +28,10 @@ class X19Error(BaseMinerError):
error_message: str
error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self):
return asdict(self)

View File

@@ -18,15 +18,9 @@ from typing import TypeVar
from .bos import BraiinsOSError
from .innosilicon import InnosiliconError
from .vnish import VnishError
from .whatsminer import WhatsminerError
from .X19 import X19Error
MinerErrorData = TypeVar(
"MinerErrorData",
WhatsminerError,
BraiinsOSError,
X19Error,
InnosiliconError,
VnishError,
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
)

View File

@@ -1,32 +0,0 @@
from pydantic import BaseModel
class BaseMinerError(BaseModel):
@classmethod
def fields(cls):
return list(cls.model_fields.keys())
def asdict(self) -> dict:
return self.model_dump()
def as_dict(self) -> dict:
"""Get this dataclass as a dictionary.
Returns:
A dictionary version of this class.
"""
return self.asdict()
def as_influxdb(self, root_key: str, level_delimiter: str = ".") -> str:
field_data = []
if self.error_code is not None:
field_data.append(
f"{root_key}{level_delimiter}error_code={self.error_code}"
)
if self.error_message is not None:
field_data.append(
f'{root_key}{level_delimiter}error_message="{self.error_message}"'
)
return ",".join(field_data)

View File

@@ -14,10 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, fields
class BraiinsOSError(BaseMinerError):
@dataclass
class BraiinsOSError:
"""A Dataclass to handle error codes of BraiinsOS+ miners.
Attributes:
@@ -27,3 +28,10 @@ class BraiinsOSError(BaseMinerError):
error_message: str
error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self):
return asdict(self)

View File

@@ -14,13 +14,11 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from pydantic import computed_field
from pyasic.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, field, fields
class InnosiliconError(BaseMinerError):
@dataclass
class InnosiliconError:
"""A Dataclass to handle error codes of Innosilicon miners.
Attributes:
@@ -29,14 +27,25 @@ class InnosiliconError(BaseMinerError):
"""
error_code: int
error_message: str = field(init=False)
@classmethod
def fields(cls):
return fields(cls)
@computed_field # type: ignore[misc]
@property
def error_message(self) -> str: # noqa - Skip PyCharm inspection
def error_message(self): # noqa - Skip PyCharm inspection
if self.error_code in ERROR_CODES:
return ERROR_CODES[self.error_code]
return "Unknown error type."
@error_message.setter
def error_message(self, val):
pass
def asdict(self):
return asdict(self)
ERROR_CODES = {
21: "The PLUG signal of the hash board is not detected.",

View File

@@ -13,12 +13,12 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pydantic import computed_field
from pyasic.data.error_codes.base import BaseMinerError
from dataclasses import asdict, dataclass, field, fields
class WhatsminerError(BaseMinerError):
@dataclass
class WhatsminerError:
"""A Dataclass to handle error codes of Whatsminers.
Attributes:
@@ -27,10 +27,14 @@ class WhatsminerError(BaseMinerError):
"""
error_code: int
error_message: str = field(init=False)
@classmethod
def fields(cls):
return fields(cls)
@computed_field # type: ignore[misc]
@property
def error_message(self) -> str: # noqa - Skip PyCharm inspection
def error_message(self): # noqa - Skip PyCharm inspection
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
err_type = int(str(self.error_code)[:2])
err_subtype = int(str(self.error_code)[2:3])
@@ -70,6 +74,13 @@ class WhatsminerError(BaseMinerError):
except KeyError:
return "Unknown error type."
@error_message.setter
def error_message(self, val):
pass
def asdict(self):
return asdict(self)
ERROR_CODES = {
1: { # Fan error

View File

@@ -14,12 +14,12 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
from dataclasses import dataclass
from typing import Any
from pydantic import BaseModel
class Fan(BaseModel):
@dataclass
class Fan:
"""A Dataclass to standardize fan data.
Attributes:

View File

@@ -1,141 +0,0 @@
from enum import Enum
from typing import Optional
from urllib.parse import urlparse
from pydantic import BaseModel, computed_field, model_serializer
from typing_extensions import Self
class Scheme(Enum):
STRATUM_V1 = "stratum+tcp"
STRATUM_V2 = "stratum2+tcp"
STRATUM_V1_SSL = "stratum+ssl"
class PoolUrl(BaseModel):
scheme: Scheme
host: str
port: int
pubkey: Optional[str] = None
@model_serializer
def serialize(self):
return str(self)
def __str__(self) -> str:
if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
else:
return f"{self.scheme.value}://{self.host}:{self.port}"
@classmethod
def from_str(cls, url: str) -> Self | None:
parsed_url = urlparse(url)
if not parsed_url.hostname:
return None
if not parsed_url.scheme.strip() == "":
scheme = Scheme(parsed_url.scheme)
else:
scheme = Scheme.STRATUM_V1
host = parsed_url.hostname
port = parsed_url.port
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
class PoolMetrics(BaseModel):
"""A dataclass to standardize pool metrics returned from miners.
Attributes:
accepted: Number of accepted shares.
rejected: Number of rejected shares.
get_failures: Number of failures in obtaining work from the pool.
remote_failures: Number of failures communicating with the pool server.
active: Indicates if the miner is connected to the stratum server.
Alive : Indicates if a pool is alive.
url: URL of the pool.
index: Index of the pool.
user: Username for the pool.
pool_rejected_percent: Percentage of rejected shares by the pool.
pool_stale_percent: Percentage of stale shares by the pool.
"""
url: PoolUrl | None
accepted: int | None = None
rejected: int | None = None
get_failures: int | None = None
remote_failures: int | None = None
active: bool | None = None
alive: bool | None = None
index: int | None = None
user: str | None = None
@computed_field # type: ignore[misc]
@property
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of rejected shares"""
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
@computed_field # type: ignore[misc]
@property
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
"""Calculate and return the percentage of stale shares."""
return self._calculate_percentage(
self.get_failures, self.accepted + self.rejected
)
@staticmethod
def _calculate_percentage(value: int, total: int) -> float:
"""Calculate the percentage."""
if value is None or total is None:
return 0
if total == 0:
return 0
return (value / total) * 100
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
def serialize_int(key: str, value: int) -> str:
return f"{key}={value}"
def serialize_float(key: str, value: float) -> str:
return f"{key}={value}"
def serialize_str(key: str, value: str) -> str:
return f'{key}="{value}"'
def serialize_pool_url(key: str, value: str) -> str:
return f'{key}="{str(value)}"'
def serialize_bool(key: str, value: bool):
return f"{key}={str(value).lower()}"
serialization_map = {
int: serialize_int,
float: serialize_float,
str: serialize_str,
bool: serialize_bool,
PoolUrl: serialize_pool_url,
}
include = [
"url",
"accepted",
"rejected",
"active",
"alive",
"user",
]
field_data = []
for field in include:
field_val = getattr(self, field)
serialization_func = serialization_map.get(
type(field_val), lambda _k, _v: None
)
serialized = serialization_func(
f"{key_root}{level_delimiter}{field}", field_val
)
if serialized is not None:
field_data.append(serialized)
return ",".join(field_data)

View File

@@ -1,4 +0,0 @@
from .algorithm import MinerAlgo
from .firmware import MinerFirmware
from .makes import MinerMake
from .models import MinerModel

View File

@@ -1,30 +0,0 @@
from .base import MinerAlgoType
from .blake256 import Blake256Algo
from .blockflow import BlockFlowAlgo
from .eaglesong import EaglesongAlgo
from .equihash import EquihashAlgo
from .ethash import EtHashAlgo
from .handshake import HandshakeAlgo
from .hashrate import *
from .hashrate.unit import *
from .kadena import KadenaAlgo
from .kheavyhash import KHeavyHashAlgo
from .scrypt import ScryptAlgo
from .sha256 import SHA256Algo
from .x11 import X11Algo
from .zksnark import ZkSnarkAlgo
class MinerAlgo:
SHA256 = SHA256Algo
SCRYPT = ScryptAlgo
KHEAVYHASH = KHeavyHashAlgo
KADENA = KadenaAlgo
HANDSHAKE = HandshakeAlgo
X11 = X11Algo
BLAKE256 = Blake256Algo
EAGLESONG = EaglesongAlgo
ETHASH = EtHashAlgo
EQUIHASH = EquihashAlgo
BLOCKFLOW = BlockFlowAlgo
ZKSNARK = ZkSnarkAlgo

View File

@@ -1,23 +0,0 @@
from __future__ import annotations
from .hashrate.base import AlgoHashRateType, GenericHashrate
from .hashrate.unit.base import AlgoHashRateUnitType, GenericUnit
class MinerAlgoMeta(type):
name: str
def __str__(cls):
return cls.name
class MinerAlgoType(metaclass=MinerAlgoMeta):
hashrate: type[AlgoHashRateType]
unit: type[AlgoHashRateUnitType]
class GenericAlgo(MinerAlgoType):
hashrate: type[GenericHashrate] = GenericHashrate
unit: type[GenericUnit] = GenericUnit
name = "Generic (Unknown)"

View File

@@ -1,13 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import Blake256HashRate
from .hashrate.unit import Blake256Unit
# make this json serializable
class Blake256Algo(MinerAlgoType):
hashrate: type[Blake256HashRate] = Blake256HashRate
unit: type[Blake256Unit] = Blake256Unit
name = "Blake256"

View File

@@ -1,12 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import BlockFlowHashRate
from .hashrate.unit import BlockFlowUnit
class BlockFlowAlgo(MinerAlgoType):
hashrate: type[BlockFlowHashRate] = BlockFlowHashRate
unit: type[BlockFlowUnit] = BlockFlowUnit
name = "BlockFlow"

View File

@@ -1,13 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import EaglesongHashRate
from .hashrate.unit import EaglesongUnit
# make this json serializable
class EaglesongAlgo(MinerAlgoType):
hashrate: type[EaglesongHashRate] = EaglesongHashRate
unit: type[EaglesongUnit] = EaglesongUnit
name = "Eaglesong"

View File

@@ -1,13 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import EquihashHashRate
from .hashrate.unit import EquihashUnit
# make this json serializable
class EquihashAlgo(MinerAlgoType):
hashrate: type[EquihashHashRate] = EquihashHashRate
unit: type[EquihashUnit] = EquihashUnit
name = "Equihash"

View File

@@ -1,12 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import EtHashHashRate
from .hashrate.unit import EtHashUnit
class EtHashAlgo(MinerAlgoType):
hashrate: type[EtHashHashRate] = EtHashHashRate
unit: type[EtHashUnit] = EtHashUnit
name = "EtHash"

View File

@@ -1,13 +0,0 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import HandshakeHashRate
from .hashrate.unit import HandshakeUnit
# make this json serializable
class HandshakeAlgo(MinerAlgoType):
hashrate: type[HandshakeHashRate] = HandshakeHashRate
unit: type[HandshakeUnit] = HandshakeUnit
name = "Handshake"

View File

@@ -1,28 +0,0 @@
from .base import AlgoHashRateType
from .blake256 import Blake256HashRate
from .blockflow import BlockFlowHashRate
from .eaglesong import EaglesongHashRate
from .equihash import EquihashHashRate
from .ethash import EtHashHashRate
from .handshake import HandshakeHashRate
from .kadena import KadenaHashRate
from .kheavyhash import KHeavyHashHashRate
from .scrypt import ScryptHashRate
from .sha256 import SHA256HashRate
from .x11 import X11HashRate
from .zksnark import ZkSnarkHashRate
class AlgoHashRate:
SHA256 = SHA256HashRate
SCRYPT = ScryptHashRate
KHEAVYHASH = KHeavyHashHashRate
KADENA = KadenaHashRate
HANDSHAKE = HandshakeHashRate
X11 = X11HashRate
BLAKE256 = Blake256HashRate
EAGLESONG = EaglesongHashRate
ETHASH = EtHashHashRate
EQUIHASH = EquihashHashRate
BLOCKFLOW = BlockFlowHashRate
ZKSNARK = ZkSnarkHashRate

View File

@@ -1,95 +0,0 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from pydantic import BaseModel, field_serializer
from typing_extensions import Self
from .unit.base import AlgoHashRateUnitType, GenericUnit
class AlgoHashRateType(BaseModel, ABC):
unit: AlgoHashRateUnitType
rate: float
@field_serializer("unit")
def serialize_unit(self, unit: AlgoHashRateUnitType):
return unit.model_dump()
@abstractmethod
def into(self, other: "AlgoHashRateUnitType"):
pass
def auto_unit(self):
if 1 < self.rate // int(self.unit.H) < 1000:
return self.into(self.unit.H)
if 1 < self.rate // int(self.unit.MH) < 1000:
return self.into(self.unit.MH)
if 1 < self.rate // int(self.unit.GH) < 1000:
return self.into(self.unit.GH)
if 1 < self.rate // int(self.unit.TH) < 1000:
return self.into(self.unit.TH)
if 1 < self.rate // int(self.unit.PH) < 1000:
return self.into(self.unit.PH)
if 1 < self.rate // int(self.unit.EH) < 1000:
return self.into(self.unit.EH)
if 1 < self.rate // int(self.unit.ZH) < 1000:
return self.into(self.unit.ZH)
return self
def __float__(self):
return float(self.rate)
def __int__(self):
return int(self.rate)
def __repr__(self):
return f"{self.rate} {str(self.unit)}"
def __round__(self, n: int = None):
return round(self.rate, n)
def __add__(self, other: Self | int | float) -> Self:
if isinstance(other, AlgoHashRateType):
return self.__class__(
rate=self.rate + other.into(self.unit).rate, unit=self.unit
)
return self.__class__(rate=self.rate + other, unit=self.unit)
def __sub__(self, other: Self | int | float) -> Self:
if isinstance(other, AlgoHashRateType):
return self.__class__(
rate=self.rate - other.into(self.unit).rate, unit=self.unit
)
return self.__class__(rate=self.rate - other, unit=self.unit)
def __truediv__(self, other: Self | int | float) -> Self:
if isinstance(other, AlgoHashRateType):
return self.__class__(
rate=self.rate / other.into(self.unit).rate, unit=self.unit
)
return self.__class__(rate=self.rate / other, unit=self.unit)
def __floordiv__(self, other: Self | int | float) -> Self:
if isinstance(other, AlgoHashRateType):
return self.__class__(
rate=self.rate // other.into(self.unit).rate, unit=self.unit
)
return self.__class__(rate=self.rate // other, unit=self.unit)
def __mul__(self, other: Self | int | float) -> Self:
if isinstance(other, AlgoHashRateType):
return self.__class__(
rate=self.rate * other.into(self.unit).rate, unit=self.unit
)
return self.__class__(rate=self.rate * other, unit=self.unit)
class GenericHashrate(AlgoHashRateType):
rate: float = 0
unit: GenericUnit = GenericUnit.H
def into(self, other: GenericUnit):
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.blake256 import Blake256Unit
from .unit import HashUnit
class Blake256HashRate(AlgoHashRateType):
rate: float
unit: Blake256Unit = HashUnit.BLAKE256.default
def into(self, other: Blake256Unit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.blockflow import BlockFlowUnit
from .unit import HashUnit
class BlockFlowHashRate(AlgoHashRateType):
rate: float
unit: BlockFlowUnit = HashUnit.BLOCKFLOW.default
def into(self, other: BlockFlowUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.eaglesong import EaglesongUnit
from .unit import HashUnit
class EaglesongHashRate(AlgoHashRateType):
rate: float
unit: EaglesongUnit = HashUnit.EAGLESONG.default
def into(self, other: EaglesongUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.equihash import EquihashUnit
from .unit import HashUnit
class EquihashHashRate(AlgoHashRateType):
rate: float
unit: EquihashUnit = HashUnit.ETHASH.default
def into(self, other: EquihashUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.ethash import EtHashUnit
from .unit import HashUnit
class EtHashHashRate(AlgoHashRateType):
rate: float
unit: EtHashUnit = HashUnit.ETHASH.default
def into(self, other: EtHashUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.handshake import HandshakeUnit
from .unit import HashUnit
class HandshakeHashRate(AlgoHashRateType):
rate: float
unit: HandshakeUnit = HashUnit.HANDSHAKE.default
def into(self, other: HandshakeUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.kadena import KadenaUnit
from .unit import HashUnit
class KadenaHashRate(AlgoHashRateType):
rate: float
unit: KadenaUnit = HashUnit.KADENA.default
def into(self, other: KadenaUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.kheavyhash import KHeavyHashUnit
from .unit import HashUnit
class KHeavyHashHashRate(AlgoHashRateType):
rate: float
unit: KHeavyHashUnit = HashUnit.KHEAVYHASH.default
def into(self, other: KHeavyHashUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.scrypt import ScryptUnit
from .unit import HashUnit
class ScryptHashRate(AlgoHashRateType):
rate: float
unit: ScryptUnit = HashUnit.SCRYPT.default
def into(self, other: ScryptUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,18 +0,0 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.sha256 import SHA256Unit
from .unit import HashUnit
class SHA256HashRate(AlgoHashRateType):
rate: float
unit: SHA256Unit = HashUnit.SHA256.default
def into(self, other: SHA256Unit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -1,27 +0,0 @@
from .blake256 import Blake256Unit
from .blockflow import BlockFlowUnit
from .eaglesong import EaglesongUnit
from .equihash import EquihashUnit
from .ethash import EtHashUnit
from .handshake import HandshakeUnit
from .kadena import KadenaUnit
from .kheavyhash import KHeavyHashUnit
from .scrypt import ScryptUnit
from .sha256 import SHA256Unit
from .x11 import X11Unit
from .zksnark import ZkSnarkUnit
class HashUnit:
SHA256 = SHA256Unit
SCRYPT = ScryptUnit
KHEAVYHASH = KHeavyHashUnit
KADENA = KadenaUnit
HANDSHAKE = HandshakeUnit
X11 = X11Unit
BLAKE256 = Blake256Unit
EAGLESONG = EaglesongUnit
ETHASH = EtHashUnit
EQUIHASH = EquihashUnit
BLOCKFLOW = BlockFlowUnit
ZKSNARK = ZkSnarkUnit

View File

@@ -1,71 +0,0 @@
from enum import IntEnum
class AlgoHashRateUnitType(IntEnum):
H: int
KH: int
MH: int
GH: int
TH: int
PH: int
EH: int
ZH: int
default: int
def __str__(self):
if self.value == self.H:
return "H/s"
if self.value == self.KH:
return "KH/s"
if self.value == self.MH:
return "MH/s"
if self.value == self.GH:
return "GH/s"
if self.value == self.TH:
return "TH/s"
if self.value == self.PH:
return "PH/s"
if self.value == self.EH:
return "EH/s"
if self.value == self.ZH:
return "ZH/s"
@classmethod
def from_str(cls, value: str):
if value == "H":
return cls.H
elif value == "KH":
return cls.KH
elif value == "MH":
return cls.MH
elif value == "GH":
return cls.GH
elif value == "TH":
return cls.TH
elif value == "PH":
return cls.PH
elif value == "EH":
return cls.EH
elif value == "ZH":
return cls.ZH
return cls.default
def __repr__(self):
return str(self)
def model_dump(self):
return {"value": self.value, "suffix": str(self)}
class GenericUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = H

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