Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8adc3d2adf | ||
|
|
040c0b6842 | ||
|
|
550b4a97a1 | ||
|
|
d84d95fe5f | ||
|
|
0e5b811fb9 | ||
|
|
3d31179562 | ||
|
|
69f39bef0c | ||
|
|
1076dab7f5 | ||
|
|
3ae1f700c2 | ||
|
|
dc3f061b9b | ||
|
|
52758dd8b3 | ||
|
|
0e492f1cfd | ||
|
|
659dc55f3c | ||
|
|
eb9b29aca1 | ||
|
|
b045abe76e | ||
|
|
7a75818a20 | ||
|
|
d2be68d35e | ||
|
|
c5c4bb10ee | ||
|
|
c4dfdda448 | ||
|
|
4459de2260 | ||
|
|
201cfd7ef9 | ||
|
|
4201905fdd | ||
|
|
497ffb5bc0 | ||
|
|
2f762c95db | ||
|
|
67aed79330 | ||
|
|
073e048726 | ||
|
|
02234f3d1e | ||
|
|
dc22df0280 | ||
|
|
02056b8c88 | ||
|
|
3a43cd293c | ||
|
|
6941d9f349 | ||
|
|
f6b0b64d86 | ||
|
|
8d68dd9dac | ||
|
|
27368a9bd2 | ||
|
|
c919b00312 | ||
|
|
f162529883 | ||
|
|
bb182bb22d | ||
|
|
af15c4fbd1 | ||
|
|
47c2eb9f0e | ||
|
|
1ab39f5873 | ||
|
|
43200a7354 | ||
|
|
4fc57832e1 | ||
|
|
9ee63cc3ab | ||
|
|
b22b506d55 | ||
|
|
468fba3465 | ||
|
|
0399094197 | ||
|
|
bfdfa8a6ab | ||
|
|
83d0d09b0d | ||
|
|
f892c3a0fd | ||
|
|
81b974f565 | ||
|
|
5eaf876c6d | ||
|
|
d7d1b845a7 | ||
|
|
242517a36a | ||
|
|
791249bf3d | ||
|
|
5a70a27f07 | ||
|
|
bca81f3bca | ||
|
|
6d75565baf | ||
|
|
9f42e6a3be | ||
|
|
362b204c91 | ||
|
|
952b660c05 | ||
|
|
fbd73881d4 | ||
|
|
68c4dadb63 | ||
|
|
87016670d4 | ||
|
|
8701bbe4e2 | ||
|
|
7d1f125b0b | ||
|
|
e433902bd5 | ||
|
|
a653772968 | ||
|
|
d8b938cd5b | ||
|
|
47d76e325a | ||
|
|
7ee7868094 | ||
|
|
3f1183a4f9 | ||
|
|
2b443497ea | ||
|
|
c3972f9524 | ||
|
|
92bbb21c11 | ||
|
|
1980ff2563 | ||
|
|
93d09a1483 | ||
|
|
690d0d99df | ||
|
|
78f689eb2c | ||
|
|
e68f188e8f | ||
|
|
7eda611fe9 | ||
|
|
1d12817942 | ||
|
|
b24efd4c69 | ||
|
|
5533135b05 | ||
|
|
475054fbe0 | ||
|
|
06bad1bbe0 | ||
|
|
f3746ff756 | ||
|
|
9f16d37c8b | ||
|
|
8a13c7940a | ||
|
|
8bea76ff67 | ||
|
|
1504bd744c | ||
|
|
6449f10615 | ||
|
|
d79509bda7 | ||
|
|
630b847466 | ||
|
|
ed11611919 | ||
|
|
e2431c938d | ||
|
|
60f4b4a5ed | ||
|
|
d41097af20 | ||
|
|
8a5d505731 | ||
|
|
36e76c6f41 | ||
|
|
717b9421dd | ||
|
|
d2f71e8c94 | ||
|
|
697991f28f | ||
|
|
b0e18ab766 | ||
|
|
e39a6921d0 | ||
|
|
aac1be0565 | ||
|
|
683fcb2138 | ||
|
|
9fbbef9b18 | ||
|
|
6e0b9a0a7b | ||
|
|
7f472f6f4f | ||
|
|
b7d7b33ab9 | ||
|
|
da11c0bb1f | ||
|
|
eae433d2bd | ||
|
|
c16bc37aff | ||
|
|
255b06ac9e | ||
|
|
29ec619126 | ||
|
|
247def04ff | ||
|
|
4600e7d953 | ||
|
|
850c266555 | ||
|
|
ad374fe2fb | ||
|
|
5ca39b6fe7 | ||
|
|
b50dd26e6f | ||
|
|
53eaccaa9b | ||
|
|
91f207316a | ||
|
|
1e37418909 | ||
|
|
4c09ba5529 | ||
|
|
7bab4747ad | ||
|
|
fd8cc7378c | ||
|
|
8aeef4d5e7 | ||
|
|
4bafde9da7 | ||
|
|
5a3107aecf | ||
|
|
7e758720f0 | ||
|
|
39e3e249f8 | ||
|
|
118c5b056e | ||
|
|
2c3b5599fe | ||
|
|
e421eaa324 | ||
|
|
75d6bc6808 | ||
|
|
98c547e416 | ||
|
|
45250e36e4 | ||
|
|
fa7544d052 | ||
|
|
53f3fc5ee9 | ||
|
|
1b36de4131 | ||
|
|
6f0c6f6284 | ||
|
|
b7dda5bf87 | ||
|
|
14f33a40c3 | ||
|
|
5c904aced0 | ||
|
|
53a3bbf531 | ||
|
|
50586f1ce7 | ||
|
|
9f6235a0fc |
291
README.md
291
README.md
@@ -1,142 +1,249 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
*A simplified and standardized interface for Bitcoin ASICs.*
|
||||||
|
|
||||||
[](https://github.com/psf/black)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
[](https://pypi.org/project/pyasic/)
|
|
||||||
[](https://pyasic.readthedocs.io/en/latest/)
|
|
||||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
|
||||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
|
||||||
## Documentation and Supported Miners
|
|
||||||
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/).
|
|
||||||
|
|
||||||
Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/).
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||||
|
[](https://github.com/UpstreamData/pyasic/commits/master/)
|
||||||
|
|
||||||
## Installation
|
[](https://github.com/psf/black)
|
||||||
You can install pyasic directly from pip with the command `pip install pyasic`.
|
[](https://pyasic.readthedocs.io/en/latest/)
|
||||||
|
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||||
|
|
||||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library [here](https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing).
|
---
|
||||||
|
## Intro
|
||||||
|
|
||||||
## Developer Setup
|
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
||||||
It is highly reccommended that you contribute to this project through [`pyasic-super`](https://github.com/UpstreamData/pyasic-super) using its submodules. This allows testing in conjunction with other `pyasic` related programs.
|
|
||||||
|
|
||||||
<br>
|
[Click here to view supported miner types](miners/supported_types.md)
|
||||||
|
|
||||||
This repo uses poetry for dependencies, which can be installed by following the guide on their website [here](https://python-poetry.org/docs/#installation).
|
---
|
||||||
|
## Getting started
|
||||||
|
|
||||||
After you have poetry installed, run `poetry install --with dev`, or `poetry install --with dev,docs` if you want to include packages required for documentation.
|
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.
|
||||||
|
|
||||||
Finally, initialize pre-commit hooks with `poetry run pre-commit install`.
|
##### Scanning for miners
|
||||||
|
To scan for miners in `pyasic`, we use the class `MinerNetwork`, which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||||
### Documentation Testing
|
The command `MinerNetwork.scan()` returns a list that contains any miners found.
|
||||||
Testing the documentation can be done by running `poetry run mkdocs serve`, whcih will serve the documentation locally on port 8000.
|
|
||||||
|
|
||||||
## Interfacing with miners programmatically
|
|
||||||
|
|
||||||
There are 2 main ways to get a miner (and the functions attached to it), via scanning or via the `MinerFactory()`.
|
|
||||||
|
|
||||||
#### Scanning for miners
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio # asyncio for handling the async part
|
||||||
|
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||||
from pyasic.network import MinerNetwork
|
|
||||||
|
|
||||||
|
|
||||||
# define asynchronous function to scan for miners
|
async def scan_miners(): # define async scan function to allow awaiting
|
||||||
async def scan_and_get_data():
|
# create a miner network
|
||||||
# Define network range to be used for scanning
|
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||||
# This can take a list of IPs, a constructor string, or an IP and subnet mask
|
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
|
||||||
# The standard mask is /24 (x.x.x.0-255), and you can pass any IP address in the subnet
|
|
||||||
net = MinerNetwork("192.168.1.69", mask=24)
|
|
||||||
# Scan the network for miners
|
|
||||||
# This function returns a list of miners of the correct type as a class
|
|
||||||
miners: list = await net.scan_network_for_miners()
|
|
||||||
|
|
||||||
# We can now get data from any of these miners
|
# scan for miners asynchronously
|
||||||
# To do them all we have to create a list of tasks and gather them
|
# this will return the correct type of miners if they are supported with all functionality.
|
||||||
tasks = [miner.get_data() for miner in miners]
|
miners = await network.scan()
|
||||||
# Gather all tasks asynchronously and run them
|
print(miners)
|
||||||
data = await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
# Data is now a list of MinerData, and we can reference any part of that
|
|
||||||
# Print out all data for now
|
|
||||||
for item in data:
|
|
||||||
print(item)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(scan_and_get_data())
|
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
##### Creating miners based on IP
|
||||||
|
If you already know the IP address of your miner or miners, you can use the `MinerFactory` to communicate and identify the miners, or an abstraction of its functionality, `get_miner()`.
|
||||||
|
The function `get_miner()` will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||||
|
```python
|
||||||
|
import asyncio # asyncio for handling the async part
|
||||||
|
from pyasic import get_miner # handles miner creation
|
||||||
|
|
||||||
#### Getting a miner if you know the IP
|
|
||||||
|
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)
|
||||||
|
print(miners)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(get_miners()) # 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` with all data it can gather from the miner.
|
||||||
|
Each piece of data in a `MinerData` instance can be referenced by getting it as an attribute, such as `MinerData().hashrate`.
|
||||||
|
|
||||||
|
##### One miner
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
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())
|
||||||
|
```
|
||||||
|
---
|
||||||
|
##### 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 # asyncio for handling the async part
|
||||||
|
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||||
|
|
||||||
|
|
||||||
|
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) # print out all the data one by one
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(gather_miner_data())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## Miner control
|
||||||
|
|
||||||
|
`pyasic` exposes a standard interface for each miner using control functions.
|
||||||
|
Every miner class in `pyasic` must implement all the control functions defined in `BaseMiner`.
|
||||||
|
|
||||||
|
These functions are
|
||||||
|
`check_light`,
|
||||||
|
`fault_light_off`,
|
||||||
|
`fault_light_on`,
|
||||||
|
`get_config`,
|
||||||
|
`get_data`,
|
||||||
|
`get_errors`,
|
||||||
|
`get_hostname`,
|
||||||
|
`get_model`,
|
||||||
|
`reboot`,
|
||||||
|
`restart_backend`,
|
||||||
|
`stop_mining`,
|
||||||
|
`resume_mining`,
|
||||||
|
`is_mining`,
|
||||||
|
`send_config`, and
|
||||||
|
`set_power_limit`.
|
||||||
|
|
||||||
|
##### Usage
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
from pyasic import get_miner
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
# define asynchronous function to get miner and data
|
async def set_fault_light():
|
||||||
async def get_miner_data(miner_ip: str):
|
miner = await get_miner("192.168.1.20")
|
||||||
# Use MinerFactory to get miner
|
|
||||||
# MinerFactory is a singleton, so we can just get the instance in place
|
|
||||||
miner = await get_miner(miner_ip)
|
|
||||||
|
|
||||||
# Get data from the miner
|
# call control function
|
||||||
data = await miner.get_data()
|
await miner.fault_light_on()
|
||||||
print(data)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(get_miner_data("192.168.1.69"))
|
asyncio.run(set_fault_light())
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced data gathering
|
---
|
||||||
|
## Helper dataclasses
|
||||||
|
|
||||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
##### `MinerConfig` and `MinerData`
|
||||||
|
|
||||||
You can see more information on basic usage of the APIs past this example in the docs [here](https://pyasic.readthedocs.io/en/latest/API/api/).
|
`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()`.
|
||||||
|
|
||||||
Please see the appropriate API documentation page (pyasic docs -> Advanced -> Miner APIs -> your API type) for a link to that specific miner's API documentation page and more information.
|
---
|
||||||
|
|
||||||
#### List available API commands
|
##### MinerData
|
||||||
|
|
||||||
|
`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()` to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||||
|
|
||||||
|
`MinerData` instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
|
||||||
|
```python
|
||||||
|
from pyasic import MinerData
|
||||||
|
|
||||||
|
# examples of miner data
|
||||||
|
d1 = MinerData("192.168.1.1")
|
||||||
|
d2 = MinerData("192.168.1.2")
|
||||||
|
|
||||||
|
list_of_miner_data = [d1, d2]
|
||||||
|
|
||||||
|
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
##### MinerConfig
|
||||||
|
|
||||||
|
`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()`](#get-config).
|
||||||
|
|
||||||
|
Each miner has a unique way to convert the `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()`](#send-config) takes a [`MinerConfig` and will do the conversion to the right type for you.
|
||||||
|
|
||||||
|
You can use the `MinerConfig` as follows:
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from pyasic import get_miner
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
async def get_api_commands(miner_ip: str):
|
async def set_fault_light():
|
||||||
# Get the miner
|
miner = await get_miner("192.168.1.20")
|
||||||
miner = await get_miner(miner_ip)
|
|
||||||
|
|
||||||
# List all available commands
|
# get config
|
||||||
# Can also be called explicitly with the function miner.api.get_commands()
|
cfg = await miner.get_config()
|
||||||
print(miner.api.commands)
|
|
||||||
|
|
||||||
|
# send config
|
||||||
|
await miner.send_config(cfg)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(get_api_commands("192.168.1.69"))
|
asyncio.run(set_fault_light())
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Use miner API commands to gather data
|
---
|
||||||
|
## Settings
|
||||||
|
|
||||||
The miner API commands will raise an `APIError` if they fail with a bad status code, to bypass this you must send them manually by using `miner.api.send_command(command, ignore_errors=True)`
|
`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:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
from pyasic import settings
|
||||||
|
|
||||||
from pyasic import get_miner
|
settings.update("default_antminer_password", "my_pwd")
|
||||||
|
```
|
||||||
|
|
||||||
async def get_api_commands(miner_ip: str):
|
##### Default values:
|
||||||
# Get the miner
|
```
|
||||||
miner = await get_miner(miner_ip)
|
"network_ping_retries": 1,
|
||||||
|
"network_ping_timeout": 3,
|
||||||
# Run the devdetails command
|
"network_scan_threads": 300,
|
||||||
# This is equivalent to await miner.api.send_command("devdetails")
|
"factory_get_retries": 1,
|
||||||
devdetails: dict = await miner.api.devdetails()
|
"factory_get_timeout": 3,
|
||||||
print(devdetails)
|
"get_data_retries": 1,
|
||||||
|
"api_function_timeout": 5,
|
||||||
|
"default_whatsminer_password": "admin",
|
||||||
if __name__ == "__main__":
|
"default_innosilicon_password": "admin",
|
||||||
asyncio.run(get_api_commands("192.168.1.69"))
|
"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
|
||||||
|
"socket_linger_time": 1000,
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,19 +6,3 @@
|
|||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## Pool Groups
|
|
||||||
|
|
||||||
::: pyasic.config._PoolGroup
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## Pools
|
|
||||||
|
|
||||||
::: pyasic.config._Pool
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ def backend_str(backend: MinerTypes) -> str:
|
|||||||
return "Stock Firmware Avalonminers"
|
return "Stock Firmware Avalonminers"
|
||||||
case MinerTypes.VNISH:
|
case MinerTypes.VNISH:
|
||||||
return "Vnish Firmware Miners"
|
return "Vnish Firmware Miners"
|
||||||
|
case MinerTypes.EPIC:
|
||||||
|
return "ePIC Firmware Miners"
|
||||||
case MinerTypes.BRAIINS_OS:
|
case MinerTypes.BRAIINS_OS:
|
||||||
return "BOS+ Firmware Miners"
|
return "BOS+ Firmware Miners"
|
||||||
case MinerTypes.HIVEON:
|
case MinerTypes.HIVEON:
|
||||||
|
|||||||
305
docs/index.md
305
docs/index.md
@@ -1,25 +1,31 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
*A simplified and standardized interface for Bitcoin ASICs.*
|
||||||
|
|
||||||
[](https://github.com/psf/black)
|
|
||||||
[](https://pypi.org/project/pyasic/)
|
|
||||||
[](https://pypi.org/project/pyasic/)
|
|
||||||
[](https://pyasic.readthedocs.io/en/latest/)
|
|
||||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
|
||||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
|
||||||
|
|
||||||
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||||
|
[](https://github.com/UpstreamData/pyasic/commits/master/)
|
||||||
|
[](https://github.com/psf/black)
|
||||||
|
[](https://pyasic.readthedocs.io/en/latest/)
|
||||||
|
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||||
|
|
||||||
|
---
|
||||||
## Intro
|
## Intro
|
||||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
---
|
||||||
|
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
||||||
|
|
||||||
[Supported Miner Types](miners/supported_types.md)
|
[Click here to view supported miner types](miners/supported_types.md)
|
||||||
|
|
||||||
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.
|
---
|
||||||
|
## 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.
|
||||||
|
|
||||||
<br>
|
##### 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.
|
||||||
## Scanning for miners
|
The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
|
||||||
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_network_for_miners()`][pyasic.network.MinerNetwork.scan_network_for_miners] returns a list that contains any miners found.
|
|
||||||
```python
|
```python
|
||||||
import asyncio # asyncio for handling the async part
|
import asyncio # asyncio for handling the async part
|
||||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||||
@@ -28,20 +34,19 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
|||||||
async def scan_miners(): # define async scan function to allow awaiting
|
async def scan_miners(): # define async scan function to allow awaiting
|
||||||
# create a miner network
|
# create a miner network
|
||||||
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||||
network = MinerNetwork("192.168.1.50") # this uses the 192.168.1.0-255 network
|
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
|
||||||
|
|
||||||
# scan for miners asynchronously
|
# scan for miners asynchronously
|
||||||
# this will return the correct type of miners if they are supported with all functionality.
|
# this will return the correct type of miners if they are supported with all functionality.
|
||||||
miners = await network.scan_network_for_miners()
|
miners = await network.scan()
|
||||||
print(miners)
|
print(miners)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
<br>
|
---
|
||||||
|
##### Creating miners based on IP
|
||||||
## Creating miners based on IP
|
|
||||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
||||||
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||||
```python
|
```python
|
||||||
@@ -58,6 +63,8 @@ async def get_miners(): # define async scan function to allow awaiting
|
|||||||
print(miner_1, miner_2)
|
print(miner_1, miner_2)
|
||||||
|
|
||||||
# can also gather these, since they are async
|
# 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")]
|
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||||
miners = await asyncio.gather(*tasks)
|
miners = await asyncio.gather(*tasks)
|
||||||
print(miners)
|
print(miners)
|
||||||
@@ -67,13 +74,14 @@ if __name__ == "__main__":
|
|||||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
<br>
|
---
|
||||||
|
## Data gathering
|
||||||
## Getting data from miners
|
---
|
||||||
|
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()`.
|
||||||
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.
|
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].
|
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
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
from pyasic import get_miner
|
from pyasic import get_miner
|
||||||
@@ -88,7 +96,8 @@ async def gather_miner_data():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(gather_miner_data())
|
asyncio.run(gather_miner_data())
|
||||||
```
|
```
|
||||||
|
---
|
||||||
|
##### 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.
|
You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.
|
||||||
```python
|
```python
|
||||||
import asyncio # asyncio for handling the async part
|
import asyncio # asyncio for handling the async part
|
||||||
@@ -96,8 +105,8 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
|||||||
|
|
||||||
|
|
||||||
async def gather_miner_data(): # define async scan function to allow awaiting
|
async def gather_miner_data(): # define async scan function to allow awaiting
|
||||||
network = MinerNetwork("192.168.1.50")
|
network = MinerNetwork.from_subnet("192.168.1.50/24")
|
||||||
miners = await network.scan_network_for_miners()
|
miners = await network.scan()
|
||||||
|
|
||||||
# we need to asyncio.gather() all the miners get_data() functions to make them run together
|
# 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])
|
all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])
|
||||||
@@ -109,161 +118,60 @@ if __name__ == "__main__":
|
|||||||
asyncio.run(gather_miner_data())
|
asyncio.run(gather_miner_data())
|
||||||
```
|
```
|
||||||
|
|
||||||
<br>
|
---
|
||||||
|
## Miner control
|
||||||
## Controlling miners via pyasic
|
---
|
||||||
Every miner class in pyasic must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
`pyasic` exposes a standard interface for each miner using control functions.
|
||||||
|
Every miner class in `pyasic` must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
||||||
|
|
||||||
These functions are
|
These functions are
|
||||||
[`check_light`](#check-light),
|
[`check_light`][pyasic.miners.BaseMiner.check_light],
|
||||||
[`fault_light_off`](#fault-light-off),
|
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off],
|
||||||
[`fault_light_on`](#fault-light-on),
|
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on],
|
||||||
[`get_config`](#get-config),
|
[`get_config`][pyasic.miners.BaseMiner.get_config],
|
||||||
[`get_data`](#get-data),
|
[`get_data`][pyasic.miners.BaseMiner.get_data],
|
||||||
[`get_errors`](#get-errors),
|
[`get_errors`][pyasic.miners.BaseMiner.get_errors],
|
||||||
[`get_hostname`](#get-hostname),
|
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname],
|
||||||
[`get_model`](#get-model),
|
[`get_model`][pyasic.miners.BaseMiner.get_model],
|
||||||
[`reboot`](#reboot),
|
[`reboot`][pyasic.miners.BaseMiner.reboot],
|
||||||
[`restart_backend`](#restart-backend),
|
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend],
|
||||||
[`stop_mining`](#stop-mining),
|
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining],
|
||||||
[`resume_mining`](#resume-mining),
|
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining],
|
||||||
[`is_mining`](#is-mining),
|
[`is_mining`][pyasic.miners.BaseMiner.is_mining],
|
||||||
[`send_config`](#send-config), and
|
[`send_config`][pyasic.miners.BaseMiner.send_config], and
|
||||||
[`set_power_limit`](#set-power-limit).
|
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit].
|
||||||
|
|
||||||
<br>
|
##### Usage
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from pyasic import get_miner
|
||||||
|
|
||||||
### Check Light
|
|
||||||
::: pyasic.miners.BaseMiner.check_light
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
async def set_fault_light():
|
||||||
|
miner = await get_miner("192.168.1.20")
|
||||||
|
|
||||||
### Fault Light Off
|
# call control function
|
||||||
::: pyasic.miners.BaseMiner.fault_light_off
|
await miner.fault_light_on()
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(set_fault_light())
|
||||||
|
```
|
||||||
|
|
||||||
### Fault Light On
|
---
|
||||||
::: pyasic.miners.BaseMiner.fault_light_on
|
## Helper dataclasses
|
||||||
handler: python
|
---
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
##### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||||
|
|
||||||
### Get Config
|
`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()`.
|
||||||
::: pyasic.miners.BaseMiner.get_config
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
---
|
||||||
|
|
||||||
### Get Data
|
##### [`MinerData`][pyasic.data.MinerData]
|
||||||
::: pyasic.miners.BaseMiner.get_data
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Get Errors
|
|
||||||
::: pyasic.miners.BaseMiner.get_errors
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Get Hostname
|
|
||||||
::: pyasic.miners.BaseMiner.get_hostname
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Get Model
|
|
||||||
::: pyasic.miners.BaseMiner.get_model
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Reboot
|
|
||||||
::: pyasic.miners.BaseMiner.reboot
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Restart Backend
|
|
||||||
::: pyasic.miners.BaseMiner.restart_backend
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Stop Mining
|
|
||||||
::: pyasic.miners.BaseMiner.stop_mining
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Resume Mining
|
|
||||||
::: pyasic.miners.BaseMiner.resume_mining
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Is Mining
|
|
||||||
::: pyasic.miners.BaseMiner.is_mining
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Send Config
|
|
||||||
::: pyasic.miners.BaseMiner.send_config
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### Set Power Limit
|
|
||||||
::: pyasic.miners.BaseMiner.set_power_limit
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## [`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()`.
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
### [`MinerData`][pyasic.data.MinerData]
|
|
||||||
|
|
||||||
[`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.
|
[`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.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
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.
|
||||||
|
|
||||||
[`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
|
[`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
|
||||||
```python
|
```python
|
||||||
@@ -278,13 +186,64 @@ list_of_miner_data = [d1, d2]
|
|||||||
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
|
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<br>
|
##### [`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()`](#get-config).
|
||||||
[`MinerConfig`][pyasic.config.MinerConfig] is pyasic's way to represent a configuration file from a miner.
|
|
||||||
It 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.
|
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()`](#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
|
||||||
|
import asyncio
|
||||||
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
|
async def set_fault_light():
|
||||||
|
miner = await get_miner("192.168.1.20")
|
||||||
|
|
||||||
|
# get config
|
||||||
|
cfg = await miner.get_config()
|
||||||
|
|
||||||
|
# send config
|
||||||
|
await miner.send_config(cfg)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(set_fault_light())
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pyasic import settings
|
||||||
|
|
||||||
|
settings.update("default_antminer_password", "my_pwd")
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Default values:
|
||||||
|
```
|
||||||
|
"network_ping_retries": 1,
|
||||||
|
"network_ping_timeout": 3,
|
||||||
|
"network_scan_threads": 300,
|
||||||
|
"factory_get_retries": 1,
|
||||||
|
"factory_get_timeout": 3,
|
||||||
|
"get_data_retries": 1,
|
||||||
|
"api_function_timeout": 5,
|
||||||
|
"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
|
||||||
|
"socket_linger_time": 1000,
|
||||||
|
```
|
||||||
|
|||||||
@@ -29,6 +29,20 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19i
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19i
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19+
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## S19j No PIC
|
## S19j No PIC
|
||||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
||||||
handler: python
|
handler: python
|
||||||
@@ -113,6 +127,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19j Pro (BOS)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## T19 (BOS)
|
## T19 (BOS)
|
||||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||||
handler: python
|
handler: python
|
||||||
@@ -176,3 +197,38 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X19.S19.ePICS19
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X19.S19.ePICS19Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19j (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X19.S19.ePICS19j
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19j Pro (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X19.S19.ePICS19jPro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 XP (ePIC)
|
||||||
|
::: pyasic.miners.antminer.epic.X19.S19.ePICS19XP
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|||||||
8
docs/miners/backends/epic.md
Normal file
8
docs/miners/backends/epic.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## ePIC Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.epic.ePIC
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
91
docs/miners/functions.md
Normal file
91
docs/miners/functions.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
## Control functionality
|
||||||
|
|
||||||
|
### Check Light
|
||||||
|
::: pyasic.miners.BaseMiner.check_light
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Fault Light Off
|
||||||
|
::: pyasic.miners.BaseMiner.fault_light_off
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Fault Light On
|
||||||
|
::: pyasic.miners.BaseMiner.fault_light_on
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Get Config
|
||||||
|
::: pyasic.miners.BaseMiner.get_config
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Get Data
|
||||||
|
::: pyasic.miners.BaseMiner.get_data
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Get Errors
|
||||||
|
::: pyasic.miners.BaseMiner.get_errors
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Get Hostname
|
||||||
|
::: pyasic.miners.BaseMiner.get_hostname
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Get Model
|
||||||
|
::: pyasic.miners.BaseMiner.get_model
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Reboot
|
||||||
|
::: pyasic.miners.BaseMiner.reboot
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Restart Backend
|
||||||
|
::: pyasic.miners.BaseMiner.restart_backend
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Stop Mining
|
||||||
|
::: pyasic.miners.BaseMiner.stop_mining
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Resume Mining
|
||||||
|
::: pyasic.miners.BaseMiner.resume_mining
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Is Mining
|
||||||
|
::: pyasic.miners.BaseMiner.is_mining
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Send Config
|
||||||
|
::: pyasic.miners.BaseMiner.send_config
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### Set Power Limit
|
||||||
|
::: pyasic.miners.BaseMiner.set_power_limit
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Miner Factory
|
## Miner Factory
|
||||||
|
|
||||||
|
[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||||
|
|
||||||
|
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||||
|
|
||||||
|
[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||||
|
|
||||||
|
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||||
|
|
||||||
::: pyasic.miners.miner_factory.MinerFactory
|
::: pyasic.miners.miner_factory.MinerFactory
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ details {
|
|||||||
padding-top:0px;
|
padding-top:0px;
|
||||||
padding-bottom:0px;
|
padding-bottom:0px;
|
||||||
}
|
}
|
||||||
ul {
|
|
||||||
margin:0px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -73,6 +70,8 @@ ul {
|
|||||||
<li><a href="../antminer/X19#s19l">S19L</a></li>
|
<li><a href="../antminer/X19#s19l">S19L</a></li>
|
||||||
<li><a href="../antminer/X19#s19-pro">S19 Pro</a></li>
|
<li><a href="../antminer/X19#s19-pro">S19 Pro</a></li>
|
||||||
<li><a href="../antminer/X19#s19j">S19j</a></li>
|
<li><a href="../antminer/X19#s19j">S19j</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19i">S19i</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19_1">S19+</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
||||||
<li><a href="../antminer/X19#s19-pro_1">S19 Pro+</a></li>
|
<li><a href="../antminer/X19#s19-pro_1">S19 Pro+</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||||
@@ -94,6 +93,8 @@ ul {
|
|||||||
<li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
|
<li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
|
||||||
<li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
|
<li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
|
||||||
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
||||||
|
<li><a href="../whatsminer/M2X#m20p-v10">M20P V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M2X#m20p-v30">M20P V30</a></li>
|
||||||
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
||||||
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
||||||
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
||||||
@@ -108,6 +109,8 @@ ul {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
|
<li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30k-v10">M30K V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
|
<li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
||||||
@@ -157,6 +160,7 @@ ul {
|
|||||||
<li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vg20">M30S+ VG20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
||||||
@@ -190,6 +194,9 @@ ul {
|
|||||||
<li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
|
<li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
|
<li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31h-v10">M31H V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
||||||
@@ -205,7 +212,6 @@ ul {
|
|||||||
<li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
|
<li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
|
<li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
|
<li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
|
||||||
<li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
|
<li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
|
<li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
|
<li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
|
||||||
@@ -232,6 +238,7 @@ ul {
|
|||||||
<li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
|
<li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
|
<li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
|
<li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1-vg20">M33S+ VG20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
||||||
@@ -241,12 +248,15 @@ ul {
|
|||||||
<li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
|
<li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
|
<li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
|
<li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m39-v10">M39 V10</a></li>
|
||||||
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m39-v30">M39 V30</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>M5X Series:</summary>
|
<summary>M5X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="../whatsminer/M5X#m50-ve30">M50 VE30</a></li>
|
||||||
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
||||||
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
||||||
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
||||||
@@ -397,6 +407,7 @@ ul {
|
|||||||
<li><a href="../antminer/X19#s19j-bos">S19j (BOS)</a></li>
|
<li><a href="../antminer/X19#s19j-bos">S19j (BOS)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-no-pic-bos">S19j No PIC (BOS)</a></li>
|
<li><a href="../antminer/X19#s19j-no-pic-bos">S19j No PIC (BOS)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-bos">T19 (BOS)</a></li>
|
<li><a href="../antminer/X19#t19-bos">T19 (BOS)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -434,6 +445,21 @@ ul {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
|
<summary>ePIC Firmware Miners:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary>X19 Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../antminer/X19#s19-epic">S19 (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro-epic">S19 Pro (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19j-epic">S19j (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19j-pro-epic">S19j Pro (ePIC)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-xp-epic">S19 XP (ePIC)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
<summary>HiveOS Firmware Miners:</summary>
|
<summary>HiveOS Firmware Miners:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<details>
|
||||||
@@ -454,4 +480,4 @@ ul {
|
|||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -29,6 +29,20 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20P V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20P V30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M20S+ V30
|
## M20S+ V30
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
@@ -15,6 +15,20 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30K V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30K.BTMinerM30KV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30L V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30L.BTMinerM30LV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M30S V10
|
## M30S V10
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV10
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV10
|
||||||
handler: python
|
handler: python
|
||||||
@@ -358,6 +372,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+ VG20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M30S+ VG30
|
## M30S+ VG30
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG30
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG30
|
||||||
handler: python
|
handler: python
|
||||||
@@ -589,6 +610,27 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M31H V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M31H V40
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30L V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31L.BTMinerM31LV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M31S V10
|
## M31S V10
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
|
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
|
||||||
handler: python
|
handler: python
|
||||||
@@ -694,13 +736,6 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## M31H V40
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## M31S+ V10
|
## M31S+ V10
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV10
|
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV10
|
||||||
handler: python
|
handler: python
|
||||||
@@ -883,6 +918,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M33S+ VG20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVG20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M33S+ VH20
|
## M33S+ VH20
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH20
|
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH20
|
||||||
handler: python
|
handler: python
|
||||||
@@ -946,6 +988,13 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M39 V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M39 V20
|
## M39 V20
|
||||||
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V20
|
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V20
|
||||||
handler: python
|
handler: python
|
||||||
@@ -953,3 +1002,10 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M39 V30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## M5X Models
|
## M5X Models
|
||||||
|
|
||||||
|
## M50 VE30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## M50 VG30
|
## M50 VG30
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||||
handler: python
|
handler: python
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# pyasic
|
|
||||||
## Miner Network Range
|
|
||||||
|
|
||||||
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
|
||||||
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
|
||||||
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
|
||||||
|
|
||||||
::: pyasic.network.net_range.MinerNetworkRange
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
35
docs/settings/settings.md
Normal file
35
docs/settings/settings.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# pyasic
|
||||||
|
## settings
|
||||||
|
|
||||||
|
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
|
||||||
|
|
||||||
|
Settings options:
|
||||||
|
- `network_ping_retries`
|
||||||
|
- `network_ping_timeout`
|
||||||
|
- `network_scan_threads`
|
||||||
|
- `factory_get_retries`
|
||||||
|
- `factory_get_timeout`
|
||||||
|
- `get_data_retries`
|
||||||
|
- `api_function_timeout`
|
||||||
|
- `default_whatsminer_password`
|
||||||
|
- `default_innosilicon_password`
|
||||||
|
- `default_antminer_password`
|
||||||
|
- `default_bosminer_password`
|
||||||
|
- `default_vnish_password`
|
||||||
|
- `default_goldshell_password`
|
||||||
|
- `socket_linger_time`
|
||||||
|
|
||||||
|
|
||||||
|
### get
|
||||||
|
::: pyasic.settings.get
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
### update
|
||||||
|
::: pyasic.settings.update
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -4,10 +4,10 @@ nav:
|
|||||||
- Introduction: "index.md"
|
- Introduction: "index.md"
|
||||||
- Miners:
|
- Miners:
|
||||||
- Supported Miners: "miners/supported_types.md"
|
- Supported Miners: "miners/supported_types.md"
|
||||||
|
- Standard Functionality: "miners/functions.md"
|
||||||
- Miner Factory: "miners/miner_factory.md"
|
- Miner Factory: "miners/miner_factory.md"
|
||||||
- Network:
|
- Network:
|
||||||
- Miner Network: "network/miner_network.md"
|
- Miner Network: "network/miner_network.md"
|
||||||
- Miner Network Range: "network/miner_network_range.md"
|
|
||||||
- Dataclasses:
|
- Dataclasses:
|
||||||
- Miner Data: "data/miner_data.md"
|
- Miner Data: "data/miner_data.md"
|
||||||
- Error Codes: "data/error_codes.md"
|
- Error Codes: "data/error_codes.md"
|
||||||
@@ -30,6 +30,7 @@ nav:
|
|||||||
- CGMiner: "miners/backends/cgminer.md"
|
- CGMiner: "miners/backends/cgminer.md"
|
||||||
- LUXMiner: "miners/backends/luxminer.md"
|
- LUXMiner: "miners/backends/luxminer.md"
|
||||||
- VNish: "miners/backends/vnish.md"
|
- VNish: "miners/backends/vnish.md"
|
||||||
|
- ePIC: "miners/backends/epic.md"
|
||||||
- Hiveon: "miners/backends/hiveon.md"
|
- Hiveon: "miners/backends/hiveon.md"
|
||||||
- Classes:
|
- Classes:
|
||||||
- Antminer X3: "miners/antminer/X3.md"
|
- Antminer X3: "miners/antminer/X3.md"
|
||||||
@@ -53,7 +54,8 @@ nav:
|
|||||||
- Goldshell X5: "miners/goldshell/X5.md"
|
- Goldshell X5: "miners/goldshell/X5.md"
|
||||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||||
- Base Miner: "miners/base_miner.md"
|
- Base Miner: "miners/base_miner.md"
|
||||||
|
- Settings:
|
||||||
|
- Settings: "settings/settings.md"
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- mkdocstrings
|
- mkdocstrings
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Tuple, Union
|
from typing import Union
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
|
|
||||||
@@ -83,15 +83,15 @@ class BaseMinerAPI:
|
|||||||
data = self._load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# check for if the user wants to allow errors to return
|
# check for if the user wants to allow errors to return
|
||||||
if not ignore_errors:
|
validation = self._validate_command_output(data)
|
||||||
# validate the command succeeded
|
if not validation[0]:
|
||||||
validation = self._validate_command_output(data)
|
if not ignore_errors:
|
||||||
if not validation[0]:
|
# validate the command succeeded
|
||||||
if allow_warning:
|
|
||||||
logging.warning(
|
|
||||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
|
||||||
)
|
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
|
if allow_warning:
|
||||||
|
logging.warning(
|
||||||
|
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||||
|
)
|
||||||
|
|
||||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||||
return data
|
return data
|
||||||
@@ -118,11 +118,12 @@ class BaseMinerAPI:
|
|||||||
data = await self.send_command(command, allow_warning=allow_warning)
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
# try to identify the error
|
# try to identify the error
|
||||||
if ":" in e.message:
|
if e.message is not None:
|
||||||
err_command = e.message.split(":")[0]
|
if ":" in e.message:
|
||||||
if err_command in commands:
|
err_command = e.message.split(":")[0]
|
||||||
commands.remove(err_command)
|
if err_command in commands:
|
||||||
continue
|
commands.remove(err_command)
|
||||||
|
continue
|
||||||
return {command: [{}] for command in commands}
|
return {command: [{}] for command in commands}
|
||||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
data["multicommand"] = True
|
data["multicommand"] = True
|
||||||
@@ -206,16 +207,18 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
try:
|
try:
|
||||||
ret_data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
# TO address a situation where a whatsminer has an unknown PW -AND-
|
||||||
except ConnectionAbortedError:
|
|
||||||
return b"{}"
|
|
||||||
try:
|
|
||||||
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||||
# have to receive, save the data, check if there is more data by reading with a short timeout
|
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||||
# append that data if there is more, and then onto the main loop.
|
# append that data if there is more, and then onto the main loop.
|
||||||
ret_data += await asyncio.wait_for(reader.read(1), timeout=1)
|
# the password timeout might need to be longer than 1, but it seems to work for now.
|
||||||
|
ret_data = await asyncio.wait_for(reader.read(1), timeout=1)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return ret_data
|
return b"{}"
|
||||||
|
try:
|
||||||
|
ret_data += await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||||
|
except ConnectionAbortedError:
|
||||||
|
return b"{}"
|
||||||
|
|
||||||
# loop to receive all the data
|
# loop to receive all the data
|
||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||||
@@ -254,6 +257,12 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# this is an error
|
# this is an error
|
||||||
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
||||||
elif "id" not in data.keys():
|
elif "id" not in data.keys():
|
||||||
|
if isinstance(data["STATUS"], list):
|
||||||
|
if data["STATUS"][0].get("STATUS", None) in ["S", "I"]:
|
||||||
|
return True, None
|
||||||
|
else:
|
||||||
|
return False, data["STATUS"][0]["Msg"]
|
||||||
|
|
||||||
if data["STATUS"] not in ["S", "I"]:
|
if data["STATUS"] not in ["S", "I"]:
|
||||||
return False, data["Msg"]
|
return False, data["Msg"]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ from typing import Literal, Union
|
|||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.misc import api_min_version
|
from pyasic.misc import api_min_version
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
### IMPORTANT ###
|
### IMPORTANT ###
|
||||||
# you need to change the password of the miners using the Whatsminer
|
# you need to change the password of the miners using the Whatsminer
|
||||||
@@ -192,7 +192,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
ip: str,
|
ip: str,
|
||||||
api_ver: str = "0.0.0",
|
api_ver: str = "0.0.0",
|
||||||
port: int = 4028,
|
port: int = 4028,
|
||||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
pwd: str = settings.get("default_whatsminer_password", "admin"),
|
||||||
):
|
):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.pwd = pwd
|
self.pwd = pwd
|
||||||
@@ -487,6 +487,34 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_privileged_command("set_low_power")
|
return await self.send_privileged_command("set_low_power")
|
||||||
|
|
||||||
|
async def set_high_power(self) -> dict:
|
||||||
|
"""Set low power mode on the miner using the API.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set low power mode on the miner using the API, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting low power mode.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_privileged_command("set_high_power")
|
||||||
|
|
||||||
|
async def set_normal_power(self) -> dict:
|
||||||
|
"""Set low power mode on the miner using the API.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set low power mode on the miner using the API, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting low power mode.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_privileged_command("set_normal_power")
|
||||||
|
|
||||||
async def update_firmware(self): # noqa - static
|
async def update_firmware(self): # noqa - static
|
||||||
"""Not implemented."""
|
"""Not implemented."""
|
||||||
# to be determined if this will be added later
|
# to be determined if this will be added later
|
||||||
@@ -735,6 +763,34 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
### ADDED IN V2.0.5 Whatsminer API ###
|
### ADDED IN V2.0.5 Whatsminer API ###
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def set_power_pct_v2(self, percent: int) -> dict:
|
||||||
|
"""Set the power percentage of the miner based on current power. Used for temporary adjustment. Added in API v2.0.5.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set the power percentage of the miner, only works after changing
|
||||||
|
the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
percent: The power percentage to set.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting the power percentage.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 < percent < 100:
|
||||||
|
raise APIError(
|
||||||
|
f"Power PCT % is outside of the allowed "
|
||||||
|
f"range. Please set a % between 0 and "
|
||||||
|
f"100"
|
||||||
|
)
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"set_power_pct_v2", percent=str(percent)
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def set_temp_offset(self, temp_offset: int) -> dict:
|
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||||
"""Set the offset of miner hash board target temperature.
|
"""Set the offset of miner hash board target temperature.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
from pyasic.API.bmminer import BMMinerAPI
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
from pyasic.API.bosminer import BOSMinerAPI
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
@@ -29,10 +30,9 @@ from pyasic.data import (
|
|||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
from pyasic.miners import get_miner
|
from pyasic.miners import get_miner
|
||||||
from pyasic.miners.base import AnyMiner
|
from pyasic.miners.base import AnyMiner
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic.miners.miner_factory import MinerFactory, miner_factory
|
||||||
from pyasic.miners.miner_listener import MinerListener
|
from pyasic.miners.miner_listener import MinerListener
|
||||||
from pyasic.network import MinerNetwork
|
from pyasic.network import MinerNetwork
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BMMinerAPI",
|
"BMMinerAPI",
|
||||||
@@ -51,7 +51,8 @@ __all__ = [
|
|||||||
"get_miner",
|
"get_miner",
|
||||||
"AnyMiner",
|
"AnyMiner",
|
||||||
"MinerFactory",
|
"MinerFactory",
|
||||||
|
"miner_factory",
|
||||||
"MinerListener",
|
"MinerListener",
|
||||||
"MinerNetwork",
|
"MinerNetwork",
|
||||||
"PyasicSettings",
|
"settings",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,664 +13,161 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from copy import deepcopy
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
|
||||||
import logging
|
from pyasic.config.fans import FanModeConfig
|
||||||
import random
|
from pyasic.config.mining import MiningModeConfig
|
||||||
import string
|
from pyasic.config.pools import PoolConfig
|
||||||
import time
|
from pyasic.config.power_scaling import PowerScalingConfig, PowerScalingShutdown
|
||||||
from dataclasses import asdict, dataclass, fields
|
from pyasic.config.temperature import TemperatureConfig
|
||||||
from enum import IntEnum
|
|
||||||
from typing import List, Literal
|
|
||||||
|
|
||||||
import toml
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
class X19PowerMode(IntEnum):
|
|
||||||
Normal = 0
|
|
||||||
Sleep = 1
|
|
||||||
LPM = 3
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _Pool:
|
|
||||||
"""A dataclass for pool information.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
url: URL of the pool.
|
|
||||||
username: Username on the pool.
|
|
||||||
password: Worker password on the pool.
|
|
||||||
"""
|
|
||||||
|
|
||||||
url: str = ""
|
|
||||||
username: str = ""
|
|
||||||
password: str = ""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fields(cls):
|
|
||||||
return fields(cls)
|
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
|
||||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The raw config data to convert.
|
|
||||||
"""
|
|
||||||
for key in data.keys():
|
|
||||||
if key == "url":
|
|
||||||
self.url = data[key]
|
|
||||||
if key in ["user", "username"]:
|
|
||||||
self.username = data[key]
|
|
||||||
if key in ["pass", "password"]:
|
|
||||||
self.password = data[key]
|
|
||||||
return self
|
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an Whatsminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an X19 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_x17(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an X5 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by a goldshell device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {
|
|
||||||
f"Pool": self.url,
|
|
||||||
f"UserName": username,
|
|
||||||
f"Password": self.password,
|
|
||||||
}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
|
||||||
"""Convert the data in this class to a string usable by an Avalonminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = ",".join([self.url, username, self.password])
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_bos(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
username = self.username
|
|
||||||
if user_suffix:
|
|
||||||
username = f"{username}{user_suffix}"
|
|
||||||
|
|
||||||
pool = {"url": self.url, "user": username, "password": self.password}
|
|
||||||
return pool
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _PoolGroup:
|
|
||||||
"""A dataclass for pool group information.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
quota: The group quota.
|
|
||||||
group_name: The name of the pool group.
|
|
||||||
pools: A list of pools in this group.
|
|
||||||
"""
|
|
||||||
|
|
||||||
quota: int = 1
|
|
||||||
group_name: str = None
|
|
||||||
pools: List[_Pool] = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fields(cls):
|
|
||||||
return fields(cls)
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
if not self.group_name:
|
|
||||||
self.group_name = "".join(
|
|
||||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
|
||||||
) # generate random pool group name in case it isn't set
|
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
|
||||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The raw config data to convert.
|
|
||||||
"""
|
|
||||||
pools = []
|
|
||||||
for key in data.keys():
|
|
||||||
if key in ["name", "group_name"]:
|
|
||||||
self.group_name = data[key]
|
|
||||||
if key == "quota":
|
|
||||||
self.quota = data[key]
|
|
||||||
if key in ["pools", "pool"]:
|
|
||||||
for pool in data[key]:
|
|
||||||
pools.append(_Pool().from_dict(pool))
|
|
||||||
self.pools = pools
|
|
||||||
return self
|
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None) -> List[dict]:
|
|
||||||
"""Convert the data in this class to a list usable by an X19 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
pools = []
|
|
||||||
for pool in self.pools[:3]:
|
|
||||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
|
||||||
return pools
|
|
||||||
|
|
||||||
def as_x17(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a list usable by an X17 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
pools = {
|
|
||||||
"_ant_pool1url": "",
|
|
||||||
"_ant_pool1user": "",
|
|
||||||
"_ant_pool1pw": "",
|
|
||||||
"_ant_pool2url": "",
|
|
||||||
"_ant_pool2user": "",
|
|
||||||
"_ant_pool2pw": "",
|
|
||||||
"_ant_pool3url": "",
|
|
||||||
"_ant_pool3user": "",
|
|
||||||
"_ant_pool3pw": "",
|
|
||||||
}
|
|
||||||
for idx, pool in enumerate(self.pools[:3]):
|
|
||||||
pools[f"_ant_pool{idx+1}url"] = pool.as_x17(user_suffix=user_suffix)["url"]
|
|
||||||
pools[f"_ant_pool{idx+1}user"] = pool.as_x17(user_suffix=user_suffix)[
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
pools[f"_ant_pool{idx+1}pw"] = pool.as_x17(user_suffix=user_suffix)["pass"]
|
|
||||||
|
|
||||||
return pools
|
|
||||||
|
|
||||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
|
||||||
"""Convert the data in this class to a list usable by a goldshell device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
return [pool.as_goldshell(user_suffix=user_suffix) for pool in self.pools[:3]]
|
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a list usable by an Innosilicon device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
pools = {
|
|
||||||
"Pool1": None,
|
|
||||||
"UserName1": None,
|
|
||||||
"Password1": None,
|
|
||||||
"Pool2": None,
|
|
||||||
"UserName2": None,
|
|
||||||
"Password2": None,
|
|
||||||
"Pool3": None,
|
|
||||||
"UserName3": None,
|
|
||||||
"Password3": None,
|
|
||||||
}
|
|
||||||
for idx, pool in enumerate(self.pools[:3]):
|
|
||||||
pool_data = pool.as_inno(user_suffix=user_suffix)
|
|
||||||
for key in pool_data:
|
|
||||||
pools[f"{key}{idx+1}"] = pool_data[key]
|
|
||||||
return pools
|
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a list usable by a Whatsminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
pools = {}
|
|
||||||
for i in range(1, 4):
|
|
||||||
if i <= len(self.pools):
|
|
||||||
pool_wm = self.pools[i - 1].as_wm(user_suffix)
|
|
||||||
pools[f"pool_{i}"] = pool_wm["url"]
|
|
||||||
pools[f"worker_{i}"] = pool_wm["user"]
|
|
||||||
pools[f"passwd_{i}"] = pool_wm["pass"]
|
|
||||||
else:
|
|
||||||
pools[f"pool_{i}"] = ""
|
|
||||||
pools[f"worker_{i}"] = ""
|
|
||||||
pools[f"passwd_{i}"] = ""
|
|
||||||
return pools
|
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
|
||||||
"""Convert the data in this class to a dict usable by an Avalonminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
|
||||||
return pool
|
|
||||||
|
|
||||||
def as_bos(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
group = {
|
|
||||||
"name": self.group_name,
|
|
||||||
"quota": self.quota,
|
|
||||||
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
|
|
||||||
}
|
|
||||||
return group
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MinerConfig:
|
class MinerConfig:
|
||||||
"""A dataclass for miner configuration information.
|
pools: PoolConfig = field(default_factory=PoolConfig.default)
|
||||||
|
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default)
|
||||||
Attributes:
|
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
|
||||||
pool_groups: A list of pool groups in this config.
|
mining_mode: MiningModeConfig = field(default_factory=MiningModeConfig.default)
|
||||||
temp_mode: The temperature control mode.
|
power_scaling: PowerScalingConfig = field(
|
||||||
temp_target: The target temp.
|
default_factory=PowerScalingConfig.default
|
||||||
temp_hot: The hot temp (100% fans).
|
)
|
||||||
temp_dangerous: The dangerous temp (shutdown).
|
|
||||||
minimum_fans: The minimum numbers of fans to run the miner.
|
|
||||||
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
|
||||||
asicboost: Whether or not to enable asicboost.
|
|
||||||
autotuning_enabled: Whether or not to enable autotuning.
|
|
||||||
autotuning_mode: Autotuning mode, either "wattage" or "hashrate".
|
|
||||||
autotuning_wattage: The wattage to use when autotuning.
|
|
||||||
autotuning_hashrate: The hashrate to use when autotuning.
|
|
||||||
dps_enabled: Whether or not to enable dynamic power scaling.
|
|
||||||
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
|
||||||
dps_min_power: The minimum power to reduce autotuning to.
|
|
||||||
dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
|
||||||
dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
|
||||||
"""
|
|
||||||
|
|
||||||
pool_groups: List[_PoolGroup] = None
|
|
||||||
|
|
||||||
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
|
|
||||||
temp_target: float = 70.0
|
|
||||||
temp_hot: float = 80.0
|
|
||||||
temp_dangerous: float = 100.0
|
|
||||||
|
|
||||||
minimum_fans: int = None
|
|
||||||
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
|
|
||||||
|
|
||||||
asicboost: bool = None
|
|
||||||
|
|
||||||
miner_mode: IntEnum = X19PowerMode.Normal
|
|
||||||
autotuning_enabled: bool = True
|
|
||||||
autotuning_mode: Literal["power", "hashrate"] = None
|
|
||||||
autotuning_wattage: int = None
|
|
||||||
autotuning_hashrate: int = None
|
|
||||||
|
|
||||||
dps_enabled: bool = None
|
|
||||||
dps_power_step: int = None
|
|
||||||
dps_min_power: int = None
|
|
||||||
dps_shutdown_enabled: bool = None
|
|
||||||
dps_shutdown_duration: float = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fields(cls):
|
|
||||||
return fields(cls)
|
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
"""Convert the data in this class to a dict."""
|
return asdict(self)
|
||||||
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
|
|
||||||
data_dict = asdict(self)
|
|
||||||
for key in asdict(self).keys():
|
|
||||||
if isinstance(data_dict[key], IntEnum):
|
|
||||||
data_dict[key] = data_dict[key].value
|
|
||||||
if data_dict[key] is None:
|
|
||||||
del data_dict[key]
|
|
||||||
return data_dict
|
|
||||||
|
|
||||||
def as_toml(self) -> str:
|
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to toml."""
|
return {
|
||||||
logging.debug(f"MinerConfig - (To TOML) - Dumping TOML config")
|
**self.fan_mode.as_am_modern(),
|
||||||
return toml.dumps(self.as_dict())
|
"freq-level": "100",
|
||||||
|
**self.mining_mode.as_am_modern(),
|
||||||
def as_yaml(self) -> str:
|
**self.pools.as_am_modern(user_suffix=user_suffix),
|
||||||
"""Convert the data in this class to yaml."""
|
**self.temperature.as_am_modern(),
|
||||||
logging.debug(f"MinerConfig - (To YAML) - Dumping YAML config")
|
**self.power_scaling.as_am_modern(),
|
||||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
}
|
||||||
|
|
||||||
def from_raw(self, data: dict):
|
|
||||||
"""Convert raw config data as a dict to usable data and save it to this class.
|
|
||||||
This should be able to handle any raw config file from any miner supported by pyasic.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The raw config data to convert.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
|
|
||||||
pool_groups = []
|
|
||||||
if isinstance(data, list):
|
|
||||||
# goldshell config list
|
|
||||||
data = {"pools": data}
|
|
||||||
for key in data.keys():
|
|
||||||
if key == "pools":
|
|
||||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
|
||||||
elif key == "group":
|
|
||||||
for group in data[key]:
|
|
||||||
pool_groups.append(_PoolGroup().from_dict(group))
|
|
||||||
|
|
||||||
if key == "bitmain-fan-ctrl":
|
|
||||||
if data[key]:
|
|
||||||
self.temp_mode = "manual"
|
|
||||||
if data.get("bitmain-fan-pwm"):
|
|
||||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
|
||||||
elif key == "bitmain-work-mode":
|
|
||||||
if data[key]:
|
|
||||||
self.miner_mode = X19PowerMode(int(data[key]))
|
|
||||||
elif key == "fan_control":
|
|
||||||
for _key in data[key]:
|
|
||||||
if _key == "min_fans":
|
|
||||||
self.minimum_fans = data[key][_key]
|
|
||||||
elif _key == "speed":
|
|
||||||
self.fan_speed = data[key][_key]
|
|
||||||
elif key == "temp_control":
|
|
||||||
for _key in data[key]:
|
|
||||||
if _key == "mode":
|
|
||||||
self.temp_mode = data[key][_key]
|
|
||||||
elif _key == "target_temp":
|
|
||||||
self.temp_target = data[key][_key]
|
|
||||||
elif _key == "hot_temp":
|
|
||||||
self.temp_hot = data[key][_key]
|
|
||||||
elif _key == "dangerous_temp":
|
|
||||||
self.temp_dangerous = data[key][_key]
|
|
||||||
|
|
||||||
if key == "hash_chain_global":
|
|
||||||
if data[key].get("asic_boost"):
|
|
||||||
self.asicboost = data[key]["asic_boost"]
|
|
||||||
|
|
||||||
if key == "autotuning":
|
|
||||||
for _key in data[key]:
|
|
||||||
if _key == "enabled":
|
|
||||||
self.autotuning_enabled = data[key][_key]
|
|
||||||
elif _key == "psu_power_limit":
|
|
||||||
self.autotuning_wattage = data[key][_key]
|
|
||||||
elif _key == "power_target":
|
|
||||||
self.autotuning_wattage = data[key][_key]
|
|
||||||
elif _key == "hashrate_target":
|
|
||||||
self.autotuning_hashrate = data[key][_key]
|
|
||||||
elif _key == "mode":
|
|
||||||
self.autotuning_mode = data[key][_key].replace("_target", "")
|
|
||||||
|
|
||||||
if key in ["power_scaling", "performance_scaling"]:
|
|
||||||
for _key in data[key]:
|
|
||||||
if _key == "enabled":
|
|
||||||
self.dps_enabled = data[key][_key]
|
|
||||||
elif _key == "power_step":
|
|
||||||
self.dps_power_step = data[key][_key]
|
|
||||||
elif _key in ["min_psu_power_limit", "min_power_target"]:
|
|
||||||
self.dps_min_power = data[key][_key]
|
|
||||||
elif _key == "shutdown_enabled":
|
|
||||||
self.dps_shutdown_enabled = data[key][_key]
|
|
||||||
elif _key == "shutdown_duration":
|
|
||||||
self.dps_shutdown_duration = data[key][_key]
|
|
||||||
|
|
||||||
self.pool_groups = pool_groups
|
|
||||||
return self
|
|
||||||
|
|
||||||
def from_api(self, pools: list):
|
|
||||||
"""Convert list output from the `AnyMiner.api.pools()` command into a usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
pools: The list of pool data to convert.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (From API) - Loading API config")
|
|
||||||
_pools = []
|
|
||||||
for pool in pools:
|
|
||||||
url = pool.get("URL")
|
|
||||||
user = pool.get("User")
|
|
||||||
_pools.append({"url": url, "user": user, "pass": "123"})
|
|
||||||
self.pool_groups = [_PoolGroup().from_dict({"pools": _pools})]
|
|
||||||
return self
|
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
|
||||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The dict config data to convert.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (From Dict) - Loading Dict config")
|
|
||||||
pool_groups = []
|
|
||||||
for group in data["pool_groups"]:
|
|
||||||
pool_groups.append(_PoolGroup().from_dict(group))
|
|
||||||
for key in data:
|
|
||||||
if (
|
|
||||||
hasattr(self, key)
|
|
||||||
and not key == "pool_groups"
|
|
||||||
and not key == "miner_mode"
|
|
||||||
):
|
|
||||||
setattr(self, key, data[key])
|
|
||||||
if key == "miner_mode":
|
|
||||||
self.miner_mode = X19PowerMode(data[key])
|
|
||||||
self.pool_groups = pool_groups
|
|
||||||
return self
|
|
||||||
|
|
||||||
def from_toml(self, data: str):
|
|
||||||
"""Convert output toml of this class back into usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The toml config data to convert.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (From TOML) - Loading TOML config")
|
|
||||||
return self.from_dict(toml.loads(data))
|
|
||||||
|
|
||||||
def from_yaml(self, data: str):
|
|
||||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: The yaml config data to convert.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
|
|
||||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str = None) -> dict:
|
def as_wm(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a config usable by a Whatsminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
|
||||||
return {
|
return {
|
||||||
"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix),
|
**self.fan_mode.as_wm(),
|
||||||
"wattage": self.autotuning_wattage,
|
**self.mining_mode.as_wm(),
|
||||||
|
**self.pools.as_wm(user_suffix=user_suffix),
|
||||||
|
**self.temperature.as_wm(),
|
||||||
|
**self.power_scaling.as_wm(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||||
|
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) -> dict:
|
||||||
|
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) -> dict:
|
||||||
|
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) -> dict:
|
def as_inno(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a config usable by an Innosilicon device.
|
return {
|
||||||
|
**self.fan_mode.as_inno(),
|
||||||
Parameters:
|
**self.mining_mode.as_inno(),
|
||||||
user_suffix: The suffix to append to username.
|
**self.pools.as_inno(user_suffix=user_suffix),
|
||||||
"""
|
**self.temperature.as_inno(),
|
||||||
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
|
**self.power_scaling.as_inno(),
|
||||||
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a config usable by an X19 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (As X19) - Generating X19 config")
|
|
||||||
cfg = {
|
|
||||||
"bitmain-fan-ctrl": False,
|
|
||||||
"bitmain-fan-pwn": "100",
|
|
||||||
"freq-level": "100",
|
|
||||||
"miner-mode": str(self.miner_mode.value),
|
|
||||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if not self.temp_mode == "auto":
|
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||||
cfg["bitmain-fan-ctrl"] = True
|
return {
|
||||||
|
**merge(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||||
if self.fan_speed:
|
**self.mining_mode.as_bosminer(),
|
||||||
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
**self.pools.as_bosminer(user_suffix=user_suffix),
|
||||||
|
**self.power_scaling.as_bosminer(),
|
||||||
return cfg
|
|
||||||
|
|
||||||
def as_x17(self, user_suffix: str = None) -> dict:
|
|
||||||
"""Convert the data in this class to a config usable by an X5 device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
cfg = self.pool_groups[0].as_x17(user_suffix=user_suffix)
|
|
||||||
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
|
||||||
"""Convert the data in this class to a config usable by a goldshell device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
cfg = self.pool_groups[0].as_goldshell(user_suffix=user_suffix)
|
|
||||||
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
|
||||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (As Avalon) - Generating AvalonMiner config")
|
|
||||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
|
||||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
model: The model of the miner to be used in the format portion of the config.
|
|
||||||
user_suffix: The suffix to append to username.
|
|
||||||
"""
|
|
||||||
logging.debug(f"MinerConfig - (As BOS) - Generating BOSMiner config")
|
|
||||||
cfg = {
|
|
||||||
"format": {
|
|
||||||
"version": "1.2+",
|
|
||||||
"model": f"Antminer {model.replace('j', 'J')}",
|
|
||||||
"generator": "pyasic",
|
|
||||||
"timestamp": int(time.time()),
|
|
||||||
},
|
|
||||||
"group": [
|
|
||||||
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
|
|
||||||
],
|
|
||||||
"temp_control": {
|
|
||||||
"mode": self.temp_mode,
|
|
||||||
"target_temp": self.temp_target,
|
|
||||||
"hot_temp": self.temp_hot,
|
|
||||||
"dangerous_temp": self.temp_dangerous,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.autotuning_enabled or self.autotuning_wattage:
|
def as_bos_grpc(self, user_suffix: str = None) -> dict:
|
||||||
cfg["autotuning"] = {}
|
return {
|
||||||
if self.autotuning_enabled:
|
**self.fan_mode.as_bos_grpc(),
|
||||||
cfg["autotuning"]["enabled"] = True
|
**self.temperature.as_bos_grpc(),
|
||||||
else:
|
**self.mining_mode.as_bos_grpc(),
|
||||||
cfg["autotuning"]["enabled"] = False
|
**self.pools.as_bos_grpc(user_suffix=user_suffix),
|
||||||
if self.autotuning_mode:
|
**self.power_scaling.as_bos_grpc(),
|
||||||
cfg["format"]["version"] = "2.0"
|
}
|
||||||
cfg["autotuning"]["mode"] = self.autotuning_mode + "_target"
|
|
||||||
if self.autotuning_wattage:
|
|
||||||
cfg["autotuning"]["power_target"] = self.autotuning_wattage
|
|
||||||
elif self.autotuning_hashrate:
|
|
||||||
cfg["autotuning"]["hashrate_target"] = self.autotuning_hashrate
|
|
||||||
else:
|
|
||||||
if self.autotuning_wattage:
|
|
||||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
|
||||||
|
|
||||||
if self.asicboost:
|
def as_epic(self, user_suffix: str = None) -> dict:
|
||||||
cfg["hash_chain_global"] = {}
|
return {
|
||||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
**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(),
|
||||||
|
}
|
||||||
|
|
||||||
if self.minimum_fans is not None or self.fan_speed is not None:
|
@classmethod
|
||||||
cfg["fan_control"] = {}
|
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||||
if self.minimum_fans is not None:
|
return cls(
|
||||||
cfg["fan_control"]["min_fans"] = self.minimum_fans
|
pools=PoolConfig.from_dict(dict_conf.get("pools")),
|
||||||
if self.fan_speed is not None:
|
mining_mode=MiningModeConfig.from_dict(dict_conf.get("mining_mode")),
|
||||||
cfg["fan_control"]["speed"] = self.fan_speed
|
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")),
|
||||||
|
)
|
||||||
|
|
||||||
if any(
|
@classmethod
|
||||||
[
|
def from_api(cls, api_pools: dict) -> "MinerConfig":
|
||||||
getattr(self, item)
|
return cls(pools=PoolConfig.from_api(api_pools))
|
||||||
for item in [
|
|
||||||
"dps_enabled",
|
|
||||||
"dps_power_step",
|
|
||||||
"dps_min_power",
|
|
||||||
"dps_shutdown_enabled",
|
|
||||||
"dps_shutdown_duration",
|
|
||||||
]
|
|
||||||
]
|
|
||||||
):
|
|
||||||
cfg["power_scaling"] = {}
|
|
||||||
if self.dps_enabled:
|
|
||||||
cfg["power_scaling"]["enabled"] = self.dps_enabled
|
|
||||||
if self.dps_power_step:
|
|
||||||
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
|
||||||
if self.dps_min_power:
|
|
||||||
if cfg["format"]["version"] == "2.0":
|
|
||||||
cfg["power_scaling"]["min_power_target"] = self.dps_min_power
|
|
||||||
else:
|
|
||||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
|
||||||
if self.dps_shutdown_enabled:
|
|
||||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
|
||||||
if self.dps_shutdown_duration:
|
|
||||||
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
|
|
||||||
|
|
||||||
return toml.dumps(cfg)
|
@classmethod
|
||||||
|
def from_am_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||||
|
return cls(
|
||||||
|
pools=PoolConfig.from_am_modern(web_conf),
|
||||||
|
mining_mode=MiningModeConfig.from_am_modern(web_conf),
|
||||||
|
fan_mode=FanModeConfig.from_am_modern(web_conf),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||||
|
return cls.from_am_modern(web_conf)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_goldshell(cls, web_conf: dict) -> "MinerConfig":
|
||||||
|
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||||
|
return cls(pools=PoolConfig.from_inno(web_pools))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bosminer(cls, toml_conf: dict) -> "MinerConfig":
|
||||||
|
return cls(
|
||||||
|
pools=PoolConfig.from_bosminer(toml_conf),
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def merge(a: dict, b: dict) -> dict:
|
||||||
|
result = deepcopy(a)
|
||||||
|
for b_key, b_val in b.items():
|
||||||
|
a_val = result.get(b_key)
|
||||||
|
if isinstance(a_val, dict) and isinstance(b_val, dict):
|
||||||
|
result[b_key] = merge(a_val, b_val)
|
||||||
|
else:
|
||||||
|
result[b_key] = deepcopy(b_val)
|
||||||
|
return result
|
||||||
|
|||||||
95
pyasic/config/base.py
Normal file
95
pyasic/config/base.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
class MinerConfigOption(Enum):
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return self.value.as_am_modern()
|
||||||
|
|
||||||
|
def as_am_old(self) -> dict:
|
||||||
|
return self.value.as_am_old()
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return self.value.as_wm()
|
||||||
|
|
||||||
|
def as_inno(self) -> dict:
|
||||||
|
return self.value.as_inno()
|
||||||
|
|
||||||
|
def as_goldshell(self) -> dict:
|
||||||
|
return self.value.as_goldshell()
|
||||||
|
|
||||||
|
def as_avalon(self) -> dict:
|
||||||
|
return self.value.as_avalon()
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return self.value.as_bosminer()
|
||||||
|
|
||||||
|
def as_bos_grpc(self) -> dict:
|
||||||
|
return self.value.as_bos_grpc()
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return self.value.as_epic()
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self.value(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MinerConfigValue:
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_am_old(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_inno(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_goldshell(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_avalon(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_bos_grpc(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {}
|
||||||
134
pyasic/config/fans.py
Normal file
134
pyasic/config/fans.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FanModeNormal(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="normal")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return {"temp_control": {"mode": "auto"}}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FanModeManual(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="manual")
|
||||||
|
speed: int = 100
|
||||||
|
minimum_fans: int = 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeManual":
|
||||||
|
cls_conf = {}
|
||||||
|
if dict_conf.get("speed") is not None:
|
||||||
|
cls_conf["speed"] = dict_conf["speed"]
|
||||||
|
if dict_conf.get("minimum_fans") is not None:
|
||||||
|
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||||
|
return cls(**cls_conf)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bosminer(cls, toml_fan_conf: dict) -> "FanModeManual":
|
||||||
|
cls_conf = {}
|
||||||
|
if toml_fan_conf.get("min_fans") is not None:
|
||||||
|
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||||
|
if toml_fan_conf.get("speed") is not None:
|
||||||
|
cls_conf["speed"] = toml_fan_conf["speed"]
|
||||||
|
return cls(**cls_conf)
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)}
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return {
|
||||||
|
"temp_control": {"mode": "manual"},
|
||||||
|
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FanModeImmersion(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="immersion")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeImmersion":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": "0"}
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return {"temp_control": {"mode": "disabled"}}
|
||||||
|
|
||||||
|
|
||||||
|
class FanModeConfig(MinerConfigOption):
|
||||||
|
normal = FanModeNormal
|
||||||
|
manual = FanModeManual
|
||||||
|
immersion = FanModeImmersion
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
return cls.normal()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[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_am_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:
|
||||||
|
return cls.manual(speed=web_conf["bitmain-fan-pwm"])
|
||||||
|
else:
|
||||||
|
return cls.normal()
|
||||||
|
else:
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bosminer(cls, toml_conf: dict):
|
||||||
|
if toml_conf.get("temp_control") is None:
|
||||||
|
return cls.default()
|
||||||
|
if toml_conf["temp_control"].get("mode") is None:
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
mode = toml_conf["temp_control"]["mode"]
|
||||||
|
if mode == "auto":
|
||||||
|
return cls.normal()
|
||||||
|
elif mode == "manual":
|
||||||
|
if toml_conf.get("fan_control"):
|
||||||
|
return cls.manual().from_bosminer(toml_conf["fan_control"])
|
||||||
|
return cls.manual()
|
||||||
|
elif mode == "disabled":
|
||||||
|
return cls.immersion()
|
||||||
213
pyasic/config/mining.py
Normal file
213
pyasic/config/mining.py
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModeNormal(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="normal")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeNormal":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"miner-mode": "0"}
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModeSleep(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="sleep")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeSleep":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"miner-mode": "1"}
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModeLPM(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="low")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeLPM":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"miner-mode": "3"}
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModeHPM(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="high")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHPM":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_am_modern(self):
|
||||||
|
return {"miner-mode": "0"}
|
||||||
|
|
||||||
|
def as_wm(self) -> dict:
|
||||||
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModePowerTune(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="power_tuning")
|
||||||
|
power: int = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModePowerTune":
|
||||||
|
return cls(dict_conf.get("power"))
|
||||||
|
|
||||||
|
def as_am_modern(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_bosminer(self) -> dict:
|
||||||
|
return {"autotuning": {"enabled": True, "psu_power_limit": self.power}}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiningModeHashrateTune(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="hashrate_tuning")
|
||||||
|
hashrate: int = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHashrateTune":
|
||||||
|
return cls(dict_conf.get("hashrate"))
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
return {"miner-mode": "0"}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ManualBoardSettings(MinerConfigValue):
|
||||||
|
freq: float
|
||||||
|
volt: float
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "ManualBoardSettings":
|
||||||
|
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||||
|
|
||||||
|
def as_am_modern(self) -> dict:
|
||||||
|
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: Union[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:
|
||||||
|
return {"miner-mode": "0"}
|
||||||
|
|
||||||
|
|
||||||
|
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: Union[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_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_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()
|
||||||
356
pyasic/config/pools.py
Normal file
356
pyasic/config/pools.py
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyasic.config.base import MinerConfigValue
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Pool(MinerConfigValue):
|
||||||
|
url: str
|
||||||
|
user: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
def as_am_modern(self, user_suffix: str = None):
|
||||||
|
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_wm(self, idx: int, user_suffix: str = None):
|
||||||
|
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}": self.user,
|
||||||
|
f"passwd_{idx}": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
def as_am_old(self, idx: int, user_suffix: str = None):
|
||||||
|
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": self.user,
|
||||||
|
f"_ant_pool{idx}pw": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
def as_goldshell(self, user_suffix: str = None):
|
||||||
|
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):
|
||||||
|
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, user_suffix: str = None):
|
||||||
|
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}": self.user,
|
||||||
|
f"Password{idx}": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
def as_bosminer(self, user_suffix: str = None):
|
||||||
|
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}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool":
|
||||||
|
return cls(
|
||||||
|
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api(cls, api_pool: dict) -> "Pool":
|
||||||
|
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_am_modern(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":
|
||||||
|
return cls(
|
||||||
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_inno(cls, web_pool: dict) -> "Pool":
|
||||||
|
return cls(
|
||||||
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bosminer(cls, toml_pool_conf: dict) -> "Pool":
|
||||||
|
return cls(
|
||||||
|
url=toml_pool_conf["url"],
|
||||||
|
user=toml_pool_conf["user"],
|
||||||
|
password=toml_pool_conf["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PoolGroup(MinerConfigValue):
|
||||||
|
pools: list[Pool] = field(default_factory=list)
|
||||||
|
quota: int = 1
|
||||||
|
name: str = None
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.name is None:
|
||||||
|
self.name = "".join(
|
||||||
|
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) -> 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("", "", "").as_am_modern())
|
||||||
|
idx += 1
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def as_wm(self, user_suffix: str = None) -> dict:
|
||||||
|
pools = {}
|
||||||
|
idx = 0
|
||||||
|
while idx < 3:
|
||||||
|
if len(self.pools) > idx:
|
||||||
|
pools.update(
|
||||||
|
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pools.update(**Pool("", "", "").as_wm(idx=idx + 1))
|
||||||
|
idx += 1
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||||
|
pools = {}
|
||||||
|
idx = 0
|
||||||
|
while idx < 3:
|
||||||
|
if len(self.pools) > idx:
|
||||||
|
pools.update(
|
||||||
|
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1))
|
||||||
|
idx += 1
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||||
|
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
||||||
|
|
||||||
|
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||||
|
if len(self.pools) > 0:
|
||||||
|
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||||
|
return Pool("", "", "").as_avalon()
|
||||||
|
|
||||||
|
def as_inno(self, user_suffix: str = None) -> dict:
|
||||||
|
pools = {}
|
||||||
|
idx = 0
|
||||||
|
while idx < 3:
|
||||||
|
if len(self.pools) > idx:
|
||||||
|
pools.update(
|
||||||
|
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pools.update(**Pool("", "", "").as_inno(idx=idx + 1))
|
||||||
|
idx += 1
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||||
|
if len(self.pools) > 0:
|
||||||
|
conf = {
|
||||||
|
"name": self.name,
|
||||||
|
"pool": [
|
||||||
|
pool.as_bosminer(user_suffix=user_suffix) for pool in self.pools
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if self.quota is not None:
|
||||||
|
conf["quota"] = self.quota
|
||||||
|
return conf
|
||||||
|
return {"name": "Group", "pool": []}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup":
|
||||||
|
cls_conf = {}
|
||||||
|
|
||||||
|
if dict_conf.get("quota") is not None:
|
||||||
|
cls_conf["quota"] = dict_conf["quota"]
|
||||||
|
if dict_conf.get("name") is not None:
|
||||||
|
cls_conf["name"] = dict_conf["name"]
|
||||||
|
cls_conf["pools"] = [Pool.from_dict(p) for p in dict_conf["pools"]]
|
||||||
|
return cls(**cls_conf)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api(cls, api_pool_list: list) -> "PoolGroup":
|
||||||
|
pools = []
|
||||||
|
for pool in api_pool_list:
|
||||||
|
pools.append(Pool.from_api(pool))
|
||||||
|
return cls(pools=pools)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_am_modern(cls, web_pool_list: list) -> "PoolGroup":
|
||||||
|
pools = []
|
||||||
|
for pool in web_pool_list:
|
||||||
|
pools.append(Pool.from_am_modern(pool))
|
||||||
|
return cls(pools=pools)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
||||||
|
return cls([Pool.from_goldshell(p) for p in web_pools])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_inno(cls, web_pools: list) -> "PoolGroup":
|
||||||
|
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"),
|
||||||
|
pools=[Pool.from_bosminer(p) for p in toml_group_conf["pool"]],
|
||||||
|
)
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PoolConfig(MinerConfigValue):
|
||||||
|
groups: list[PoolGroup] = field(default_factory=list)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls) -> "PoolConfig":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolConfig":
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def simple(cls, pools: list[Union[Pool, dict[str, str]]]) -> "PoolConfig":
|
||||||
|
group_pools = []
|
||||||
|
for pool in pools:
|
||||||
|
if isinstance(pool, dict):
|
||||||
|
pool = Pool(**pool)
|
||||||
|
group_pools.append(pool)
|
||||||
|
return cls(groups=[PoolGroup(pools=group_pools)])
|
||||||
|
|
||||||
|
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_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_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) -> 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) -> 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) -> 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) -> 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_bos_grpc(self, user_suffix: str = None) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||||
|
pool_data = api_pools["POOLS"]
|
||||||
|
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
|
||||||
|
|
||||||
|
return cls([PoolGroup.from_api(pool_data)])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||||
|
pool_data = web_conf["pools"]
|
||||||
|
|
||||||
|
return cls([PoolGroup.from_am_modern(pool_data)])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||||
|
return cls([PoolGroup.from_goldshell(web_pools)])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
||||||
|
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([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||||
189
pyasic/config/power_scaling.py
Normal file
189
pyasic/config/power_scaling.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
|
from pyasic.web.bosminer.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PowerScalingShutdownEnabled(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="enabled")
|
||||||
|
duration: int = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[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_bos_grpc(self) -> dict:
|
||||||
|
cfg = {"enable_shutdown ": True}
|
||||||
|
|
||||||
|
if self.duration is not None:
|
||||||
|
cfg["shutdown_duration"] = Hours(self.duration)
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PowerScalingShutdownDisabled(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="disabled")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownDisabled":
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
return {"shutdown_enabled": False}
|
||||||
|
|
||||||
|
def as_bos_grpc(self) -> dict:
|
||||||
|
return {"enable_shutdown ": False}
|
||||||
|
|
||||||
|
|
||||||
|
class PowerScalingShutdown(MinerConfigOption):
|
||||||
|
enabled = PowerScalingShutdownEnabled
|
||||||
|
disabled = PowerScalingShutdownDisabled
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[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
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PowerScalingEnabled(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="enabled")
|
||||||
|
power_step: int = None
|
||||||
|
minimum_power: int = None
|
||||||
|
shutdown_enabled: Union[
|
||||||
|
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")
|
||||||
|
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: Union[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_psu_power_limit"] = self.minimum_power
|
||||||
|
|
||||||
|
if self.shutdown_enabled is not None:
|
||||||
|
cfg = {**cfg, **self.shutdown_enabled.as_bosminer()}
|
||||||
|
|
||||||
|
return {"power_scaling": cfg}
|
||||||
|
|
||||||
|
def as_bos_grpc(self) -> dict:
|
||||||
|
cfg = {"enable": True}
|
||||||
|
target_conf = {}
|
||||||
|
if self.power_step is not None:
|
||||||
|
target_conf["power_step"] = self.power_step
|
||||||
|
if self.minimum_power is not None:
|
||||||
|
target_conf["min_power_target"] = self.minimum_power
|
||||||
|
|
||||||
|
cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf))
|
||||||
|
|
||||||
|
if self.shutdown_enabled is not None:
|
||||||
|
cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()}
|
||||||
|
|
||||||
|
return {"dps": cfg}
|
||||||
|
|
||||||
|
|
||||||
|
@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: Union[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 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()
|
||||||
58
pyasic/config/temperature.py
Normal file
58
pyasic/config/temperature.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from pyasic.config.base import MinerConfigValue
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TemperatureConfig(MinerConfigValue):
|
||||||
|
target: int = None
|
||||||
|
hot: int = None
|
||||||
|
danger: int = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
def as_bosminer(self) -> dict:
|
||||||
|
temp_cfg = {}
|
||||||
|
if self.target is not None:
|
||||||
|
temp_cfg["target_temp"] = self.target
|
||||||
|
if self.hot is not None:
|
||||||
|
temp_cfg["hot_temp"] = self.hot
|
||||||
|
if self.danger is not None:
|
||||||
|
temp_cfg["dangerous_temp"] = self.danger
|
||||||
|
return {"temp_control": temp_cfg}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, dict_conf: Union[dict, None]) -> "TemperatureConfig":
|
||||||
|
return cls(
|
||||||
|
target=dict_conf.get("target"),
|
||||||
|
hot=dict_conf.get("hot"),
|
||||||
|
danger=dict_conf.get("danger"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bosminer(cls, toml_conf: dict) -> "TemperatureConfig":
|
||||||
|
temp_control = toml_conf.get("temp_control")
|
||||||
|
if temp_control is not None:
|
||||||
|
return cls(
|
||||||
|
target=temp_control.get("target_temp"),
|
||||||
|
hot=temp_control.get("hot_temp"),
|
||||||
|
danger=temp_control.get("dangerous_temp"),
|
||||||
|
)
|
||||||
@@ -22,6 +22,9 @@ from dataclasses import asdict, dataclass, field, fields
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, List, Union
|
from typing import Any, List, Union
|
||||||
|
|
||||||
|
from pyasic.config import MinerConfig
|
||||||
|
from pyasic.config.mining import MiningModePowerTune
|
||||||
|
|
||||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +38,7 @@ class HashBoard:
|
|||||||
temp: The temperature of the PCB as an int.
|
temp: The temperature of the PCB as an int.
|
||||||
chip_temp: The temperature of the chips as an int.
|
chip_temp: The temperature of the chips as an int.
|
||||||
chips: The chip count of the board as an int.
|
chips: The chip count of the board as an int.
|
||||||
expected_chips: The ideal chip count of the board as an int.
|
expected_chips: The expected chip count of the board as an int.
|
||||||
missing: Whether the board is returned from the miners data as a bool.
|
missing: Whether the board is returned from the miners data as a bool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ class MinerData:
|
|||||||
hostname: The network hostname of 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: The hashrate of the miner in TH/s as a float. Calculated automatically.
|
||||||
_hashrate: Backup for hashrate found via API instead of hashboards.
|
_hashrate: Backup for hashrate found via API instead of hashboards.
|
||||||
nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
expected_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
||||||
hashboards: A list of hashboards on the miner with their statistics.
|
hashboards: A list of hashboards on the miner with their statistics.
|
||||||
temperature_avg: The average temperature across the boards. Calculated automatically.
|
temperature_avg: The average temperature across the boards. Calculated automatically.
|
||||||
env_temp: The environment temps as a float.
|
env_temp: The environment temps as a float.
|
||||||
@@ -114,10 +117,10 @@ class MinerData:
|
|||||||
fans: A list of fans on the miner with their speeds.
|
fans: A list of fans on the miner with their speeds.
|
||||||
fan_psu: The speed of the PSU on the fan if the miner collects it.
|
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.
|
total_chips: The total number of chips on all boards. Calculated automatically.
|
||||||
ideal_chips: The ideal number of chips in the miner as an int.
|
expected_chips: The expected number of chips in the miner as an int.
|
||||||
percent_ideal_chips: The percent of total chips out of the ideal count. Calculated automatically.
|
percent_expected_chips: The percent of total chips out of the expected count. Calculated automatically.
|
||||||
percent_ideal_hashrate: The percent of total hashrate out of the ideal hashrate. Calculated automatically.
|
percent_expected_hashrate: The percent of total hashrate out of the expected hashrate. Calculated automatically.
|
||||||
percent_ideal_wattage: The percent of total wattage out of the ideal wattage. Calculated automatically.
|
percent_expected_wattage: The percent of total wattage out of the expected wattage. Calculated automatically.
|
||||||
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
|
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
|
||||||
pool_split: The pool split as a str.
|
pool_split: The pool split as a str.
|
||||||
pool_1_url: The first pool url on the miner as a str.
|
pool_1_url: The first pool url on the miner as a str.
|
||||||
@@ -140,27 +143,24 @@ class MinerData:
|
|||||||
fw_ver: str = None
|
fw_ver: str = None
|
||||||
hostname: str = None
|
hostname: str = None
|
||||||
hashrate: float = field(init=False)
|
hashrate: float = field(init=False)
|
||||||
_hashrate: float = None
|
_hashrate: float = field(repr=False, default=None)
|
||||||
nominal_hashrate: float = None
|
expected_hashrate: float = None
|
||||||
hashboards: List[HashBoard] = field(default_factory=list)
|
hashboards: List[HashBoard] = field(default_factory=list)
|
||||||
ideal_hashboards: int = None
|
expected_hashboards: int = None
|
||||||
temperature_avg: int = field(init=False)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = None
|
env_temp: float = None
|
||||||
wattage: int = None
|
wattage: int = None
|
||||||
wattage_limit: int = None
|
wattage_limit: int = field(init=False)
|
||||||
|
_wattage_limit: int = field(repr=False, default=None)
|
||||||
fans: List[Fan] = field(default_factory=list)
|
fans: List[Fan] = field(default_factory=list)
|
||||||
fan_psu: int = None
|
fan_psu: int = None
|
||||||
total_chips: int = field(init=False)
|
total_chips: int = field(init=False)
|
||||||
ideal_chips: int = None
|
expected_chips: int = None
|
||||||
percent_ideal_chips: float = field(init=False)
|
percent_expected_chips: float = field(init=False)
|
||||||
percent_ideal_hashrate: float = field(init=False)
|
percent_expected_hashrate: float = field(init=False)
|
||||||
percent_ideal_wattage: float = field(init=False)
|
percent_expected_wattage: float = field(init=False)
|
||||||
nominal: bool = field(init=False)
|
nominal: bool = field(init=False)
|
||||||
pool_split: str = "0"
|
config: MinerConfig = None
|
||||||
pool_1_url: str = "Unknown"
|
|
||||||
pool_1_user: str = "Unknown"
|
|
||||||
pool_2_url: str = ""
|
|
||||||
pool_2_user: str = ""
|
|
||||||
errors: List[
|
errors: List[
|
||||||
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
|
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
|
||||||
] = field(default_factory=list)
|
] = field(default_factory=list)
|
||||||
@@ -170,7 +170,11 @@ class MinerData:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fields(cls):
|
def fields(cls):
|
||||||
return [f.name for f in 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):
|
def __post_init__(self):
|
||||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||||
@@ -241,13 +245,24 @@ class MinerData:
|
|||||||
if item.hashrate is not None:
|
if item.hashrate is not None:
|
||||||
hr_data.append(item.hashrate)
|
hr_data.append(item.hashrate)
|
||||||
if len(hr_data) > 0:
|
if len(hr_data) > 0:
|
||||||
return sum(hr_data)
|
return round(sum(hr_data), 2)
|
||||||
return self._hashrate
|
return self._hashrate
|
||||||
|
|
||||||
@hashrate.setter
|
@hashrate.setter
|
||||||
def hashrate(self, val):
|
def hashrate(self, val):
|
||||||
self._hashrate = val
|
self._hashrate = val
|
||||||
|
|
||||||
|
@property
|
||||||
|
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._wattage_limit
|
||||||
|
|
||||||
|
@wattage_limit.setter
|
||||||
|
def wattage_limit(self, val: int):
|
||||||
|
self._wattage_limit = val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||||
if len(self.hashboards) > 0:
|
if len(self.hashboards) > 0:
|
||||||
@@ -265,48 +280,48 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def nominal(self): # noqa - Skip PyCharm inspection
|
def nominal(self): # noqa - Skip PyCharm inspection
|
||||||
if self.total_chips is None or self.ideal_chips is None:
|
if self.total_chips is None or self.expected_chips is None:
|
||||||
return None
|
return None
|
||||||
return self.ideal_chips == self.total_chips
|
return self.expected_chips == self.total_chips
|
||||||
|
|
||||||
@nominal.setter
|
@nominal.setter
|
||||||
def nominal(self, val):
|
def nominal(self, val):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
def percent_expected_chips(self): # noqa - Skip PyCharm inspection
|
||||||
if self.total_chips is None or self.ideal_chips is None:
|
if self.total_chips is None or self.expected_chips is None:
|
||||||
return None
|
return None
|
||||||
if self.total_chips == 0 or self.ideal_chips == 0:
|
if self.total_chips == 0 or self.expected_chips == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.total_chips / self.ideal_chips) * 100)
|
return round((self.total_chips / self.expected_chips) * 100)
|
||||||
|
|
||||||
@percent_ideal_chips.setter
|
@percent_expected_chips.setter
|
||||||
def percent_ideal_chips(self, val):
|
def percent_expected_chips(self, val):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
def percent_expected_hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
if self.hashrate is None or self.nominal_hashrate is None:
|
if self.hashrate is None or self.expected_hashrate is None:
|
||||||
return None
|
return None
|
||||||
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
if self.hashrate == 0 or self.expected_hashrate == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.hashrate / self.nominal_hashrate) * 100)
|
return round((self.hashrate / self.expected_hashrate) * 100)
|
||||||
|
|
||||||
@percent_ideal_hashrate.setter
|
@percent_expected_hashrate.setter
|
||||||
def percent_ideal_hashrate(self, val):
|
def percent_expected_hashrate(self, val):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
def percent_expected_wattage(self): # noqa - Skip PyCharm inspection
|
||||||
if self.wattage_limit is None or self.wattage is None:
|
if self.wattage_limit is None or self.wattage is None:
|
||||||
return None
|
return None
|
||||||
if self.wattage_limit == 0 or self.wattage == 0:
|
if self.wattage_limit == 0 or self.wattage == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.wattage / self.wattage_limit) * 100)
|
return round((self.wattage / self.wattage_limit) * 100)
|
||||||
|
|
||||||
@percent_ideal_wattage.setter
|
@percent_expected_wattage.setter
|
||||||
def percent_ideal_wattage(self, val):
|
def percent_expected_wattage(self, val):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -339,7 +354,7 @@ class MinerData:
|
|||||||
|
|
||||||
def asdict(self) -> dict:
|
def asdict(self) -> dict:
|
||||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||||
return asdict(self)
|
return asdict(self, dict_factory=self.dict_factory)
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
"""Get this dataclass as a dictionary.
|
"""Get this dataclass as a dictionary.
|
||||||
|
|||||||
@@ -137,10 +137,10 @@ class _MinerPhaseBalancer:
|
|||||||
for miner in self.miners
|
for miner in self.miners
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
pct_ideal_list = [d.percent_ideal for d in data]
|
pct_expected_list = [d.percent_ideal for d in data]
|
||||||
pct_ideal = 0
|
pct_ideal = 0
|
||||||
if len(pct_ideal_list) > 0:
|
if len(pct_expected_list) > 0:
|
||||||
pct_ideal = sum(pct_ideal_list) / len(pct_ideal_list)
|
pct_ideal = sum(pct_expected_list) / len(pct_expected_list)
|
||||||
|
|
||||||
wattage = round(wattage * 1 / (pct_ideal / 100))
|
wattage = round(wattage * 1 / (pct_ideal / 100))
|
||||||
|
|
||||||
|
|||||||
@@ -16,31 +16,29 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
def init_logger():
|
def init_logger():
|
||||||
if PyasicSettings().logfile:
|
# if PyasicSettings().logfile:
|
||||||
logging.basicConfig(
|
# logging.basicConfig(
|
||||||
filename="logfile.txt",
|
# filename="logfile.txt",
|
||||||
filemode="a",
|
# filemode="a",
|
||||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
# format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||||
datefmt="%x %X",
|
# datefmt="%x %X",
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||||
datefmt="%x %X",
|
datefmt="%x %X",
|
||||||
)
|
)
|
||||||
|
|
||||||
_logger = logging.getLogger()
|
_logger = logging.getLogger()
|
||||||
|
|
||||||
if PyasicSettings().debug:
|
# if PyasicSettings().debug:
|
||||||
_logger.setLevel(logging.DEBUG)
|
# _logger.setLevel(logging.DEBUG)
|
||||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
# logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||||
else:
|
# else:
|
||||||
_logger.setLevel(logging.WARNING)
|
_logger.setLevel(logging.WARNING)
|
||||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||||
|
|
||||||
return _logger
|
return _logger
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
from .bmminer import *
|
from .bmminer import *
|
||||||
from .bosminer import *
|
from .bosminer import *
|
||||||
from .cgminer import *
|
from .cgminer import *
|
||||||
|
from .epic import *
|
||||||
from .hiveon import *
|
from .hiveon import *
|
||||||
from .luxos import *
|
from .luxos import *
|
||||||
from .vnish import *
|
from .vnish import *
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ from pyasic.miners.types import (
|
|||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
S19aPro,
|
S19aPro,
|
||||||
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jNoPIC,
|
S19jNoPIC,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
|
S19Plus,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
|
S19ProHydro,
|
||||||
S19ProPlus,
|
S19ProPlus,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +36,14 @@ class BMMinerS19(AntminerModern, S19):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19Plus(AntminerModern, S19Plus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19i(AntminerModern, S19i):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS19Pro(AntminerModern, S19Pro):
|
class BMMinerS19Pro(AntminerModern, S19Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -67,3 +78,7 @@ class BMMinerS19jPro(AntminerModern, S19jPro):
|
|||||||
|
|
||||||
class BMMinerS19L(AntminerModern, S19L):
|
class BMMinerS19L(AntminerModern, S19L):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19ProHydro(AntminerModern, S19ProHydro):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ from .S19 import (
|
|||||||
BMMinerS19,
|
BMMinerS19,
|
||||||
BMMinerS19a,
|
BMMinerS19a,
|
||||||
BMMinerS19aPro,
|
BMMinerS19aPro,
|
||||||
|
BMMinerS19i,
|
||||||
BMMinerS19j,
|
BMMinerS19j,
|
||||||
BMMinerS19jNoPIC,
|
BMMinerS19jNoPIC,
|
||||||
BMMinerS19jPro,
|
BMMinerS19jPro,
|
||||||
BMMinerS19L,
|
BMMinerS19L,
|
||||||
|
BMMinerS19Plus,
|
||||||
BMMinerS19Pro,
|
BMMinerS19Pro,
|
||||||
|
BMMinerS19ProHydro,
|
||||||
BMMinerS19ProPlus,
|
BMMinerS19ProPlus,
|
||||||
BMMinerS19XP,
|
BMMinerS19XP,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,17 +15,37 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSMiner
|
from pyasic.miners.backends import BOSMiner
|
||||||
from pyasic.miners.types import S19, S19j, S19jNoPIC, S19jPro, S19Pro
|
from pyasic.miners.types import (
|
||||||
|
S19,
|
||||||
|
S19XP,
|
||||||
|
S19a,
|
||||||
|
S19aPro,
|
||||||
|
S19j,
|
||||||
|
S19jNoPIC,
|
||||||
|
S19jPro,
|
||||||
|
S19jProPlus,
|
||||||
|
S19kProNoPIC,
|
||||||
|
S19Plus,
|
||||||
|
S19Pro,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS19(BOSMiner, S19):
|
class BOSMinerS19(BOSMiner, S19):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19Plus(BOSMiner, S19Plus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19a(BOSMiner, S19a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS19j(BOSMiner, S19j):
|
class BOSMinerS19j(BOSMiner, S19j):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -36,3 +56,19 @@ class BOSMinerS19jNoPIC(BOSMiner, S19jNoPIC):
|
|||||||
|
|
||||||
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19kProNoPIC(BOSMiner, S19kProNoPIC):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19aPro(BOSMiner, S19aPro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19jProPlus(BOSMiner, S19jProPlus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19XP(BOSMiner, S19XP):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -16,9 +16,15 @@
|
|||||||
|
|
||||||
from .S19 import (
|
from .S19 import (
|
||||||
BOSMinerS19,
|
BOSMinerS19,
|
||||||
|
BOSMinerS19a,
|
||||||
|
BOSMinerS19aPro,
|
||||||
BOSMinerS19j,
|
BOSMinerS19j,
|
||||||
BOSMinerS19jNoPIC,
|
BOSMinerS19jNoPIC,
|
||||||
BOSMinerS19jPro,
|
BOSMinerS19jPro,
|
||||||
|
BOSMinerS19jProPlus,
|
||||||
|
BOSMinerS19kProNoPIC,
|
||||||
|
BOSMinerS19Plus,
|
||||||
BOSMinerS19Pro,
|
BOSMinerS19Pro,
|
||||||
|
BOSMinerS19XP,
|
||||||
)
|
)
|
||||||
from .T19 import BOSMinerT19
|
from .T19 import BOSMinerT19
|
||||||
|
|||||||
46
pyasic/miners/antminer/epic/X19/S19.py
Normal file
46
pyasic/miners/antminer/epic/X19/S19.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import ePIC
|
||||||
|
from pyasic.miners.types import S19, S19XP, S19j, S19jPro, S19jProPlus, S19kPro, S19Pro
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19(ePIC, S19):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19Pro(ePIC, S19Pro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19j(ePIC, S19j):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19jPro(ePIC, S19jPro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19jProPlus(ePIC, S19jProPlus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19kPro(ePIC, S19kPro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ePICS19XP(ePIC, S19XP):
|
||||||
|
pass
|
||||||
25
pyasic/miners/antminer/epic/X19/__init__.py
Normal file
25
pyasic/miners/antminer/epic/X19/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from .S19 import (
|
||||||
|
ePICS19,
|
||||||
|
ePICS19j,
|
||||||
|
ePICS19jPro,
|
||||||
|
ePICS19jProPlus,
|
||||||
|
ePICS19kPro,
|
||||||
|
ePICS19Pro,
|
||||||
|
ePICS19XP,
|
||||||
|
)
|
||||||
17
pyasic/miners/antminer/epic/__init__.py
Normal file
17
pyasic/miners/antminer/epic/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 .X19 import *
|
||||||
@@ -54,7 +54,7 @@ class HiveonT9(Hiveon, T9):
|
|||||||
hashboards = []
|
hashboards = []
|
||||||
|
|
||||||
for board in board_map:
|
for board in board_map:
|
||||||
hashboard = HashBoard(slot=board, expected_chips=self.nominal_chips)
|
hashboard = HashBoard(slot=board, expected_chips=self.expected_chips)
|
||||||
hashrate = 0
|
hashrate = 0
|
||||||
chips = 0
|
chips = 0
|
||||||
for chipset in board_map[board]:
|
for chipset in board_map[board]:
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ from .bosminer import BOSMiner
|
|||||||
from .btminer import BTMiner
|
from .btminer import BTMiner
|
||||||
from .cgminer import CGMiner
|
from .cgminer import CGMiner
|
||||||
from .cgminer_avalon import CGMinerAvalon
|
from .cgminer_avalon import CGMinerAvalon
|
||||||
|
from .epic import ePIC
|
||||||
from .hiveon import Hiveon
|
from .hiveon import Hiveon
|
||||||
from .luxminer import LUXMiner
|
from .luxminer import LUXMiner
|
||||||
from .vnish import VNish
|
from .vnish import VNish
|
||||||
from .whatsminer import M2X, M3X, M5X
|
from .whatsminer import M2X, M3X, M5X, M6X
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import asyncio
|
|||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from pyasic.API import APIError
|
from pyasic.API import APIError
|
||||||
from pyasic.config import MinerConfig, X19PowerMode
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||||
from pyasic.miners.backends.bmminer import BMMiner
|
from pyasic.miners.backends.bmminer import BMMiner
|
||||||
@@ -38,8 +38,8 @@ ANTMINER_MODERN_DATA_LOC = {
|
|||||||
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
},
|
},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -62,6 +62,10 @@ ANTMINER_MODERN_DATA_LOC = {
|
|||||||
"cmd": "get_uptime",
|
"cmd": "get_uptime",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
|
"config": {
|
||||||
|
"cmd": "get_config",
|
||||||
|
"kwargs": {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -80,23 +84,21 @@ class AntminerModern(BMMiner):
|
|||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig().from_raw(data)
|
self.config = MinerConfig.from_am_modern(data)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
conf = config.as_x19(user_suffix=user_suffix)
|
await self.web.set_miner_conf(config.as_am_modern(user_suffix=user_suffix))
|
||||||
data = await self.web.set_miner_conf(conf)
|
# if data:
|
||||||
|
# if data.get("code") == "M000":
|
||||||
if data:
|
# return
|
||||||
if data.get("code") == "M000":
|
#
|
||||||
return
|
# for i in range(7):
|
||||||
|
# data = await self.get_config()
|
||||||
for i in range(7):
|
# if data == self.config:
|
||||||
data = await self.get_config()
|
# break
|
||||||
if data.as_x19() == conf:
|
# await asyncio.sleep(1)
|
||||||
break
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
data = await self.web.blink(blink=True)
|
data = await self.web.blink(blink=True)
|
||||||
@@ -120,13 +122,13 @@ class AntminerModern(BMMiner):
|
|||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
cfg.miner_mode = X19PowerMode.Sleep
|
cfg.miner_mode = MiningModeConfig.sleep
|
||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
cfg.miner_mode = X19PowerMode.Normal
|
cfg.miner_mode = MiningModeConfig.normal
|
||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -200,7 +202,7 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
api_stats = await self.api.stats()
|
||||||
@@ -209,17 +211,17 @@ class AntminerModern(BMMiner):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
try:
|
try:
|
||||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
return round(ideal_rate / 1000, 2)
|
return round(expected_rate / 1000, 2)
|
||||||
if rate_unit == "MH":
|
if rate_unit == "MH":
|
||||||
return round(ideal_rate / 1000000, 2)
|
return round(expected_rate / 1000000, 2)
|
||||||
else:
|
else:
|
||||||
return round(ideal_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -274,7 +276,11 @@ class AntminerModern(BMMiner):
|
|||||||
|
|
||||||
if web_get_conf:
|
if web_get_conf:
|
||||||
try:
|
try:
|
||||||
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
if web_get_conf["bitmain-work-mode"].isdigit():
|
||||||
|
return (
|
||||||
|
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||||
|
)
|
||||||
|
return False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -294,10 +300,7 @@ class AntminerModern(BMMiner):
|
|||||||
|
|
||||||
ANTMINER_OLD_DATA_LOC = {
|
ANTMINER_OLD_DATA_LOC = {
|
||||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||||
"model": {
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
"cmd": "get_model",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {
|
"hostname": {
|
||||||
@@ -305,8 +308,8 @@ ANTMINER_OLD_DATA_LOC = {
|
|||||||
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
},
|
},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -325,10 +328,8 @@ ANTMINER_OLD_DATA_LOC = {
|
|||||||
"cmd": "is_mining",
|
"cmd": "is_mining",
|
||||||
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}},
|
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}},
|
||||||
},
|
},
|
||||||
"uptime": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"cmd": "get_uptime",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -345,11 +346,12 @@ class AntminerOld(CGMiner):
|
|||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig().from_raw(data)
|
self.config = MinerConfig.from_am_old(data)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
await self.web.set_miner_conf(config.as_x17(user_suffix=user_suffix))
|
self.config = config
|
||||||
|
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
|
||||||
|
|
||||||
async def get_mac(self) -> Union[str, None]:
|
async def get_mac(self) -> Union[str, None]:
|
||||||
try:
|
try:
|
||||||
@@ -469,9 +471,11 @@ class AntminerOld(CGMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(
|
||||||
|
board_offset, board_offset + self.expected_hashboards
|
||||||
|
):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
|
|
||||||
chip_temp = boards[1].get(f"temp{i}")
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ BFGMINER_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -46,6 +46,7 @@ BFGMINER_DATA_LOC = {
|
|||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||||
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ class BFGMiner(BaseMiner):
|
|||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig().from_api(pools["POOLS"])
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
@@ -197,9 +198,11 @@ class BFGMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(
|
||||||
|
board_offset, board_offset + self.expected_hashboards
|
||||||
|
):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
|
|
||||||
chip_temp = boards[1].get(f"temp{i}")
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
@@ -297,7 +300,7 @@ class BFGMiner(BaseMiner):
|
|||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
@@ -307,17 +310,17 @@ class BFGMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
try:
|
try:
|
||||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
return round(ideal_rate / 1000, 2)
|
return round(expected_rate / 1000, 2)
|
||||||
if rate_unit == "MH":
|
if rate_unit == "MH":
|
||||||
return round(ideal_rate / 1000000, 2)
|
return round(expected_rate / 1000000, 2)
|
||||||
else:
|
else:
|
||||||
return round(ideal_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ GOLDSHELL_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {
|
"hashboards": {
|
||||||
@@ -50,6 +50,10 @@ GOLDSHELL_DATA_LOC = {
|
|||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||||
|
"config": {
|
||||||
|
"cmd": "get_config",
|
||||||
|
"kwargs": {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +68,14 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
self.data_locations = GOLDSHELL_DATA_LOC
|
self.data_locations = GOLDSHELL_DATA_LOC
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
return MinerConfig().from_raw(await self.web.pools())
|
# get pool data
|
||||||
|
try:
|
||||||
|
pools = await self.web.pools()
|
||||||
|
except APIError:
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
self.config = MinerConfig.from_goldshell(pools)
|
||||||
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
pools_data = await self.web.pools()
|
pools_data = await self.web.pools()
|
||||||
@@ -80,7 +91,7 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
# send them back 1 at a time
|
# send them back 1 at a time
|
||||||
for pool in config.as_goldshell(user_suffix=user_suffix):
|
for pool in config.as_goldshell(user_suffix=user_suffix)["pools"]:
|
||||||
await self.web.newpool(
|
await self.web.newpool(
|
||||||
url=pool["url"], user=pool["user"], password=pool["pass"]
|
url=pool["url"], user=pool["user"], password=pool["pass"]
|
||||||
)
|
)
|
||||||
@@ -121,8 +132,8 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if api_devs:
|
if api_devs:
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ BMMINER_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -46,10 +46,8 @@ BMMINER_DATA_LOC = {
|
|||||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"cmd": "get_uptime",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ class BMMiner(BaseMiner):
|
|||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig().from_api(pools["POOLS"])
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
@@ -239,18 +237,20 @@ class BMMiner(BaseMiner):
|
|||||||
|
|
||||||
for i in range(board_offset, board_offset + 4):
|
for i in range(board_offset, board_offset + 4):
|
||||||
try:
|
try:
|
||||||
key = f'chain_acs{i}'
|
key = f"chain_acs{i}"
|
||||||
if boards[1].get(key, '') != '':
|
if boards[1].get(key, "") != "":
|
||||||
real_slots.append(i)
|
real_slots.append(i)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if len(real_slots) < 3:
|
if len(real_slots) < 3:
|
||||||
real_slots = list(range(board_offset, board_offset + self.ideal_hashboards))
|
real_slots = list(
|
||||||
|
range(board_offset, board_offset + self.expected_hashboards)
|
||||||
|
)
|
||||||
|
|
||||||
for i in real_slots:
|
for i in real_slots:
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
|
|
||||||
chip_temp = boards[1].get(f"temp{i}")
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
@@ -347,7 +347,7 @@ class BMMiner(BaseMiner):
|
|||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
@@ -357,17 +357,17 @@ class BMMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
try:
|
try:
|
||||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
return round(ideal_rate / 1000, 2)
|
return round(expected_rate / 1000, 2)
|
||||||
if rate_unit == "MH":
|
if rate_unit == "MH":
|
||||||
return round(ideal_rate / 1000000, 2)
|
return round(expected_rate / 1000000, 2)
|
||||||
else:
|
else:
|
||||||
return round(ideal_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import List, Optional, Tuple, Union
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import toml
|
|||||||
|
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
from pyasic.API.bosminer import BOSMinerAPI
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
from pyasic.config.mining import MiningModePowerTune
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
@@ -36,10 +38,7 @@ BOSMINER_DATA_LOC = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"model": {"cmd": "get_model", "kwargs": {}},
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
"api_ver": {
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"cmd": "get_api_ver",
|
|
||||||
"kwargs": {"api_version": {"api": "version"}},
|
|
||||||
},
|
|
||||||
"fw_ver": {
|
"fw_ver": {
|
||||||
"cmd": "get_fw_ver",
|
"cmd": "get_fw_ver",
|
||||||
"kwargs": {
|
"kwargs": {
|
||||||
@@ -63,8 +62,8 @@ BOSMINER_DATA_LOC = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_devs": {"api": "devs"}},
|
"kwargs": {"api_devs": {"api": "devs"}},
|
||||||
},
|
},
|
||||||
"hashboards": {
|
"hashboards": {
|
||||||
@@ -176,19 +175,17 @@ BOSMINER_DATA_LOC = {
|
|||||||
"cmd": "is_mining",
|
"cmd": "is_mining",
|
||||||
"kwargs": {"api_devdetails": {"api": "devdetails"}},
|
"kwargs": {"api_devdetails": {"api": "devdetails"}},
|
||||||
},
|
},
|
||||||
"uptime": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"cmd": "get_uptime",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {"api_summary": {"api": "summary"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BOSMiner(BaseMiner):
|
class BOSMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0", boser: bool = None) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
# interfaces
|
# interfaces
|
||||||
self.api = BOSMinerAPI(ip, api_ver)
|
self.api = BOSMinerAPI(ip, api_ver)
|
||||||
self.web = BOSMinerWebAPI(ip)
|
self.web = BOSMinerWebAPI(ip, boser=boser)
|
||||||
|
|
||||||
# static data
|
# static data
|
||||||
self.api_type = "BOSMiner"
|
self.api_type = "BOSMiner"
|
||||||
@@ -297,11 +294,6 @@ class BOSMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
"""Gets the config for the miner and sets it as `self.config`.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The config from `self.config`.
|
|
||||||
"""
|
|
||||||
logging.debug(f"{self}: Getting config.")
|
logging.debug(f"{self}: Getting config.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -316,21 +308,42 @@ class BOSMiner(BaseMiner):
|
|||||||
(await conn.run("cat /etc/bosminer.toml")).stdout
|
(await conn.run("cat /etc/bosminer.toml")).stdout
|
||||||
)
|
)
|
||||||
logging.debug(f"{self}: Converting config file.")
|
logging.debug(f"{self}: Converting config file.")
|
||||||
cfg = MinerConfig().from_raw(toml_data)
|
cfg = MinerConfig.from_bosminer(toml_data)
|
||||||
self.config = cfg
|
self.config = cfg
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
"""Configures miner with yaml config."""
|
|
||||||
logging.debug(f"{self}: Sending config.")
|
logging.debug(f"{self}: Sending config.")
|
||||||
self.config = config
|
self.config = config
|
||||||
toml_conf = config.as_bos(
|
|
||||||
model=self.model.replace(" (BOS)", ""), user_suffix=user_suffix
|
if self.web.grpc is not None:
|
||||||
|
try:
|
||||||
|
await self._send_config_grpc(config, user_suffix)
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
await self._send_config_bosminer(config, user_suffix)
|
||||||
|
|
||||||
|
async def _send_config_grpc(self, config: MinerConfig, user_suffix: str = None):
|
||||||
|
raise NotImplementedError
|
||||||
|
mining_mode = config.mining_mode
|
||||||
|
|
||||||
|
async def _send_config_bosminer(self, config: MinerConfig, user_suffix: str = None):
|
||||||
|
toml_conf = toml.dumps(
|
||||||
|
{
|
||||||
|
"format": {
|
||||||
|
"version": "1.2+",
|
||||||
|
"generator": "pyasic",
|
||||||
|
"model": f"{self.make.replace('Miner', 'miner')} {self.model.replace(' (BOS)', '').replace('j', 'J')}",
|
||||||
|
"timestamp": int(time.time()),
|
||||||
|
},
|
||||||
|
**config.as_bosminer(user_suffix=user_suffix),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
conn = await self._get_ssh_connection()
|
conn = await self._get_ssh_connection()
|
||||||
except ConnectionError:
|
except ConnectionError as e:
|
||||||
return None
|
raise APIError("SSH connection failed when sending config.") from e
|
||||||
async with conn:
|
async with conn:
|
||||||
# BBB check because bitmain suxx
|
# BBB check because bitmain suxx
|
||||||
bbb_check = await conn.run(
|
bbb_check = await conn.run(
|
||||||
@@ -362,7 +375,7 @@ class BOSMiner(BaseMiner):
|
|||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
return False
|
return False
|
||||||
cfg.autotuning_wattage = wattage
|
cfg.mining_mode = MiningModePowerTune(wattage)
|
||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self} set_power_limit: {e}")
|
logging.warning(f"{self} set_power_limit: {e}")
|
||||||
@@ -539,7 +552,6 @@ class BOSMiner(BaseMiner):
|
|||||||
async def get_hashrate(
|
async def get_hashrate(
|
||||||
self, api_summary: dict = None, graphql_hashrate: dict = None
|
self, api_summary: dict = None, graphql_hashrate: dict = None
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
|
|
||||||
# get hr from graphql
|
# get hr from graphql
|
||||||
if not graphql_hashrate:
|
if not graphql_hashrate:
|
||||||
try:
|
try:
|
||||||
@@ -584,8 +596,8 @@ class BOSMiner(BaseMiner):
|
|||||||
graphql_boards: dict = None,
|
graphql_boards: dict = None,
|
||||||
):
|
):
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not graphql_boards and not (api_devs or api_temps or api_devdetails):
|
if not graphql_boards and not (api_devs or api_temps or api_devdetails):
|
||||||
@@ -622,7 +634,7 @@ class BOSMiner(BaseMiner):
|
|||||||
offset = 0
|
offset = 0
|
||||||
if 3 in b_names:
|
if 3 in b_names:
|
||||||
offset = 1
|
offset = 1
|
||||||
elif 6 in b_names:
|
elif 6 in b_names or 7 in b_names or 8 in b_names:
|
||||||
offset = 6
|
offset = 6
|
||||||
for hb in boards:
|
for hb in boards:
|
||||||
_id = int(hb["name"]) - offset
|
_id = int(hb["name"]) - offset
|
||||||
@@ -787,7 +799,7 @@ class BOSMiner(BaseMiner):
|
|||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
if graphql_fans:
|
if graphql_fans.get("data"):
|
||||||
fans = []
|
fans = []
|
||||||
for n in range(self.fan_count):
|
for n in range(self.fan_count):
|
||||||
try:
|
try:
|
||||||
@@ -1043,10 +1055,10 @@ class BOSMiner(BaseMiner):
|
|||||||
if data == "50":
|
if data == "50":
|
||||||
self.light = True
|
self.light = True
|
||||||
return self.light
|
return self.light
|
||||||
except TypeError:
|
except (TypeError, AttributeError):
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||||
if not api_devs:
|
if not api_devs:
|
||||||
try:
|
try:
|
||||||
api_devs = await self.api.devs()
|
api_devs = await self.api.devs()
|
||||||
@@ -1060,14 +1072,14 @@ class BOSMiner(BaseMiner):
|
|||||||
|
|
||||||
for board in api_devs["DEVS"]:
|
for board in api_devs["DEVS"]:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
nominal_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
|
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
|
||||||
if nominal_hashrate:
|
if expected_hashrate:
|
||||||
hr_list.append(nominal_hashrate)
|
hr_list.append(expected_hashrate)
|
||||||
if len(hr_list) == 0:
|
if len(hr_list) == 0:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return round(
|
return round(
|
||||||
(sum(hr_list) / len(hr_list)) * self.ideal_hashboards, 2
|
(sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2
|
||||||
)
|
)
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class BOSMinerOld(BOSMiner):
|
|||||||
async def get_fault_light(self, *args, **kwargs) -> bool:
|
async def get_fault_light(self, *args, **kwargs) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, *args, **kwargs) -> Optional[float]:
|
async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
|
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
|
||||||
|
|||||||
@@ -15,12 +15,11 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
@@ -51,8 +50,8 @@ BTMINER_DATA_LOC = {
|
|||||||
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
|
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
|
||||||
},
|
},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_summary": {"api": "summary"}},
|
"kwargs": {"api_summary": {"api": "summary"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}},
|
||||||
@@ -89,10 +88,8 @@ BTMINER_DATA_LOC = {
|
|||||||
},
|
},
|
||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}},
|
||||||
"uptime": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"cmd": "get_uptime",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {"api_summary": {"api": "summary"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -198,44 +195,68 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await self.api.update_pools(**pools_conf)
|
await self.api.update_pools(**pools_conf)
|
||||||
|
|
||||||
|
if conf["mode"] == "normal":
|
||||||
|
await self.api.set_normal_power()
|
||||||
|
elif conf["mode"] == "high":
|
||||||
|
await self.api.set_high_power()
|
||||||
|
elif conf["mode"] == "low":
|
||||||
|
await self.api.set_low_power()
|
||||||
|
elif conf["mode"] == "power_tuning":
|
||||||
|
await self.api.adjust_power_limit(conf["power_tuning"]["wattage"])
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
# cannot update, no API access usually
|
||||||
try:
|
|
||||||
await self.api.adjust_power_limit(conf["wattage"])
|
|
||||||
except APIError:
|
|
||||||
# cannot set wattage
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
pools = None
|
pools = None
|
||||||
summary = None
|
summary = None
|
||||||
cfg = MinerConfig()
|
status = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await self.api.multicommand("pools", "summary")
|
data = await self.api.multicommand("pools", "summary", "status")
|
||||||
pools = data["pools"][0]
|
pools = data["pools"][0]
|
||||||
summary = data["summary"][0]
|
summary = data["summary"][0]
|
||||||
|
status = data["status"][0]
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
logging.warning(e)
|
logging.warning(e)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if pools:
|
if pools is not None:
|
||||||
if "POOLS" in pools:
|
cfg = MinerConfig.from_api(pools)
|
||||||
cfg = cfg.from_api(pools["POOLS"])
|
|
||||||
else:
|
else:
|
||||||
# somethings wrong with the miner
|
cfg = MinerConfig()
|
||||||
warnings.warn(
|
|
||||||
f"Failed to gather pool config for miner: {self}, miner did not return pool information."
|
|
||||||
)
|
|
||||||
if summary:
|
|
||||||
if "SUMMARY" in summary:
|
|
||||||
if wattage := summary["SUMMARY"][0].get("Power Limit"):
|
|
||||||
cfg.autotuning_wattage = wattage
|
|
||||||
|
|
||||||
self.config = cfg
|
is_mining = await self.is_mining(status)
|
||||||
|
if not is_mining:
|
||||||
|
cfg.mining_mode = MiningModeConfig.sleep()
|
||||||
|
return cfg
|
||||||
|
|
||||||
return self.config
|
if summary is not None:
|
||||||
|
mining_mode = None
|
||||||
|
try:
|
||||||
|
mining_mode = summary["SUMMARY"][0]["Power Mode"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if mining_mode == "High":
|
||||||
|
cfg.mining_mode = MiningModeConfig.high()
|
||||||
|
return cfg
|
||||||
|
elif mining_mode == "Low":
|
||||||
|
cfg.mining_mode = MiningModeConfig.low()
|
||||||
|
return cfg
|
||||||
|
try:
|
||||||
|
power_lim = summary["SUMMARY"][0]["Power Limit"]
|
||||||
|
except LookupError:
|
||||||
|
power_lim = None
|
||||||
|
|
||||||
|
if power_lim is None:
|
||||||
|
cfg.mining_mode = MiningModeConfig.normal()
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
cfg.mining_mode = MiningModeConfig.power_tuning(power_lim)
|
||||||
|
self.config = cfg
|
||||||
|
return self.config
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
@@ -386,10 +407,9 @@ class BTMiner(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
|
async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
|
||||||
|
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_devs:
|
if not api_devs:
|
||||||
@@ -404,10 +424,10 @@ class BTMiner(BaseMiner):
|
|||||||
if len(hashboards) < board["ASC"] + 1:
|
if len(hashboards) < board["ASC"] + 1:
|
||||||
hashboards.append(
|
hashboards.append(
|
||||||
HashBoard(
|
HashBoard(
|
||||||
slot=board["ASC"], expected_chips=self.nominal_chips
|
slot=board["ASC"], expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.ideal_hashboards += 1
|
self.expected_hashboards += 1
|
||||||
hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"])
|
hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"])
|
||||||
hashboards[board["ASC"]].temp = round(board["Temperature"])
|
hashboards[board["ASC"]].temp = round(board["Temperature"])
|
||||||
hashboards[board["ASC"]].hashrate = round(
|
hashboards[board["ASC"]].hashrate = round(
|
||||||
@@ -442,7 +462,8 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_summary:
|
if api_summary:
|
||||||
try:
|
try:
|
||||||
return api_summary["SUMMARY"][0]["Power"]
|
wattage = api_summary["SUMMARY"][0]["Power"]
|
||||||
|
return wattage if not wattage == -1 else None
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -569,7 +590,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_summary: dict = None):
|
async def get_expected_hashrate(self, api_summary: dict = None):
|
||||||
if not api_summary:
|
if not api_summary:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
api_summary = await self.api.summary()
|
||||||
@@ -578,9 +599,9 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_summary:
|
if api_summary:
|
||||||
try:
|
try:
|
||||||
nominal_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
|
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
|
||||||
if nominal_hashrate:
|
if expected_hashrate:
|
||||||
return round(nominal_hashrate / 1000, 2)
|
return round(expected_hashrate / 1000, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ CGMINER_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -46,10 +46,8 @@ CGMINER_DATA_LOC = {
|
|||||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"cmd": "get_uptime",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -143,10 +141,13 @@ class CGMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
api_pools = await self.api.pools()
|
# get pool data
|
||||||
|
try:
|
||||||
|
pools = await self.api.pools()
|
||||||
|
except APIError:
|
||||||
|
return self.config
|
||||||
|
|
||||||
if api_pools:
|
self.config = MinerConfig.from_api(pools)
|
||||||
self.config = MinerConfig().from_api(api_pools["POOLS"])
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
@@ -256,9 +257,11 @@ class CGMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(
|
||||||
|
board_offset, board_offset + self.expected_hashboards
|
||||||
|
):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
|
|
||||||
chip_temp = boards[1].get(f"temp{i}")
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
@@ -357,7 +360,7 @@ class CGMiner(BaseMiner):
|
|||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
@@ -367,17 +370,17 @@ class CGMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
try:
|
try:
|
||||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
return round(ideal_rate / 1000, 2)
|
return round(expected_rate / 1000, 2)
|
||||||
if rate_unit == "MH":
|
if rate_unit == "MH":
|
||||||
return round(ideal_rate / 1000000, 2)
|
return round(expected_rate / 1000000, 2)
|
||||||
else:
|
else:
|
||||||
return round(ideal_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ AVALON_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -52,6 +52,7 @@ AVALON_DATA_LOC = {
|
|||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||||
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -100,17 +101,17 @@ class CGMinerAvalon(CGMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
"""Configures miner with yaml config."""
|
pass
|
||||||
self.config = config
|
# self.config = config
|
||||||
return None
|
# return None
|
||||||
logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
|
# logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
|
||||||
conf = config.as_avalon(user_suffix=user_suffix)
|
# conf = config.as_avalon(user_suffix=user_suffix)
|
||||||
try:
|
# try:
|
||||||
data = await self.api.ascset( # noqa
|
# data = await self.api.ascset( # noqa
|
||||||
0, "setpool", f"root,root,{conf}"
|
# 0, "setpool", f"root,root,{conf}"
|
||||||
) # this should work but doesn't
|
# ) # this should work but doesn't
|
||||||
except APIError:
|
# except APIError:
|
||||||
pass
|
# pass
|
||||||
# return data
|
# return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -201,8 +202,8 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
@@ -218,7 +219,7 @@ class CGMinerAvalon(CGMiner):
|
|||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
for board in range(self.ideal_hashboards):
|
for board in range(self.expected_hashboards):
|
||||||
try:
|
try:
|
||||||
hashboards[board].chip_temp = int(parsed_stats["MTmax"][board])
|
hashboards[board].chip_temp = int(parsed_stats["MTmax"][board])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -247,7 +248,7 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
api_stats = await self.api.stats()
|
||||||
|
|||||||
312
pyasic/miners/backends/epic.py
Normal file
312
pyasic/miners/backends/epic.py
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from typing import List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
from pyasic.data import Fan, HashBoard
|
||||||
|
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.logger import logger
|
||||||
|
from pyasic.miners.backends.bmminer import BMMiner
|
||||||
|
from pyasic.web.epic import ePICWebAPI
|
||||||
|
|
||||||
|
EPIC_DATA_LOC = {
|
||||||
|
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "network"}}},
|
||||||
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"expected_hashrate": {
|
||||||
|
"cmd": "get_expected_hashrate",
|
||||||
|
"kwargs": {"web_summary": {"web": "summary"}},
|
||||||
|
},
|
||||||
|
"hashboards": {
|
||||||
|
"cmd": "get_hashboards",
|
||||||
|
"kwargs": {
|
||||||
|
"web_summary": {"web": "summary"},
|
||||||
|
"web_hashrate": {"web": "hashrate"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||||
|
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||||
|
"fans": {"cmd": "get_fans", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||||
|
"fault_light": {
|
||||||
|
"cmd": "get_fault_light",
|
||||||
|
"kwargs": {"web_summary": {"web": "summary"}},
|
||||||
|
},
|
||||||
|
"pools": {"cmd": "get_pools", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
|
"uptime": {"cmd": "get_uptime", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ePIC(BMMiner):
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
# interfaces
|
||||||
|
self.web = ePICWebAPI(ip)
|
||||||
|
|
||||||
|
# static data
|
||||||
|
self.api_type = "ePIC"
|
||||||
|
# data gathering locations
|
||||||
|
self.data_locations = EPIC_DATA_LOC
|
||||||
|
|
||||||
|
async def get_model(self) -> Optional[str]:
|
||||||
|
if self.model is not None:
|
||||||
|
return self.model + " (ePIC)"
|
||||||
|
return "? (ePIC)"
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
data = await self.web.restart_epic()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
data = await self.web.stop_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
data = await self.web.resume_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
data = await self.web.reboot()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_mac(self, web_summary: dict = None) -> str:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.network()
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
for network in web_summary:
|
||||||
|
mac = web_summary[network]["mac_address"]
|
||||||
|
return mac
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hostname(self, web_summary: dict = None) -> str:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
hostname = web_summary["Hostname"]
|
||||||
|
return hostname
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
wattage = web_summary["Power Supply Stats"]["Input Power"]
|
||||||
|
wattage = round(wattage)
|
||||||
|
return wattage
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||||
|
# get hr from API
|
||||||
|
if not web_summary:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
hashrate = 0
|
||||||
|
if web_summary["HBs"] != None:
|
||||||
|
for hb in web_summary["HBs"]:
|
||||||
|
hashrate += hb["Hashrate"][0]
|
||||||
|
return round(float(float(hashrate / 1000000)), 2)
|
||||||
|
except (LookupError, ValueError, TypeError) as e:
|
||||||
|
logger.error(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||||
|
# get hr from API
|
||||||
|
if not web_summary:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
hashrate = 0
|
||||||
|
if web_summary["HBs"] != None:
|
||||||
|
for hb in web_summary["HBs"]:
|
||||||
|
if hb["Hashrate"][1] == 0:
|
||||||
|
ideal = 1.0
|
||||||
|
else:
|
||||||
|
ideal = hb["Hashrate"][1] / 100
|
||||||
|
|
||||||
|
hashrate += hb["Hashrate"][0] / ideal
|
||||||
|
return round(float(float(hashrate / 1000000)), 2)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError) as e:
|
||||||
|
logger.error(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
fw_ver = web_summary["Software"]
|
||||||
|
fw_ver = fw_ver.split(" ")[1].replace("v", "")
|
||||||
|
return fw_ver
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_fans(self, web_summary: dict = None) -> List[Fan]:
|
||||||
|
if not web_summary:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans = []
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
for fan in web_summary["Fans Rpm"]:
|
||||||
|
try:
|
||||||
|
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
fans.append(Fan())
|
||||||
|
return fans
|
||||||
|
|
||||||
|
async def get_hashboards(
|
||||||
|
self, web_summary: dict = None, web_hashrate: dict = None
|
||||||
|
) -> List[HashBoard]:
|
||||||
|
if not web_summary:
|
||||||
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if not web_hashrate:
|
||||||
|
try:
|
||||||
|
web_hashrate = await self.web.hashrate()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
hb_list = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
|
for i in range(self.expected_hashboards)
|
||||||
|
]
|
||||||
|
if web_summary["HBs"] != None:
|
||||||
|
for hb in web_summary["HBs"]:
|
||||||
|
for hr in web_hashrate:
|
||||||
|
if hr["Index"] == hb["Index"]:
|
||||||
|
num_of_chips = len(hr["Data"])
|
||||||
|
hashrate = hb["Hashrate"][0]
|
||||||
|
# Update the Hashboard object
|
||||||
|
hb_list[hr["Index"]].expected_chips = num_of_chips
|
||||||
|
hb_list[hr["Index"]].missing = False
|
||||||
|
hb_list[hr["Index"]].hashrate = round(hashrate / 1000000, 2)
|
||||||
|
hb_list[hr["Index"]].chips = num_of_chips
|
||||||
|
hb_list[hr["Index"]].temp = hb["Temperature"]
|
||||||
|
return hb_list
|
||||||
|
|
||||||
|
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_pools(self, web_summary: dict = None) -> List[dict]:
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
if not web_summary:
|
||||||
|
try:
|
||||||
|
web_summary = await self.api.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
pools = {}
|
||||||
|
for i, pool in enumerate(web_summary["StratumConfigs"]):
|
||||||
|
pools[f"pool_{i + 1}_url"] = (
|
||||||
|
pool["pool"]
|
||||||
|
.replace("stratum+tcp://", "")
|
||||||
|
.replace("stratum2+tcp://", "")
|
||||||
|
)
|
||||||
|
pools[f"pool_{i + 1}_user"] = pool["login"]
|
||||||
|
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||||
|
|
||||||
|
groups.append(pools)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return groups
|
||||||
|
|
||||||
|
async def get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
uptime = web_summary["Session"]["Uptime"]
|
||||||
|
return uptime
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_fault_light(self, web_summary: dict = None) -> bool:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
light = web_summary["Misc"]["Locate Miner State"]
|
||||||
|
return light
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
|
if not web_summary:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
errors = []
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
error = web_summary["Status"]["Last Error"]
|
||||||
|
if error != None:
|
||||||
|
errors.append(X19Error(str(error)))
|
||||||
|
return errors
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return errors
|
||||||
@@ -30,69 +30,25 @@ from pyasic.miners.base import BaseMiner
|
|||||||
from pyasic.web.bosminer import BOSMinerWebAPI
|
from pyasic.web.bosminer import BOSMinerWebAPI
|
||||||
|
|
||||||
LUXMINER_DATA_LOC = {
|
LUXMINER_DATA_LOC = {
|
||||||
"mac": {
|
"mac": {"cmd": "get_mac", "kwargs": {"api_config": {"api": "config"}}},
|
||||||
"cmd": "get_mac",
|
|
||||||
"kwargs": {"api_config": {"api": "config"}},
|
|
||||||
},
|
|
||||||
"model": {"cmd": "get_model", "kwargs": {}},
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
"api_ver": {
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {}},
|
||||||
"cmd": "get_api_ver",
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {}},
|
||||||
"kwargs": {},
|
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||||
},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {}},
|
||||||
"fw_ver": {
|
"expected_hashrate": {"cmd": "get_expected_hashrate", "kwargs": {}},
|
||||||
"cmd": "get_fw_ver",
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {}},
|
||||||
"kwargs": {},
|
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||||
},
|
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||||
"hostname": {
|
"fans": {"cmd": "get_fans", "kwargs": {}},
|
||||||
"cmd": "get_hostname",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"hashrate": {
|
|
||||||
"cmd": "get_hashrate",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"nominal_hashrate": {
|
|
||||||
"cmd": "get_nominal_hashrate",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"hashboards": {
|
|
||||||
"cmd": "get_hashboards",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"wattage": {
|
|
||||||
"cmd": "get_wattage",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"wattage_limit": {
|
|
||||||
"cmd": "get_wattage_limit",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"fans": {
|
|
||||||
"cmd": "get_fans",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||||
"errors": {
|
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||||
"cmd": "get_errors",
|
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||||
"kwargs": {},
|
"pools": {"cmd": "get_pools", "kwargs": {}},
|
||||||
},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"fault_light": {
|
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"cmd": "get_fault_light",
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"pools": {
|
|
||||||
"cmd": "get_pools",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"is_mining": {
|
|
||||||
"cmd": "is_mining",
|
|
||||||
"kwargs": {},
|
|
||||||
},
|
|
||||||
"uptime": {
|
|
||||||
"cmd": "get_uptime",
|
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -196,15 +152,9 @@ class LUXMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
"""Gets the config for the miner and sets it as `self.config`.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The config from `self.config`.
|
|
||||||
"""
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
"""Configures miner with yaml config."""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
@@ -283,9 +233,11 @@ class LUXMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
for i in range(
|
||||||
|
board_offset, board_offset + self.expected_hashboards
|
||||||
|
):
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.expected_chips
|
||||||
)
|
)
|
||||||
|
|
||||||
chip_temp = boards[1].get(f"temp{i}")
|
chip_temp = boards[1].get(f"temp{i}")
|
||||||
@@ -407,7 +359,7 @@ class LUXMiner(BaseMiner):
|
|||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
api_stats = await self.api.stats()
|
||||||
@@ -416,17 +368,17 @@ class LUXMiner(BaseMiner):
|
|||||||
|
|
||||||
if api_stats:
|
if api_stats:
|
||||||
try:
|
try:
|
||||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
return round(ideal_rate / 1000, 2)
|
return round(expected_rate / 1000, 2)
|
||||||
if rate_unit == "MH":
|
if rate_unit == "MH":
|
||||||
return round(ideal_rate / 1000000, 2)
|
return round(expected_rate / 1000000, 2)
|
||||||
else:
|
else:
|
||||||
return round(ideal_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ VNISH_DATA_LOC = {
|
|||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}},
|
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"expected_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_expected_hashrate",
|
||||||
"kwargs": {"api_stats": {"api": "stats"}},
|
"kwargs": {"api_stats": {"api": "stats"}},
|
||||||
},
|
},
|
||||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
@@ -46,6 +46,7 @@ VNISH_DATA_LOC = {
|
|||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||||
|
"config": {"cmd": "get_config", "kwargs": {}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,12 @@
|
|||||||
from pyasic.miners.backends.btminer import BTMiner
|
from pyasic.miners.backends.btminer import BTMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M6X(BTMiner):
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.supports_autotuning = True
|
||||||
|
|
||||||
|
|
||||||
class M5X(BTMiner):
|
class M5X(BTMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ class BaseMiner(ABC):
|
|||||||
self.make = None
|
self.make = None
|
||||||
self.model = None
|
self.model = None
|
||||||
# physical attributes
|
# physical attributes
|
||||||
self.ideal_hashboards = 3
|
self.expected_hashboards = 3
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
# data gathering locations
|
# data gathering locations
|
||||||
self.data_locations = None
|
self.data_locations = None
|
||||||
@@ -173,7 +173,7 @@ class BaseMiner(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# Not a data gathering function, since this is used for configuration and not MinerData
|
# Not a data gathering function, since this is used for configuration
|
||||||
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
|
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -387,7 +387,7 @@ class BaseMiner(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_nominal_hashrate(self, *args, **kwargs) -> Optional[float]:
|
async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
|
||||||
"""Get the nominal hashrate from factory if available.
|
"""Get the nominal hashrate from factory if available.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -491,29 +491,7 @@ class BaseMiner(ABC):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
function = getattr(self, self.data_locations[data_name]["cmd"])
|
function = getattr(self, self.data_locations[data_name]["cmd"])
|
||||||
if not data_name == "pools":
|
miner_data[data_name] = await function(**args_to_send)
|
||||||
miner_data[data_name] = await function(**args_to_send)
|
|
||||||
else:
|
|
||||||
pools_data = await function(**args_to_send)
|
|
||||||
if pools_data:
|
|
||||||
try:
|
|
||||||
miner_data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
|
||||||
miner_data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if len(pools_data) > 1:
|
|
||||||
miner_data["pool_2_url"] = pools_data[1]["pool_1_url"]
|
|
||||||
miner_data["pool_2_user"] = pools_data[1]["pool_1_user"]
|
|
||||||
miner_data[
|
|
||||||
"pool_split"
|
|
||||||
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
miner_data["pool_2_url"] = pools_data[0]["pool_2_url"]
|
|
||||||
miner_data["pool_2_user"] = pools_data[0]["pool_2_user"]
|
|
||||||
miner_data["quota"] = "0"
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return miner_data
|
return miner_data
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
@@ -532,16 +510,16 @@ class BaseMiner(ABC):
|
|||||||
data = MinerData(
|
data = MinerData(
|
||||||
ip=str(self.ip),
|
ip=str(self.ip),
|
||||||
make=self.make,
|
make=self.make,
|
||||||
ideal_chips=self.nominal_chips * self.ideal_hashboards,
|
expected_chips=self.expected_chips * self.expected_hashboards,
|
||||||
ideal_hashboards=self.ideal_hashboards,
|
expected_hashboards=self.expected_hashboards,
|
||||||
hashboards=[
|
hashboards=[
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
gathered_data = await self._get_data(
|
gathered_data = await self._get_data(
|
||||||
allow_warning, include=include, exclude=exclude
|
allow_warning=allow_warning, include=include, exclude=exclude
|
||||||
)
|
)
|
||||||
for item in gathered_data:
|
for item in gathered_data:
|
||||||
if gathered_data[item] is not None:
|
if gathered_data[item] is not None:
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ class CGMinerA10X(CGMiner, A10X):
|
|||||||
self, api_stats: dict = None, web_get_all: dict = None
|
self, api_stats: dict = None, web_get_all: dict = None
|
||||||
) -> List[HashBoard]:
|
) -> List[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|||||||
@@ -38,16 +38,13 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_config(self, api_pools: dict = None) -> MinerConfig:
|
async def get_config(self, api_pools: dict = None) -> MinerConfig:
|
||||||
if not api_pools:
|
# get pool data
|
||||||
try:
|
try:
|
||||||
api_pools = await self.api.pools()
|
pools = await self.api.pools()
|
||||||
except APIError as e:
|
except APIError:
|
||||||
logging.warning(e)
|
return self.config
|
||||||
|
|
||||||
if api_pools:
|
self.config = MinerConfig.from_api(pools)
|
||||||
if "POOLS" in api_pools.keys():
|
|
||||||
cfg = MinerConfig().from_api(api_pools["POOLS"])
|
|
||||||
self.config = cfg
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
@@ -136,8 +133,8 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
|
|||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.ideal_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_stats:
|
if not api_stats:
|
||||||
|
|||||||
@@ -13,17 +13,17 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import enum
|
import enum
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Callable, List, Optional, Tuple, Union
|
from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import anyio
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.antminer import *
|
from pyasic.miners.antminer import *
|
||||||
from pyasic.miners.avalonminer import *
|
from pyasic.miners.avalonminer import *
|
||||||
@@ -37,6 +37,7 @@ from pyasic.miners.backends import (
|
|||||||
Hiveon,
|
Hiveon,
|
||||||
LUXMiner,
|
LUXMiner,
|
||||||
VNish,
|
VNish,
|
||||||
|
ePIC,
|
||||||
)
|
)
|
||||||
from pyasic.miners.base import AnyMiner
|
from pyasic.miners.base import AnyMiner
|
||||||
from pyasic.miners.goldshell import *
|
from pyasic.miners.goldshell import *
|
||||||
@@ -44,9 +45,6 @@ from pyasic.miners.innosilicon import *
|
|||||||
from pyasic.miners.unknown import UnknownMiner
|
from pyasic.miners.unknown import UnknownMiner
|
||||||
from pyasic.miners.whatsminer import *
|
from pyasic.miners.whatsminer import *
|
||||||
|
|
||||||
TIMEOUT = 20
|
|
||||||
RETRIES = 3
|
|
||||||
|
|
||||||
|
|
||||||
class MinerTypes(enum.Enum):
|
class MinerTypes(enum.Enum):
|
||||||
ANTMINER = 0
|
ANTMINER = 0
|
||||||
@@ -58,6 +56,7 @@ class MinerTypes(enum.Enum):
|
|||||||
VNISH = 6
|
VNISH = 6
|
||||||
HIVEON = 7
|
HIVEON = 7
|
||||||
LUX_OS = 8
|
LUX_OS = 8
|
||||||
|
EPIC = 9
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES = {
|
||||||
@@ -85,12 +84,15 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19L": BMMinerS19L,
|
"ANTMINER S19L": BMMinerS19L,
|
||||||
"ANTMINER S19 PRO": BMMinerS19Pro,
|
"ANTMINER S19 PRO": BMMinerS19Pro,
|
||||||
"ANTMINER S19J": BMMinerS19j,
|
"ANTMINER S19J": BMMinerS19j,
|
||||||
|
"ANTMINER S19I": BMMinerS19i,
|
||||||
|
"ANTMINER S19+": BMMinerS19Plus,
|
||||||
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
|
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
|
||||||
"ANTMINER S19PRO+": BMMinerS19ProPlus,
|
"ANTMINER S19PRO+": BMMinerS19ProPlus,
|
||||||
"ANTMINER S19J PRO": BMMinerS19jPro,
|
"ANTMINER S19J PRO": BMMinerS19jPro,
|
||||||
"ANTMINER S19 XP": BMMinerS19XP,
|
"ANTMINER S19 XP": BMMinerS19XP,
|
||||||
"ANTMINER S19A": BMMinerS19a,
|
"ANTMINER S19A": BMMinerS19a,
|
||||||
"ANTMINER S19A PRO": BMMinerS19aPro,
|
"ANTMINER S19A PRO": BMMinerS19aPro,
|
||||||
|
"ANTMINER S19 PRO HYD.": BMMinerS19ProHydro,
|
||||||
"ANTMINER T19": BMMinerT19,
|
"ANTMINER T19": BMMinerT19,
|
||||||
},
|
},
|
||||||
MinerTypes.WHATSMINER: {
|
MinerTypes.WHATSMINER: {
|
||||||
@@ -99,6 +101,8 @@ MINER_CLASSES = {
|
|||||||
"M20SV10": BTMinerM20SV10,
|
"M20SV10": BTMinerM20SV10,
|
||||||
"M20SV20": BTMinerM20SV20,
|
"M20SV20": BTMinerM20SV20,
|
||||||
"M20SV30": BTMinerM20SV30,
|
"M20SV30": BTMinerM20SV30,
|
||||||
|
"M20PV10": BTMinerM20PV10,
|
||||||
|
"M20PV30": BTMinerM20PV30,
|
||||||
"M20S+V30": BTMinerM20SPlusV30,
|
"M20S+V30": BTMinerM20SPlusV30,
|
||||||
"M21V10": BTMinerM21V10,
|
"M21V10": BTMinerM21V10,
|
||||||
"M21SV20": BTMinerM21SV20,
|
"M21SV20": BTMinerM21SV20,
|
||||||
@@ -108,6 +112,8 @@ MINER_CLASSES = {
|
|||||||
"M29V10": BTMinerM29V10,
|
"M29V10": BTMinerM29V10,
|
||||||
"M30V10": BTMinerM30V10,
|
"M30V10": BTMinerM30V10,
|
||||||
"M30V20": BTMinerM30V20,
|
"M30V20": BTMinerM30V20,
|
||||||
|
"M30KV10": BTMinerM30KV10,
|
||||||
|
"M30LV10": BTMinerM30LV10,
|
||||||
"M30SV10": BTMinerM30SV10,
|
"M30SV10": BTMinerM30SV10,
|
||||||
"M30SV20": BTMinerM30SV20,
|
"M30SV20": BTMinerM30SV20,
|
||||||
"M30SV30": BTMinerM30SV30,
|
"M30SV30": BTMinerM30SV30,
|
||||||
@@ -157,6 +163,7 @@ MINER_CLASSES = {
|
|||||||
"M30S+VE100": BTMinerM30SPlusVE100,
|
"M30S+VE100": BTMinerM30SPlusVE100,
|
||||||
"M30S+VF20": BTMinerM30SPlusVF20,
|
"M30S+VF20": BTMinerM30SPlusVF20,
|
||||||
"M30S+VF30": BTMinerM30SPlusVF30,
|
"M30S+VF30": BTMinerM30SPlusVF30,
|
||||||
|
"M30S+VG20": BTMinerM30SPlusVG20,
|
||||||
"M30S+VG30": BTMinerM30SPlusVG30,
|
"M30S+VG30": BTMinerM30SPlusVG30,
|
||||||
"M30S+VG40": BTMinerM30SPlusVG40,
|
"M30S+VG40": BTMinerM30SPlusVG40,
|
||||||
"M30S+VG50": BTMinerM30SPlusVG50,
|
"M30S+VG50": BTMinerM30SPlusVG50,
|
||||||
@@ -190,6 +197,9 @@ MINER_CLASSES = {
|
|||||||
"M30S++VJ30": BTMinerM30SPlusPlusVJ30,
|
"M30S++VJ30": BTMinerM30SPlusPlusVJ30,
|
||||||
"M31V10": BTMinerM31V10,
|
"M31V10": BTMinerM31V10,
|
||||||
"M31V20": BTMinerM31V20,
|
"M31V20": BTMinerM31V20,
|
||||||
|
"M31HV10": BTMinerM31HV10,
|
||||||
|
"M31HV40": BTMinerM31HV40,
|
||||||
|
"M31LV10": BTMinerM31LV10,
|
||||||
"M31SV10": BTMinerM31SV10,
|
"M31SV10": BTMinerM31SV10,
|
||||||
"M31SV20": BTMinerM31SV20,
|
"M31SV20": BTMinerM31SV20,
|
||||||
"M31SV30": BTMinerM31SV30,
|
"M31SV30": BTMinerM31SV30,
|
||||||
@@ -205,7 +215,6 @@ MINER_CLASSES = {
|
|||||||
"M31SEV10": BTMinerM31SEV10,
|
"M31SEV10": BTMinerM31SEV10,
|
||||||
"M31SEV20": BTMinerM31SEV20,
|
"M31SEV20": BTMinerM31SEV20,
|
||||||
"M31SEV30": BTMinerM31SEV30,
|
"M31SEV30": BTMinerM31SEV30,
|
||||||
"M31HV40": BTMinerM31HV40,
|
|
||||||
"M31S+V10": BTMinerM31SPlusV10,
|
"M31S+V10": BTMinerM31SPlusV10,
|
||||||
"M31S+V20": BTMinerM31SPlusV20,
|
"M31S+V20": BTMinerM31SPlusV20,
|
||||||
"M31S+V30": BTMinerM31SPlusV30,
|
"M31S+V30": BTMinerM31SPlusV30,
|
||||||
@@ -232,6 +241,7 @@ MINER_CLASSES = {
|
|||||||
"M33V20": BTMinerM33V20,
|
"M33V20": BTMinerM33V20,
|
||||||
"M33V30": BTMinerM33V30,
|
"M33V30": BTMinerM33V30,
|
||||||
"M33SVG30": BTMinerM33SVG30,
|
"M33SVG30": BTMinerM33SVG30,
|
||||||
|
"M33S+VG20": BTMinerM33SPlusVG20,
|
||||||
"M33S+VH20": BTMinerM33SPlusVH20,
|
"M33S+VH20": BTMinerM33SPlusVH20,
|
||||||
"M33S+VH30": BTMinerM33SPlusVH30,
|
"M33S+VH30": BTMinerM33SPlusVH30,
|
||||||
"M33S++VH20": BTMinerM33SPlusPlusVH20,
|
"M33S++VH20": BTMinerM33SPlusPlusVH20,
|
||||||
@@ -241,7 +251,10 @@ MINER_CLASSES = {
|
|||||||
"M36SVE10": BTMinerM36SVE10,
|
"M36SVE10": BTMinerM36SVE10,
|
||||||
"M36S+VG30": BTMinerM36SPlusVG30,
|
"M36S+VG30": BTMinerM36SPlusVG30,
|
||||||
"M36S++VH30": BTMinerM36SPlusPlusVH30,
|
"M36S++VH30": BTMinerM36SPlusPlusVH30,
|
||||||
|
"M39V10": BTMinerM39V10,
|
||||||
"M39V20": BTMinerM39V20,
|
"M39V20": BTMinerM39V20,
|
||||||
|
"M39V30": BTMinerM39V30,
|
||||||
|
"M50VE30": BTMinerM50VE30,
|
||||||
"M50VG30": BTMinerM50VG30,
|
"M50VG30": BTMinerM50VG30,
|
||||||
"M50VH10": BTMinerM50VH10,
|
"M50VH10": BTMinerM50VH10,
|
||||||
"M50VH20": BTMinerM50VH20,
|
"M50VH20": BTMinerM50VH20,
|
||||||
@@ -276,6 +289,25 @@ MINER_CLASSES = {
|
|||||||
"M56SVH30": BTMinerM56SVH30,
|
"M56SVH30": BTMinerM56SVH30,
|
||||||
"M56S+VJ30": BTMinerM56SPlusVJ30,
|
"M56S+VJ30": BTMinerM56SPlusVJ30,
|
||||||
"M59VH30": BTMinerM59VH30,
|
"M59VH30": BTMinerM59VH30,
|
||||||
|
"M60VK10": BTMinerM60VK10,
|
||||||
|
"M60VK20": BTMinerM60VK20,
|
||||||
|
"M60VK30": BTMinerM60VK30,
|
||||||
|
"M60VK40": BTMinerM60VK40,
|
||||||
|
"M60SVK10": BTMinerM60SVK10,
|
||||||
|
"M60SVK20": BTMinerM60SVK20,
|
||||||
|
"M60SVK30": BTMinerM60SVK30,
|
||||||
|
"M60SVK40": BTMinerM60SVK40,
|
||||||
|
"M63VK10": BTMinerM63VK10,
|
||||||
|
"M63VK20": BTMinerM63VK20,
|
||||||
|
"M63VK30": BTMinerM63VK30,
|
||||||
|
"M63SVK10": BTMinerM63SVK10,
|
||||||
|
"M63SVK20": BTMinerM63SVK20,
|
||||||
|
"M63SVK30": BTMinerM63SVK30,
|
||||||
|
"M66VK20": BTMinerM66VK20,
|
||||||
|
"M66VK30": BTMinerM66VK30,
|
||||||
|
"M66SVK20": BTMinerM66SVK20,
|
||||||
|
"M66SVK30": BTMinerM66SVK30,
|
||||||
|
"M66SVK40": BTMinerM66SVK40,
|
||||||
},
|
},
|
||||||
MinerTypes.AVALONMINER: {
|
MinerTypes.AVALONMINER: {
|
||||||
None: CGMinerAvalon,
|
None: CGMinerAvalon,
|
||||||
@@ -315,11 +347,17 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER T17+": BOSMinerT17Plus,
|
"ANTMINER T17+": BOSMinerT17Plus,
|
||||||
"ANTMINER T17E": BOSMinerT17e,
|
"ANTMINER T17E": BOSMinerT17e,
|
||||||
"ANTMINER S19": BOSMinerS19,
|
"ANTMINER S19": BOSMinerS19,
|
||||||
|
"ANTMINER S19+": BOSMinerS19Plus,
|
||||||
"ANTMINER S19 PRO": BOSMinerS19Pro,
|
"ANTMINER S19 PRO": BOSMinerS19Pro,
|
||||||
|
"ANTMINER S19A": BOSMinerS19a,
|
||||||
|
"ANTMINER S19A Pro": BOSMinerS19aPro,
|
||||||
"ANTMINER S19J": BOSMinerS19j,
|
"ANTMINER S19J": BOSMinerS19j,
|
||||||
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
||||||
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
||||||
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
||||||
|
"ANTMINER S19J PRO+": BOSMinerS19jProPlus,
|
||||||
|
"ANTMINER S19K PRO NOPIC": BOSMinerS19kProNoPIC,
|
||||||
|
"ANTMINER S19XP": BOSMinerS19XP,
|
||||||
"ANTMINER T19": BOSMinerT19,
|
"ANTMINER T19": BOSMinerT19,
|
||||||
},
|
},
|
||||||
MinerTypes.VNISH: {
|
MinerTypes.VNISH: {
|
||||||
@@ -336,6 +374,16 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19A PRO": VNishS19aPro,
|
"ANTMINER S19A PRO": VNishS19aPro,
|
||||||
"ANTMINER T19": VNishT19,
|
"ANTMINER T19": VNishT19,
|
||||||
},
|
},
|
||||||
|
MinerTypes.EPIC: {
|
||||||
|
None: ePIC,
|
||||||
|
"ANTMINER S19": ePICS19,
|
||||||
|
"ANTMINER S19 PRO": ePICS19Pro,
|
||||||
|
"ANTMINER S19J": ePICS19j,
|
||||||
|
"ANTMINER S19J PRO": ePICS19jPro,
|
||||||
|
"ANTMINER S19J PRO+": ePICS19jProPlus,
|
||||||
|
"ANTMINER S19K PRO": ePICS19kPro,
|
||||||
|
"ANTMINER S19 XP": ePICS19XP,
|
||||||
|
},
|
||||||
MinerTypes.HIVEON: {
|
MinerTypes.HIVEON: {
|
||||||
None: Hiveon,
|
None: Hiveon,
|
||||||
"ANTMINER T9": HiveonT9,
|
"ANTMINER T9": HiveonT9,
|
||||||
@@ -348,24 +396,18 @@ MINER_CLASSES = {
|
|||||||
|
|
||||||
|
|
||||||
async def concurrent_get_first_result(tasks: list, verification_func: Callable):
|
async def concurrent_get_first_result(tasks: list, verification_func: Callable):
|
||||||
while True:
|
res = None
|
||||||
await asyncio.sleep(0)
|
for fut in asyncio.as_completed(tasks):
|
||||||
if len(tasks) == 0:
|
res = await fut
|
||||||
return
|
if verification_func(res):
|
||||||
for task in tasks:
|
break
|
||||||
if task.done():
|
for t in tasks:
|
||||||
try:
|
t.cancel()
|
||||||
result = await task
|
try:
|
||||||
except asyncio.CancelledError:
|
await t
|
||||||
for t in tasks:
|
except asyncio.CancelledError:
|
||||||
t.cancel()
|
pass
|
||||||
raise
|
return res
|
||||||
else:
|
|
||||||
if not verification_func(result):
|
|
||||||
continue
|
|
||||||
for t in tasks:
|
|
||||||
t.cancel()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class MinerFactory:
|
class MinerFactory:
|
||||||
@@ -375,7 +417,9 @@ class MinerFactory:
|
|||||||
def clear_cached_miners(self):
|
def clear_cached_miners(self):
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
|
||||||
async def get_multiple_miners(self, ips: List[str], limit: int = 200):
|
async def get_multiple_miners(
|
||||||
|
self, ips: List[str], limit: int = 200
|
||||||
|
) -> List[AnyMiner]:
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
async for miner in self.get_miner_generator(ips, limit):
|
async for miner in self.get_miner_generator(ips, limit):
|
||||||
@@ -383,7 +427,7 @@ class MinerFactory:
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
async def get_miner_generator(self, ips: list, limit: int = 200):
|
async def get_miner_generator(self, ips: list, limit: int = 200) -> AsyncGenerator:
|
||||||
tasks = []
|
tasks = []
|
||||||
semaphore = asyncio.Semaphore(limit)
|
semaphore = asyncio.Semaphore(limit)
|
||||||
|
|
||||||
@@ -391,13 +435,10 @@ class MinerFactory:
|
|||||||
tasks.append(asyncio.create_task(self.get_miner(ip)))
|
tasks.append(asyncio.create_task(self.get_miner(ip)))
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
await semaphore.acquire()
|
async with semaphore:
|
||||||
try:
|
|
||||||
result = await task
|
result = await task
|
||||||
if result is not None:
|
if result is not None:
|
||||||
yield result
|
yield result
|
||||||
finally:
|
|
||||||
semaphore.release()
|
|
||||||
|
|
||||||
async def get_miner(self, ip: str):
|
async def get_miner(self, ip: str):
|
||||||
ip = str(ip)
|
ip = str(ip)
|
||||||
@@ -406,12 +447,14 @@ class MinerFactory:
|
|||||||
|
|
||||||
miner_type = None
|
miner_type = None
|
||||||
|
|
||||||
for _ in range(RETRIES):
|
for _ in range(settings.get("factory_get_retries", 1)):
|
||||||
task = asyncio.create_task(self._get_miner_type(ip))
|
task = asyncio.create_task(self._get_miner_type(ip))
|
||||||
try:
|
try:
|
||||||
miner_type = await asyncio.wait_for(task, timeout=TIMEOUT)
|
miner_type = await asyncio.wait_for(
|
||||||
|
task, timeout=settings.get("factory_get_timeout", 3)
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
task.cancel()
|
continue
|
||||||
else:
|
else:
|
||||||
if miner_type is not None:
|
if miner_type is not None:
|
||||||
break
|
break
|
||||||
@@ -426,6 +469,7 @@ class MinerFactory:
|
|||||||
MinerTypes.GOLDSHELL: self.get_miner_model_goldshell,
|
MinerTypes.GOLDSHELL: self.get_miner_model_goldshell,
|
||||||
MinerTypes.BRAIINS_OS: self.get_miner_model_braiins_os,
|
MinerTypes.BRAIINS_OS: self.get_miner_model_braiins_os,
|
||||||
MinerTypes.VNISH: self.get_miner_model_vnish,
|
MinerTypes.VNISH: self.get_miner_model_vnish,
|
||||||
|
MinerTypes.EPIC: self.get_miner_model_epic,
|
||||||
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
||||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||||
}
|
}
|
||||||
@@ -434,12 +478,21 @@ class MinerFactory:
|
|||||||
if fn is not None:
|
if fn is not None:
|
||||||
task = asyncio.create_task(fn(ip))
|
task = asyncio.create_task(fn(ip))
|
||||||
try:
|
try:
|
||||||
miner_model = await asyncio.wait_for(task, timeout=30)
|
miner_model = await asyncio.wait_for(
|
||||||
|
task, timeout=settings.get("factory_get_timeout", 3)
|
||||||
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
task.cancel()
|
pass
|
||||||
|
|
||||||
|
boser_enabled = None
|
||||||
|
if miner_type == MinerTypes.BRAIINS_OS:
|
||||||
|
boser_enabled = await self.get_boser_braiins_os(ip)
|
||||||
|
|
||||||
miner = self._select_miner_from_classes(
|
miner = self._select_miner_from_classes(
|
||||||
ip, miner_type=miner_type, miner_model=miner_model
|
ip,
|
||||||
|
miner_type=miner_type,
|
||||||
|
miner_model=miner_model,
|
||||||
|
boser_enabled=boser_enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
if miner is not None and not isinstance(miner, UnknownMiner):
|
if miner is not None and not isinstance(miner, UnknownMiner):
|
||||||
@@ -455,24 +508,44 @@ class MinerFactory:
|
|||||||
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||||
|
|
||||||
async def _get_miner_web(self, ip: str):
|
async def _get_miner_web(self, ip: str):
|
||||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
tasks = []
|
||||||
async with httpx.AsyncClient(verify=False) as session:
|
try:
|
||||||
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||||
|
async with httpx.AsyncClient(
|
||||||
|
transport=settings.transport(verify=False)
|
||||||
|
) as session:
|
||||||
|
tasks = [
|
||||||
|
asyncio.create_task(self._web_ping(session, url)) for url in urls
|
||||||
|
]
|
||||||
|
|
||||||
text, resp = await concurrent_get_first_result(
|
text, resp = await concurrent_get_first_result(
|
||||||
tasks, lambda x: x[0] is not None
|
tasks,
|
||||||
)
|
lambda x: x[0] is not None
|
||||||
if text is not None:
|
and self._parse_web_type(x[0], x[1]) is not None,
|
||||||
return self._parse_web_type(text, resp)
|
)
|
||||||
|
if text is not None:
|
||||||
|
return self._parse_web_type(text, resp)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
for t in tasks:
|
||||||
|
t.cancel()
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _web_ping(
|
async def _web_ping(
|
||||||
session: httpx.AsyncClient, url: str
|
session: httpx.AsyncClient, url: str
|
||||||
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
||||||
try:
|
try:
|
||||||
resp = await session.get(url, follow_redirects=False)
|
resp = await session.get(url, follow_redirects=True)
|
||||||
return resp.text, resp
|
return resp.text, resp
|
||||||
except (httpx.HTTPError, asyncio.TimeoutError):
|
except (
|
||||||
|
httpx.HTTPError,
|
||||||
|
asyncio.TimeoutError,
|
||||||
|
anyio.EndOfStream,
|
||||||
|
anyio.ClosedResourceError,
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@@ -482,38 +555,57 @@ class MinerFactory:
|
|||||||
"www-authenticate", ""
|
"www-authenticate", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.ANTMINER
|
return MinerTypes.ANTMINER
|
||||||
if web_resp.status_code == 307 and "https://" in web_resp.headers.get(
|
if len(web_resp.history) > 0:
|
||||||
"location", ""
|
history_resp = web_resp.history[0]
|
||||||
):
|
if (
|
||||||
return MinerTypes.WHATSMINER
|
"/cgi-bin/luci" in web_text
|
||||||
|
and history_resp.status_code == 307
|
||||||
|
and "https://" in history_resp.headers.get("location", "")
|
||||||
|
):
|
||||||
|
return MinerTypes.WHATSMINER
|
||||||
if "Braiins OS" in web_text:
|
if "Braiins OS" in web_text:
|
||||||
return MinerTypes.BRAIINS_OS
|
return MinerTypes.BRAIINS_OS
|
||||||
if "cloud-box" in web_text:
|
if "cloud-box" in web_text:
|
||||||
return MinerTypes.GOLDSHELL
|
return MinerTypes.GOLDSHELL
|
||||||
if "AnthillOS" in web_text:
|
if "AnthillOS" in web_text:
|
||||||
return MinerTypes.VNISH
|
return MinerTypes.VNISH
|
||||||
|
if "Miner Web Dashboard" in web_text:
|
||||||
|
return MinerTypes.EPIC
|
||||||
if "Avalon" in web_text:
|
if "Avalon" in web_text:
|
||||||
return MinerTypes.AVALONMINER
|
return MinerTypes.AVALONMINER
|
||||||
if "DragonMint" in web_text:
|
if "DragonMint" in web_text:
|
||||||
return MinerTypes.INNOSILICON
|
return MinerTypes.INNOSILICON
|
||||||
|
|
||||||
async def _get_miner_socket(self, ip: str):
|
async def _get_miner_socket(self, ip: str):
|
||||||
commands = ["version", "devdetails"]
|
tasks = []
|
||||||
tasks = [asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands]
|
try:
|
||||||
|
commands = ["version", "devdetails"]
|
||||||
|
tasks = [
|
||||||
|
asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands
|
||||||
|
]
|
||||||
|
|
||||||
data = await concurrent_get_first_result(
|
data = await concurrent_get_first_result(
|
||||||
tasks, lambda x: x is not None and self._parse_socket_type(x) is not None
|
tasks,
|
||||||
)
|
lambda x: x is not None and self._parse_socket_type(x) is not None,
|
||||||
if data is not None:
|
)
|
||||||
d = self._parse_socket_type(data)
|
if data is not None:
|
||||||
return d
|
d = self._parse_socket_type(data)
|
||||||
|
return d
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
for t in tasks:
|
||||||
|
t.cancel()
|
||||||
|
try:
|
||||||
|
await t
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
|
async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
|
||||||
data = b""
|
data = b""
|
||||||
try:
|
try:
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
asyncio.open_connection(str(ip), 4028), timeout=30
|
asyncio.open_connection(str(ip), 4028),
|
||||||
|
timeout=settings.get("factory_get_timeout", 3),
|
||||||
)
|
)
|
||||||
except (ConnectionError, OSError, asyncio.TimeoutError):
|
except (ConnectionError, OSError, asyncio.TimeoutError):
|
||||||
return
|
return
|
||||||
@@ -579,12 +671,12 @@ class MinerFactory:
|
|||||||
location: str,
|
location: str,
|
||||||
auth: Optional[httpx.DigestAuth] = None,
|
auth: Optional[httpx.DigestAuth] = None,
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
async with httpx.AsyncClient(verify=False) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
try:
|
try:
|
||||||
data = await session.get(
|
data = await session.get(
|
||||||
f"http://{str(ip)}{location}",
|
f"http://{str(ip)}{location}",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
timeout=30,
|
timeout=settings.get("factory_get_timeout", 3),
|
||||||
)
|
)
|
||||||
except (httpx.HTTPError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
logger.info(f"{ip}: Web command timeout.")
|
logger.info(f"{ip}: Web command timeout.")
|
||||||
@@ -683,9 +775,13 @@ class MinerFactory:
|
|||||||
ip: ipaddress.ip_address,
|
ip: ipaddress.ip_address,
|
||||||
miner_model: Union[str, None],
|
miner_model: Union[str, None],
|
||||||
miner_type: Union[MinerTypes, None],
|
miner_type: Union[MinerTypes, None],
|
||||||
|
boser_enabled: bool = None,
|
||||||
) -> AnyMiner:
|
) -> AnyMiner:
|
||||||
|
kwargs = {}
|
||||||
|
if boser_enabled is not None:
|
||||||
|
kwargs["boser"] = boser_enabled
|
||||||
try:
|
try:
|
||||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, **kwargs)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
if miner_type in MINER_CLASSES:
|
if miner_type in MINER_CLASSES:
|
||||||
return MINER_CLASSES[miner_type][None](ip)
|
return MINER_CLASSES[miner_type][None](ip)
|
||||||
@@ -751,7 +847,8 @@ class MinerFactory:
|
|||||||
async def get_miner_model_whatsminer(self, ip: str):
|
async def get_miner_model_whatsminer(self, ip: str):
|
||||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"]
|
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
||||||
|
miner_model = miner_model[:-1] + "0"
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
@@ -770,18 +867,20 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(verify=False) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
auth_req = await session.post(
|
auth_req = await session.post(
|
||||||
f"http://{ip}/api/auth",
|
f"http://{ip}/api/auth",
|
||||||
data={"username": "admin", "password": "admin"},
|
data={"username": "admin", "password": "admin"},
|
||||||
)
|
)
|
||||||
auth = auth_req.json()["jwt"]
|
auth = auth_req.json()["jwt"]
|
||||||
|
|
||||||
web_data = (await session.post(
|
web_data = (
|
||||||
|
await session.post(
|
||||||
f"http://{ip}/api/type",
|
f"http://{ip}/api/type",
|
||||||
headers={"Authorization": "Bearer " + auth},
|
headers={"Authorization": "Bearer " + auth},
|
||||||
data={},
|
data={},
|
||||||
)).json()
|
)
|
||||||
|
).json()
|
||||||
return web_data["type"]
|
return web_data["type"]
|
||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -798,7 +897,7 @@ class MinerFactory:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(verify=False) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
d = await session.post(
|
d = await session.post(
|
||||||
f"http://{ip}/graphql",
|
f"http://{ip}/graphql",
|
||||||
json={"query": "{bosminer {info{modelName}}}"},
|
json={"query": "{bosminer {info{modelName}}}"},
|
||||||
@@ -810,6 +909,15 @@ class MinerFactory:
|
|||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def get_boser_braiins_os(self, ip: str):
|
||||||
|
# TODO: refine this check
|
||||||
|
try:
|
||||||
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
|
return sock_json_data["STATUS"][0]["Msg"].split(" ")[0].upper() == "BOSER"
|
||||||
|
except LookupError:
|
||||||
|
# let the bosminer class decide
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
||||||
sock_json_data = await self.send_api_command(ip, "stats")
|
sock_json_data = await self.send_api_command(ip, "stats")
|
||||||
try:
|
try:
|
||||||
@@ -828,6 +936,14 @@ class MinerFactory:
|
|||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def get_miner_model_epic(self, ip: str) -> Optional[str]:
|
||||||
|
sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
|
||||||
|
try:
|
||||||
|
miner_model = sock_json_data["Model"]
|
||||||
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
|
||||||
async def get_miner_model_hiveon(self, ip: str) -> Optional[str]:
|
async def get_miner_model_hiveon(self, ip: str) -> Optional[str]:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class Z15(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Z15"
|
self.model = "Z15"
|
||||||
self.nominal_chips = 3
|
self.expected_chips = 3
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class S17(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S17"
|
self.model = "S17"
|
||||||
self.nominal_chips = 48
|
self.expected_chips = 48
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class S17Plus(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.model = "S17+"
|
self.model = "S17+"
|
||||||
self.nominal_chips = 65
|
self.expected_chips = 65
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class S17Pro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S17 Pro"
|
self.model = "S17 Pro"
|
||||||
self.nominal_chips = 48
|
self.expected_chips = 48
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -48,5 +48,5 @@ class S17e(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S17e"
|
self.model = "S17e"
|
||||||
self.nominal_chips = 135
|
self.expected_chips = 135
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class T17(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T17"
|
self.model = "T17"
|
||||||
self.nominal_chips = 30
|
self.expected_chips = 30
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class T17Plus(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T17+"
|
self.model = "T17+"
|
||||||
self.nominal_chips = 44
|
self.expected_chips = 44
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -40,5 +40,5 @@ class T17e(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T17e"
|
self.model = "T17e"
|
||||||
self.nominal_chips = 78
|
self.expected_chips = 78
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class S19(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19"
|
self.model = "S19"
|
||||||
self.nominal_chips = 76
|
self.expected_chips = 76
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19 No PIC"
|
self.model = "S19 No PIC"
|
||||||
self.nominal_chips = 88
|
self.expected_chips = 88
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +40,25 @@ class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19 Pro"
|
self.model = "S19 Pro"
|
||||||
self.nominal_chips = 114
|
self.expected_chips = 114
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19i(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19i"
|
||||||
|
self.expected_chips = 80
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19+"
|
||||||
|
self.expected_chips = 80
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +67,7 @@ class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19 Pro+"
|
self.model = "S19 Pro+"
|
||||||
self.nominal_chips = 120
|
self.expected_chips = 120
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +76,7 @@ class S19XP(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19 XP"
|
self.model = "S19 XP"
|
||||||
self.nominal_chips = 110
|
self.expected_chips = 110
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +85,7 @@ class S19a(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19a"
|
self.model = "S19a"
|
||||||
self.nominal_chips = 72
|
self.expected_chips = 72
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -76,7 +94,7 @@ class S19aPro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19a Pro"
|
self.model = "S19a Pro"
|
||||||
self.nominal_chips = 100
|
self.expected_chips = 100
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +103,7 @@ class S19j(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19j"
|
self.model = "S19j"
|
||||||
self.nominal_chips = 114
|
self.expected_chips = 114
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +112,7 @@ class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19j No PIC"
|
self.model = "S19j No PIC"
|
||||||
self.nominal_chips = 88
|
self.expected_chips = 88
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -103,7 +121,25 @@ class S19jPro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19j Pro"
|
self.model = "S19j Pro"
|
||||||
self.nominal_chips = 126
|
self.expected_chips = 126
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19j Pro+"
|
||||||
|
self.expected_chips = 120
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19kPro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19k Pro"
|
||||||
|
self.expected_chips = 77
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
@@ -112,5 +148,24 @@ class S19L(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S19L"
|
self.model = "S19L"
|
||||||
self.nominal_chips = 76
|
self.expected_chips = 76
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19k Pro No PIC"
|
||||||
|
self.expected_chips = 77
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19 Pro Hydro"
|
||||||
|
self.expected_chips = 180
|
||||||
|
self.expected_hashboards = 4
|
||||||
|
self.fan_count = 0
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class T19(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T19"
|
self.model = "T19"
|
||||||
self.nominal_chips = 76
|
self.expected_chips = 76
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -20,11 +20,17 @@ from .S19 import (
|
|||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
S19aPro,
|
S19aPro,
|
||||||
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jNoPIC,
|
S19jNoPIC,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
|
S19jProPlus,
|
||||||
|
S19kPro,
|
||||||
|
S19kProNoPIC,
|
||||||
S19NoPIC,
|
S19NoPIC,
|
||||||
|
S19Plus,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
|
S19ProHydro,
|
||||||
S19ProPlus,
|
S19ProPlus,
|
||||||
)
|
)
|
||||||
from .T19 import T19
|
from .T19 import T19
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class D3(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "D3"
|
self.model = "D3"
|
||||||
self.nominal_chips = 60
|
self.expected_chips = 60
|
||||||
self.ideal_hashboards = 3
|
self.expected_hashboards = 3
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class HS3(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "HS3"
|
self.model = "HS3"
|
||||||
self.nominal_chips = 92
|
self.expected_chips = 92
|
||||||
self.ideal_hashboards = 3
|
self.expected_hashboards = 3
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ class L3Plus(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "L3+"
|
self.model = "L3+"
|
||||||
self.nominal_chips = 72
|
self.expected_chips = 72
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class DR5(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "DR5"
|
self.model = "DR5"
|
||||||
self.nominal_chips = 72
|
self.expected_chips = 72
|
||||||
self.ideal_hashboards = 3
|
self.expected_hashboards = 3
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ class L7(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "L7"
|
self.model = "L7"
|
||||||
self.nominal_chips = 120
|
self.expected_chips = 120
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class E9Pro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "E9Pro"
|
self.model = "E9Pro"
|
||||||
self.nominal_chips = 8
|
self.expected_chips = 8
|
||||||
self.ideal_hashboards = 2
|
self.expected_hashboards = 2
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class S9(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S9"
|
self.model = "S9"
|
||||||
self.nominal_chips = 63
|
self.expected_chips = 63
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class S9i(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S9i"
|
self.model = "S9i"
|
||||||
self.nominal_chips = 63
|
self.expected_chips = 63
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -40,5 +40,5 @@ class S9j(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "S9j"
|
self.model = "S9j"
|
||||||
self.nominal_chips = 63
|
self.expected_chips = 63
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class T9(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T9"
|
self.model = "T9"
|
||||||
self.nominal_chips = 54
|
self.expected_chips = 54
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 1026"
|
self.model = "Avalon 1026"
|
||||||
self.nominal_chips = 80
|
self.expected_chips = 80
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 1047"
|
self.model = "Avalon 1047"
|
||||||
self.nominal_chips = 80
|
self.expected_chips = 80
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 1066"
|
self.model = "Avalon 1066"
|
||||||
self.nominal_chips = 114
|
self.expected_chips = 114
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ class Avalon1166Pro(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 1166 Pro"
|
self.model = "Avalon 1166 Pro"
|
||||||
self.nominal_chips = 120
|
self.expected_chips = 120
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ class Avalon1246(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 1246"
|
self.model = "Avalon 1246"
|
||||||
self.nominal_chips = 120
|
self.expected_chips = 120
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 721"
|
self.model = "Avalon 721"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 18
|
self.expected_chips = 18
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 741"
|
self.model = "Avalon 741"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 22
|
self.expected_chips = 22
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 761"
|
self.model = "Avalon 761"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 18
|
self.expected_chips = 18
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 821"
|
self.model = "Avalon 821"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 26
|
self.expected_chips = 26
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 841"
|
self.model = "Avalon 841"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 26
|
self.expected_chips = 26
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 851"
|
self.model = "Avalon 851"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 26
|
self.expected_chips = 26
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -22,6 +22,6 @@ class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "Avalon 921"
|
self.model = "Avalon 921"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 26
|
self.expected_chips = 26
|
||||||
self.fan_count = 1
|
self.fan_count = 1
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ class CK5(GoldshellMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "CK5"
|
self.model = "CK5"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 46
|
self.expected_chips = 46
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ class HS5(GoldshellMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "HS5"
|
self.model = "HS5"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 46
|
self.expected_chips = 46
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ class KD5(GoldshellMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "KD5"
|
self.model = "KD5"
|
||||||
self.ideal_hashboards = 4
|
self.expected_hashboards = 4
|
||||||
self.nominal_chips = 46
|
self.expected_chips = 46
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ class KDMax(GoldshellMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "KD Max"
|
self.model = "KD Max"
|
||||||
self.ideal_hashboards = 3
|
self.expected_hashboards = 3
|
||||||
self.nominal_chips = 84
|
self.expected_chips = 84
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class T3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "T3H+"
|
self.model = "T3H+"
|
||||||
self.nominal_chips = 114
|
self.expected_chips = 114
|
||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ class M20V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20 V10"
|
self.model = "M20 V10"
|
||||||
self.nominal_chips = 70
|
self.expected_chips = 70
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal file
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M20PV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20P V10"
|
||||||
|
self.expected_chips = 156
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M20PV30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20P V30"
|
||||||
|
self.expected_chips = 148
|
||||||
|
self.fan_count = 2
|
||||||
@@ -24,7 +24,7 @@ class M20SV10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S V10"
|
self.model = "M20S V10"
|
||||||
self.nominal_chips = 105
|
self.expected_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class M20SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S V20"
|
self.model = "M20S V20"
|
||||||
self.nominal_chips = 111
|
self.expected_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -42,8 +42,5 @@ class M20SV30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S V30"
|
self.model = "M20S V30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 140
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M20SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class M20SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S+ V30"
|
self.model = "M20S+ V30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M20S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M20S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,8 +24,5 @@ class M21V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M21 V10"
|
self.model = "M21 V10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 33
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M21V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class M21SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M21S V20"
|
self.model = "M21S V20"
|
||||||
self.nominal_chips = 66
|
self.expected_chips = 66
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class M21SV60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M21S V60"
|
self.model = "M21S V60"
|
||||||
self.nominal_chips = 105
|
self.expected_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -42,8 +42,5 @@ class M21SV70(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M21S V70"
|
self.model = "M21S V70"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 111
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M21SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class M21SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M21S+ V20"
|
self.model = "M21S+ V20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M21S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M21S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,5 +24,5 @@ class M29V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M29 V10"
|
self.model = "M29 V10"
|
||||||
self.nominal_chips = 50
|
self.expected_chips = 50
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .M20 import M20V10
|
from .M20 import M20V10
|
||||||
|
from .M20P import M20PV10, M20PV30
|
||||||
from .M20S import M20SV10, M20SV20, M20SV30
|
from .M20S import M20SV10, M20SV20, M20SV30
|
||||||
from .M20S_Plus import M20SPlusV30
|
from .M20S_Plus import M20SPlusV30
|
||||||
from .M21 import M21V10
|
from .M21 import M21V10
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ class M30V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30 V10"
|
self.model = "M30 V10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 105
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -36,8 +33,5 @@ class M30V20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30 V20"
|
self.model = "M30 V20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 111
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
29
pyasic/miners/types/whatsminer/M3X/M30K.py
Normal file
29
pyasic/miners/types/whatsminer/M3X/M30K.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pyasic.miners.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M30KV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M30K V10"
|
||||||
|
self.expected_hashboards = 4
|
||||||
|
self.expected_chips = 240
|
||||||
|
self.fan_count = 2
|
||||||
29
pyasic/miners/types/whatsminer/M3X/M30L.py
Normal file
29
pyasic/miners/types/whatsminer/M3X/M30L.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pyasic.miners.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M30LV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M30L V10"
|
||||||
|
self.board_num = 4
|
||||||
|
self.expected_chips = 144
|
||||||
|
self.fan_count = 2
|
||||||
@@ -24,10 +24,7 @@ class M30SV10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V10"
|
self.model = "M30S V10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 148
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -36,10 +33,7 @@ class M30SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V20"
|
self.model = "M30S V20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 156
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -48,10 +42,7 @@ class M30SV30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V30"
|
self.model = "M30S V30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 164
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -60,10 +51,7 @@ class M30SV40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V40"
|
self.model = "M30S V40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 172
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +60,7 @@ class M30SV50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V50"
|
self.model = "M30S V50"
|
||||||
self.nominal_chips = 156
|
self.expected_chips = 156
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -81,10 +69,7 @@ class M30SV60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V60"
|
self.model = "M30S V60"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 164
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +78,7 @@ class M30SV70(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V70"
|
self.model = "M30S V70"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -105,10 +90,7 @@ class M30SV80(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S V80"
|
self.model = "M30S V80"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 129
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SV80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +99,7 @@ class M30SVE10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE10"
|
self.model = "M30S VE10"
|
||||||
self.nominal_chips = 105
|
self.expected_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -126,7 +108,7 @@ class M30SVE20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE20"
|
self.model = "M30S VE20"
|
||||||
self.nominal_chips = 111
|
self.expected_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -135,10 +117,7 @@ class M30SVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE30"
|
self.model = "M30S VE30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 117
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -147,10 +126,7 @@ class M30SVE40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE40"
|
self.model = "M30S VE40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 123
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVE40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -159,10 +135,7 @@ class M30SVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE50"
|
self.model = "M30S VE50"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 129
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -171,7 +144,7 @@ class M30SVE60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE60"
|
self.model = "M30S VE60"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30SVE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30SVE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -183,7 +156,7 @@ class M30SVE70(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VE70"
|
self.model = "M30S VE70"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30SVE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30SVE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -195,10 +168,7 @@ class M30SVF10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VF10"
|
self.model = "M30S VF10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 70
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVF10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -207,10 +177,7 @@ class M30SVF20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VF20"
|
self.model = "M30S VF20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 74
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVF20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -219,10 +186,7 @@ class M30SVF30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VF30"
|
self.model = "M30S VF30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 78
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVF30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -231,7 +195,7 @@ class M30SVG10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VG10"
|
self.model = "M30S VG10"
|
||||||
self.nominal_chips = 66
|
self.expected_chips = 66
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -240,7 +204,7 @@ class M30SVG20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VG20"
|
self.model = "M30S VG20"
|
||||||
self.nominal_chips = 70
|
self.expected_chips = 70
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -249,10 +213,7 @@ class M30SVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VG30"
|
self.model = "M30S VG30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 74
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -261,10 +222,7 @@ class M30SVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VG40"
|
self.model = "M30S VG40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 78
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVG40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -273,10 +231,7 @@ class M30SVH10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH10"
|
self.model = "M30S VH10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 64
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -285,10 +240,7 @@ class M30SVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH20"
|
self.model = "M30S VH20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 66
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -297,7 +249,7 @@ class M30SVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH30"
|
self.model = "M30S VH30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30SVH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30SVH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -309,10 +261,7 @@ class M30SVH40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH40"
|
self.model = "M30S VH40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 64
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -321,10 +270,7 @@ class M30SVH50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH50"
|
self.model = "M30S VH50"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 66
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVH50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -333,7 +279,7 @@ class M30SVH60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VH60"
|
self.model = "M30S VH60"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30SVH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30SVH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -345,8 +291,5 @@ class M30SVI20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S VI20"
|
self.model = "M30S VI20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 70
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30SVI20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ class M30SPlusV10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V10"
|
self.model = "M30S+ V10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 215
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -36,10 +33,7 @@ class M30SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V20"
|
self.model = "M30S+ V20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 255
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +42,7 @@ class M30SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V30"
|
self.model = "M30S+ V30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -60,10 +54,7 @@ class M30SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V40"
|
self.model = "M30S+ V40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 235
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -72,10 +63,7 @@ class M30SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V50"
|
self.model = "M30S+ V50"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 225
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -84,10 +72,7 @@ class M30SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V60"
|
self.model = "M30S+ V60"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 245
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -96,10 +81,7 @@ class M30SPlusV70(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V70"
|
self.model = "M30S+ V70"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 235
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -108,10 +90,7 @@ class M30SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V80"
|
self.model = "M30S+ V80"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 245
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -120,10 +99,7 @@ class M30SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V90"
|
self.model = "M30S+ V90"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 225
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -132,10 +108,7 @@ class M30SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ V100"
|
self.model = "M30S+ V100"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 215
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ V100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -144,10 +117,7 @@ class M30SPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE30"
|
self.model = "M30S+ VE30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 148
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -156,7 +126,7 @@ class M30SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE40"
|
self.model = "M30S+ VE40"
|
||||||
self.nominal_chips = 156
|
self.expected_chips = 156
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -165,7 +135,7 @@ class M30SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE50"
|
self.model = "M30S+ VE50"
|
||||||
self.nominal_chips = 164
|
self.expected_chips = 164
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -174,10 +144,7 @@ class M30SPlusVE60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE60"
|
self.model = "M30S+ VE60"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 172
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -186,7 +153,7 @@ class M30SPlusVE70(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE70"
|
self.model = "M30S+ VE70"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30S+ VE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30S+ VE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -198,7 +165,7 @@ class M30SPlusVE80(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE80"
|
self.model = "M30S+ VE80"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -210,7 +177,7 @@ class M30SPlusVE90(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE90"
|
self.model = "M30S+ VE90"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30S+ VE90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30S+ VE90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -222,7 +189,7 @@ class M30SPlusVE100(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE100"
|
self.model = "M30S+ VE100"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 0
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Unknown chip count for miner type M30S+ VE100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
"Unknown chip count for miner type M30S+ VE100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
)
|
)
|
||||||
@@ -234,7 +201,7 @@ class M30SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VF20"
|
self.model = "M30S+ VF20"
|
||||||
self.nominal_chips = 111
|
self.expected_chips = 111
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -243,22 +210,16 @@ class M30SPlusVF30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VF30"
|
self.model = "M30S+ VF30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 117
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VF30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
class M36SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
class M30SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M36S+ VG30"
|
self.model = "M30S+ VG20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 82
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M36SPlusVG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -267,10 +228,7 @@ class M30SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VG30"
|
self.model = "M30S+ VG30"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 78
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -279,7 +237,7 @@ class M30SPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VG40"
|
self.model = "M30S+ VG40"
|
||||||
self.nominal_chips = 105
|
self.expected_chips = 105
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -288,10 +246,7 @@ class M30SPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VG50"
|
self.model = "M30S+ VG50"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 111
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VG50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -300,7 +255,7 @@ class M30SPlusVG60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VG60"
|
self.model = "M30S+ VG60"
|
||||||
self.nominal_chips = 86
|
self.expected_chips = 86
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -309,10 +264,7 @@ class M30SPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH10"
|
self.model = "M30S+ VH10"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 64
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -321,10 +273,7 @@ class M30SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH20"
|
self.model = "M30S+ VH20"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 66
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -333,7 +282,7 @@ class M30SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH30"
|
self.model = "M30S+ VH30"
|
||||||
self.nominal_chips = 70
|
self.expected_chips = 70
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -342,10 +291,7 @@ class M30SPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH40"
|
self.model = "M30S+ VH40"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 74
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -354,10 +300,7 @@ class M30SPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH50"
|
self.model = "M30S+ VH50"
|
||||||
self.nominal_chips = 0
|
self.expected_chips = 64
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VH50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
@@ -366,5 +309,5 @@ class M30SPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VH60"
|
self.model = "M30S+ VH60"
|
||||||
self.nominal_chips = 66
|
self.expected_chips = 66
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user