|
|
|
|
@@ -11,145 +11,149 @@
|
|
|
|
|
[](https://pyasic.readthedocs.io/en/latest/)
|
|
|
|
|
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
## Intro
|
|
|
|
|
---
|
|
|
|
|
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
|
|
|
|
|
|
|
|
|
[Click here to view supported miner types](miners/supported_types.md)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
|
|
|
|
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
poetry install
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
|
|
|
|
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
pyenv install <python version number>
|
|
|
|
|
pyenv virtualenv <python version number> <env name>
|
|
|
|
|
pyenv activate <env name>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- [conda](https://docs.conda.io/en/latest/)
|
|
|
|
|
|
|
|
|
|
##### Installing `pyasic`
|
|
|
|
|
|
|
|
|
|
`python -m pip install pyasic` or `poetry install`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system.
|
|
|
|
|
`pyasic` can be installed directly from pip, either with `pip install pyasic`, or a different command if using a tool like `pypoetry`.
|
|
|
|
|
|
|
|
|
|
## Getting started
|
|
|
|
|
---
|
|
|
|
|
Getting started with `pyasic` is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
|
|
|
|
|
|
|
|
|
##### Scanning for miners
|
|
|
|
|
### Scanning for miners
|
|
|
|
|
To scan for miners in `pyasic`, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
|
|
|
|
The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
|
|
|
|
|
```python
|
|
|
|
|
import asyncio # asyncio for handling the async part
|
|
|
|
|
from pyasic.network import MinerNetwork # miner network handles the scanning
|
|
|
|
|
```python3
|
|
|
|
|
import asyncio# (1)!
|
|
|
|
|
from pyasic.network import MinerNetwork# (2)!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def scan_miners(): # define async scan function to allow awaiting
|
|
|
|
|
# create a miner network
|
|
|
|
|
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
|
|
|
|
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
|
|
|
|
|
async def scan_miners():# (3)!
|
|
|
|
|
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
|
|
|
|
|
|
|
|
|
# scan for miners asynchronously
|
|
|
|
|
# this will return the correct type of miners if they are supported with all functionality.
|
|
|
|
|
miners = await network.scan()
|
|
|
|
|
miners = await network.scan()# (5)!
|
|
|
|
|
print(miners)
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
|
|
|
|
asyncio.run(scan_miners())# (6)!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
1. `asyncio` for handling the async part.
|
|
|
|
|
2. `MinerNetwork` handles the scanning.
|
|
|
|
|
3. Define an async function to allow awaiting.
|
|
|
|
|
4. Create a miner network.
|
|
|
|
|
You can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
|
|
|
|
This uses the 192.168.1.0-255 network.
|
|
|
|
|
5. Scan for miners asynchronously.
|
|
|
|
|
This will return the correct type of miners (if they are supported) with all functionality.
|
|
|
|
|
6. Run the scan asynchronously with asyncio.run().
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
##### Creating miners based on IP
|
|
|
|
|
### Creating miners based on IP
|
|
|
|
|
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
|
|
|
|
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
|
|
|
|
```python
|
|
|
|
|
import asyncio # asyncio for handling the async part
|
|
|
|
|
from pyasic import get_miner # handles miner creation
|
|
|
|
|
import asyncio# (1)!
|
|
|
|
|
from pyasic import get_miner# (2)!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
async def get_miners():# (3)!
|
|
|
|
|
miner_1 = await get_miner("192.168.1.75")# (4)!
|
|
|
|
|
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)
|
|
|
|
|
miners = await asyncio.gather(*tasks)# (5)!
|
|
|
|
|
print(miners)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
|
|
|
|
asyncio.run(get_miners())# (6)!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
1. `asyncio` for handling the async part.
|
|
|
|
|
2. `get_miner` handles the miner type selection.
|
|
|
|
|
3. Define an async function to allow awaiting.
|
|
|
|
|
4. Get the miner.
|
|
|
|
|
5. Can also gather these, since they are async.
|
|
|
|
|
Gathering them will get them both at the same time.
|
|
|
|
|
This makes it much faster to get a lot of miners at a time.
|
|
|
|
|
6. Get the miners asynchronously with asyncio.run().
|
|
|
|
|
|
|
|
|
|
## Data gathering
|
|
|
|
|
---
|
|
|
|
|
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built-in function in each miner called `get_data()`.
|
|
|
|
|
This function will return an instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
|
|
|
|
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
|
|
|
|
|
|
|
|
|
##### One miner
|
|
|
|
|
### One miner
|
|
|
|
|
```python
|
|
|
|
|
import asyncio
|
|
|
|
|
from pyasic import get_miner
|
|
|
|
|
import asyncio# (1)!
|
|
|
|
|
from pyasic import get_miner# (2)!
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
async def gather_miner_data():# (3)!
|
|
|
|
|
miner = await get_miner("192.168.1.75")# (4)!
|
|
|
|
|
if miner is not None:# (5)!
|
|
|
|
|
miner_data = await miner.get_data()# (6)!
|
|
|
|
|
print(miner_data)# (7)!
|
|
|
|
|
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(gather_miner_data())
|
|
|
|
|
asyncio.run(gather_miner_data())# (9)!
|
|
|
|
|
```
|
|
|
|
|
---
|
|
|
|
|
##### Multiple miners
|
|
|
|
|
|
|
|
|
|
1. `asyncio` for handling the async part.
|
|
|
|
|
2. `get_miner` handles the miner type selection.
|
|
|
|
|
3. Define an async function to allow awaiting.
|
|
|
|
|
4. Get the miner.
|
|
|
|
|
5. Make sure the miner exists.
|
|
|
|
|
If this result is `None`, the miner may be offline.
|
|
|
|
|
6. Get data from the miner.
|
|
|
|
|
7. All the data from the dataclass.
|
|
|
|
|
8. Hashrate of the miner, with unit information.
|
|
|
|
|
9. Get the miner data asynchronously with asyncio.run().
|
|
|
|
|
|
|
|
|
|
### Multiple miners
|
|
|
|
|
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
|
|
|
|
|
import asyncio# (1)!
|
|
|
|
|
from pyasic.network import MinerNetwork# (2)!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
async def gather_miner_data():# (3)!
|
|
|
|
|
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
|
|
|
|
miners = await network.scan()# (5)!
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
print(miner_data)# (7)!
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(gather_miner_data())
|
|
|
|
|
asyncio.run(gather_miner_data())# (8)!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
1. `asyncio` for handling the async part.
|
|
|
|
|
2. `MinerNetwork` handles the scanning.
|
|
|
|
|
3. Define an async function to allow awaiting.
|
|
|
|
|
4. Create a miner network.
|
|
|
|
|
5. Scan for miners asynchronously.
|
|
|
|
|
6. Use `asyncio.gather()` with all the miners' `get_data()` functions to make them run together.
|
|
|
|
|
7. Print out the data one at a time.
|
|
|
|
|
8. Get the miner data asynchronously with asyncio.run().
|
|
|
|
|
|
|
|
|
|
## Miner control
|
|
|
|
|
---
|
|
|
|
|
`pyasic` exposes a standard interface for each miner using control functions.
|
|
|
|
|
Every miner class in `pyasic` must implement all the control functions defined in [`MinerProtocol`][pyasic.miners.base.MinerProtocol].
|
|
|
|
|
Every miner class in `pyasic` must implement all the following control functions.
|
|
|
|
|
|
|
|
|
|
These functions are
|
|
|
|
|
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
|
|
|
|
|
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
|
|
|
|
|
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
|
|
|
|
|
@@ -166,35 +170,41 @@ These functions are
|
|
|
|
|
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
|
|
|
|
|
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
|
|
|
|
|
|
|
|
|
|
##### Usage
|
|
|
|
|
### Usage
|
|
|
|
|
```python
|
|
|
|
|
import asyncio
|
|
|
|
|
from pyasic import get_miner
|
|
|
|
|
import asyncio# (1)!
|
|
|
|
|
from pyasic import get_miner# (2)!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def set_fault_light():
|
|
|
|
|
miner = await get_miner("192.168.1.20")
|
|
|
|
|
async def set_fault_light():# (3)!
|
|
|
|
|
miner = await get_miner("192.168.1.20")# (4)!
|
|
|
|
|
|
|
|
|
|
# call control function
|
|
|
|
|
await miner.fault_light_on()
|
|
|
|
|
await miner.fault_light_on()# (5)!
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(set_fault_light())
|
|
|
|
|
asyncio.run(set_fault_light())# (6)!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
1. `asyncio` for handling the async part.
|
|
|
|
|
2. `get_miner` handles the miner type selection.
|
|
|
|
|
3. Define an async function to allow awaiting.
|
|
|
|
|
4. Get the miner.
|
|
|
|
|
5. Call the miner control function.
|
|
|
|
|
6. Call the control function asynchronously with asyncio.run().
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Helper dataclasses
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
##### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
|
|
|
|
### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
|
|
|
|
|
|
|
|
|
`pyasic` implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
##### [`MinerData`][pyasic.data.MinerData]
|
|
|
|
|
### [`MinerData`][pyasic.data.MinerData]
|
|
|
|
|
|
|
|
|
|
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#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()`][pyasic.miners.base.MinerProtocol.get_data] function, and is used to have a consistent dataset across all returns.
|
|
|
|
|
|
|
|
|
|
You can call [`MinerData.as_dict()`][pyasic.data.MinerData.as_dict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
|
|
|
|
|
|
|
|
|
@@ -213,13 +223,13 @@ average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_m
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
##### [`MinerConfig`][pyasic.config.MinerConfig]
|
|
|
|
|
### [`MinerConfig`][pyasic.config.MinerConfig]
|
|
|
|
|
|
|
|
|
|
[`MinerConfig`][pyasic.config.MinerConfig] is `pyasic`'s way to represent a configuration file from a miner.
|
|
|
|
|
It is designed to unionize the configuration of all supported miner types, and is the return from [`get_config()`](#get-config).
|
|
|
|
|
It is designed to unionize the configuration of all supported miner types, and is the return from [`get_config()`][pyasic.miners.base.MinerProtocol.get_config].
|
|
|
|
|
|
|
|
|
|
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()`][pyasic.miners.base.MinerProtocol.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
|
|
|
|
|
@@ -241,7 +251,6 @@ if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
## Settings
|
|
|
|
|
---
|
|
|
|
|
`pyasic` has settings designed to make using large groups of miners easier. You can set the default password for all types of miners using the `pyasic.settings` module, used as follows:
|
|
|
|
|
@@ -252,7 +261,7 @@ from pyasic import settings
|
|
|
|
|
settings.update("default_antminer_web_password", "my_pwd")
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
##### Default values:
|
|
|
|
|
### Default values:
|
|
|
|
|
```
|
|
|
|
|
"network_ping_retries": 1,
|
|
|
|
|
"network_ping_timeout": 3,
|
|
|
|
|
|