Skip to content

Commit

Permalink
docs: use different docs theme, update docs and examples, and improve…
Browse files Browse the repository at this point in the history
… docs build
  • Loading branch information
UpstreamData committed Dec 4, 2024
1 parent 5b4f84a commit 46788e7
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 113 deletions.
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ repos:
hooks:
- id: trailing-whitespace
- id: check-yaml
name: check-yaml for mkdocs.yml
files: ^mkdocs\.yml$
args: [--unsafe]
- id: check-yaml
name: check-yaml for other YAML files
exclude: ^mkdocs\.yml$
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 24.10.0
Expand Down
22 changes: 10 additions & 12 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
tools: { python: "3.11" }
jobs:
pre_create_environment:
- asdf plugin add poetry
- asdf install poetry latest
- asdf global poetry latest
- poetry config virtualenvs.create false
post_install:
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --only docs

mkdocs:
configuration: mkdocs.yml

# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
configuration: mkdocs.yml
189 changes: 99 additions & 90 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,145 +11,149 @@
[![Read The Docs - Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/)
[![License - Apache 2.0](https://img.shields.io/github/license/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)

---
## Intro
---
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.

[Click here to view supported miner types](miners/supported_types.md)

---
## Installation

It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. 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],
Expand All @@ -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.

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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,
Expand Down
5 changes: 0 additions & 5 deletions docs/miners/base_miner.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,3 @@ You may not instantiate this class on its own, only subclass from it.
handler: python
options:
heading_level: 4

::: pyasic.miners.base.MinerProtocol
handler: python
options:
heading_level: 4
4 changes: 0 additions & 4 deletions docs/requirements.txt

This file was deleted.

Loading

0 comments on commit 46788e7

Please sign in to comment.